add conditional stuff

This commit is contained in:
water 2020-09-13 17:34:02 -04:00
parent f46acdf87a
commit 9ec9b5a22a
32 changed files with 843 additions and 44 deletions

View file

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

View file

@ -33,6 +33,14 @@
)
)
(defmacro blg ()
`(begin
(build-game)
(dgo-load "kernel" global #xf #x200000)
(dgo-load "game" global #xf #x200000)
)
)
(defmacro e ()
`(:exit)
)
@ -110,3 +118,53 @@
`(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))
)
)
)

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,10 @@
(desfun square (x)
(* x x)
)
(defmacro call-square (x)
(square x)
)
(call-square 2)

View file

@ -0,0 +1 @@
(+ 1 2 (/ (* 2 5) (+ 1 2)))

View file

@ -0,0 +1,3 @@
(let ((x 30))
(+ (/ x 10) 4)
)

View file

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

View file

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

View file

@ -0,0 +1,7 @@
(defglobalconstant c1 3)
(+ c1 (mlet ((c1 4))
c1
)
c1
)

View file

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

View file

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

View file

@ -0,0 +1,3 @@
(let ((x 3))
(+ (+ x 1) x)
)

View file

@ -0,0 +1,6 @@
(let ((x 3))
(* x 4)
(* x)
x
)

View file

@ -0,0 +1,5 @@
(let ((x 3))
(- x 1)
(- x)
x
)

View file

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

View file

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

View file

@ -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<std::string, Label>& 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");
}

View file

@ -31,7 +31,7 @@ class Env {
virtual void emit(std::unique_ptr<IR> 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<std::string, Label>& get_label_map();
RegVal* make_gpr(TypeSpec ts);
@ -54,7 +54,7 @@ class GlobalEnv : public Env {
void emit(std::unique_ptr<IR> 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<IRegConstraint>& 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<Label>());
return m_unnamed_labels.back().get();
}
int idx_in_file = -1;
template <typename T, class... Args>
@ -173,7 +185,8 @@ class FunctionEnv : public DeclareEnv {
std::string method_of_type_name = "#f";
bool is_asm_func = false;
std::vector<UnresolvedGoto> unresolved_gotos;
std::unordered_map<std::string, Val*> params;
std::vector<UnresolvedConditionalGoto> unresolved_cond_gotos;
std::unordered_map<std::string, RegVal*> params;
protected:
void resolve_gotos();
@ -189,6 +202,7 @@ class FunctionEnv : public DeclareEnv {
bool m_aligned_stack_required = false;
std::unordered_map<std::string, Label> m_labels;
std::vector<std::unique_ptr<Label>> m_unnamed_labels;
};
class BlockEnv : public Env {
@ -206,9 +220,9 @@ class BlockEnv : public Env {
class LexicalEnv : public DeclareEnv {
public:
explicit LexicalEnv(Env* parent) : DeclareEnv(parent) {}
Val* lexical_lookup(goos::Object sym) override;
RegVal* lexical_lookup(goos::Object sym) override;
std::string print() override;
std::unordered_map<std::string, Val*> vars;
std::unordered_map<std::string, RegVal*> vars;
};
class LabelEnv : public Env {
@ -232,6 +246,7 @@ class SymbolMacroEnv : public Env {
public:
explicit SymbolMacroEnv(Env* parent) : Env(parent) {}
std::unordered_map<std::shared_ptr<goos::SymbolObject>, goos::Object> macros;
std::string print() override { return "symbol-macro-env"; }
};
template <typename T>

View file

@ -77,8 +77,27 @@ void IR_LoadConstant64::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
auto dest_reg = get_reg(m_dest, allocs, irec);
s64 svalue = m_value;
if (svalue == 0) {
gen->add_instr(IGen::xor_gpr64_gpr64(dest_reg, dest_reg), irec);
} else if (svalue > 0) {
if (svalue < UINT32_MAX) {
gen->add_instr(IGen::mov_gpr64_u32(dest_reg, m_value), irec);
} else {
// need a real 64 bit load
gen->add_instr(IGen::mov_gpr64_u64(dest_reg, m_value), irec);
}
} else {
if (svalue >= INT32_MIN) {
gen->add_instr(IGen::mov_gpr64_s32(dest_reg, svalue), irec);
} else {
// need a real 64 bit load
gen->add_instr(IGen::mov_gpr64_u64(dest_reg, m_value), irec);
}
}
}
/////////////////////
// LoadSymbolPointer
@ -198,6 +217,8 @@ void IR_RegSet::do_codegen(emitter::ObjectGenerator* gen,
gen->add_instr(IGen::mov_gpr64_gpr64(dest_reg, val_reg), irec);
} else if (val_reg.is_xmm() && dest_reg.is_gpr()) {
gen->add_instr(IGen::movd_gpr32_xmm32(dest_reg, val_reg), irec);
} else if (val_reg.is_gpr() && dest_reg.is_xmm()) {
gen->add_instr(IGen::movd_xmm32_gpr32(dest_reg, val_reg), irec);
} else {
assert(false);
}
@ -371,6 +392,8 @@ std::string IR_IntegerMath::print() {
return fmt::format("subi {}, {}", m_dest->print(), m_arg->print());
case IntegerMathKind::IMUL_32:
return fmt::format("imul {}, {}", m_dest->print(), m_arg->print());
case IntegerMathKind::IDIV_32:
return fmt::format("idiv {}, {}", m_dest->print(), m_arg->print());
default:
assert(false);
}
@ -381,6 +404,10 @@ RegAllocInstr IR_IntegerMath::to_rai() {
rai.write.push_back(m_dest->ireg());
rai.read.push_back(m_dest->ireg());
rai.read.push_back(m_arg->ireg());
if (m_kind == IntegerMathKind::IDIV_32) {
rai.exclude.emplace_back(emitter::RDX);
}
return rai;
}
@ -400,8 +427,49 @@ void IR_IntegerMath::do_codegen(emitter::ObjectGenerator* gen,
auto dr = get_reg(m_dest, allocs, irec);
gen->add_instr(IGen::imul_gpr32_gpr32(dr, get_reg(m_arg, allocs, irec)), irec);
gen->add_instr(IGen::movsx_r64_r32(dr, dr), irec);
} break;
case IntegerMathKind::IDIV_32: {
gen->add_instr(IGen::cdq(), irec);
gen->add_instr(IGen::idiv_gpr32(get_reg(m_arg, allocs, irec)), irec);
gen->add_instr(IGen::movsx_r64_r32(get_reg(m_dest, allocs, irec), emitter::RAX), irec);
} break;
default:
assert(false);
}
}
/////////////////////
// FloatMath
/////////////////////
IR_FloatMath::IR_FloatMath(FloatMathKind kind, RegVal* dest, RegVal* arg)
: m_kind(kind), m_dest(dest), m_arg(arg) {}
std::string IR_FloatMath::print() {
switch (m_kind) {
case FloatMathKind::DIV_SS:
return fmt::format("divss {}, {}", m_dest->print(), m_arg->print());
default:
assert(false);
}
}
RegAllocInstr IR_FloatMath::to_rai() {
RegAllocInstr rai;
rai.write.push_back(m_dest->ireg());
rai.read.push_back(m_dest->ireg());
rai.read.push_back(m_arg->ireg());
return rai;
}
void IR_FloatMath::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
switch (m_kind) {
case FloatMathKind::DIV_SS:
gen->add_instr(
IGen::divss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec);
break;
default:
assert(false);
@ -442,3 +510,105 @@ void IR_StaticVarLoad::do_codegen(emitter::ObjectGenerator* gen,
assert(false);
}
}
/////////////////////
// ConditionalBranch
/////////////////////
std::string Condition::print() const {
switch (kind) {
case ConditionKind::NOT_EQUAL:
return a->print() + " != " + b->print();
case ConditionKind::EQUAL:
return a->print() + " == " + b->print();
case ConditionKind::LEQ:
return a->print() + " <= " + b->print();
case ConditionKind::GEQ:
return a->print() + " >= " + b->print();
case ConditionKind::LT:
return a->print() + " < " + b->print();
case ConditionKind::GT:
return a->print() + " > " + b->print();
default:
throw std::runtime_error("unknown condition type in GoalCondition::print()");
}
}
RegAllocInstr Condition::to_rai() {
RegAllocInstr rai;
rai.read.push_back(a->ireg());
rai.read.push_back(b->ireg());
return rai;
}
IR_ConditionalBranch::IR_ConditionalBranch(const Condition& _condition, Label _label)
: condition(_condition), label(_label) {}
std::string IR_ConditionalBranch::print() {
// todo, float/signed info?
return fmt::format("j({}) {}", condition.print(), label.print());
}
RegAllocInstr IR_ConditionalBranch::to_rai() {
auto rai = condition.to_rai();
assert(m_resolved);
rai.jumps.push_back(label.idx);
return rai;
}
void IR_ConditionalBranch::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
Instruction jump_instr(0);
assert(m_resolved);
switch (condition.kind) {
case ConditionKind::EQUAL:
jump_instr = IGen::je_32();
break;
case ConditionKind::NOT_EQUAL:
jump_instr = IGen::jne_32();
break;
case ConditionKind::LEQ:
if (condition.is_signed) {
jump_instr = IGen::jle_32();
} else {
jump_instr = IGen::jbe_32();
}
break;
case ConditionKind::GEQ:
if (condition.is_signed) {
jump_instr = IGen::jge_32();
} else {
jump_instr = IGen::jae_32();
}
break;
case ConditionKind::LT:
if (condition.is_signed) {
jump_instr = IGen::jl_32();
} else {
jump_instr = IGen::jb_32();
}
break;
case ConditionKind::GT:
if (condition.is_signed) {
jump_instr = IGen::jg_32();
} else {
jump_instr = IGen::ja_32();
}
break;
default:
assert(false);
}
if (condition.is_float) {
assert(false); // for now
} else {
gen->add_instr(IGen::cmp_gpr64_gpr64(get_reg(condition.a, allocs, irec),
get_reg(condition.b, allocs, irec)),
irec);
}
auto jump_rec = gen->add_instr(jump_instr, irec);
gen->link_instruction_jump(jump_rec, gen->get_future_ir_record_in_same_func(irec, label.idx));
}

View file

@ -239,4 +239,33 @@ class IR_FloatMath : public IR {
RegVal* m_arg;
};
enum class ConditionKind { NOT_EQUAL, EQUAL, LEQ, LT, GT, GEQ, INVALID_CONDITION };
struct Condition {
ConditionKind kind = ConditionKind::INVALID_CONDITION;
RegVal* a = nullptr;
RegVal* b = nullptr;
bool is_signed = false;
bool is_float = false;
RegAllocInstr to_rai();
std::string print() const;
};
class IR_ConditionalBranch : public IR {
public:
IR_ConditionalBranch(const Condition& condition, Label _label);
std::string print() override;
RegAllocInstr to_rai() override;
void do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) override;
void mark_as_resolved() { m_resolved = true; }
Condition condition;
Label label;
private:
bool m_resolved = false;
};
#endif // JAK_IR_H

View file

@ -168,3 +168,7 @@ emitter::RegKind Compiler::get_preferred_reg_kind(const TypeSpec& ts) {
assert(false);
}
}
bool Compiler::is_none(Val* in) {
return dynamic_cast<None*>(in);
}

View file

@ -21,8 +21,12 @@ RegVal* Val::to_gpr(Env* fe) {
* Fallback to_xmm if a more optimized one is not provided.
*/
RegVal* Val::to_xmm(Env* fe) {
(void)fe;
throw std::runtime_error("Val::to_xmm NYI"); // todo
auto rv = to_reg(fe);
if (rv->ireg().kind == emitter::RegKind::XMM) {
return rv;
} else {
assert(false);
}
}
RegVal* RegVal::to_reg(Env* fe) {
@ -35,7 +39,9 @@ RegVal* RegVal::to_gpr(Env* fe) {
if (m_ireg.kind == emitter::RegKind::GPR) {
return this;
} else {
throw std::runtime_error("RegVal::to_gpr NYI"); // todo
auto re = fe->make_gpr(m_ts);
fe->emit(std::make_unique<IR_RegSet>(re, this));
return re;
}
}
@ -44,7 +50,9 @@ RegVal* RegVal::to_xmm(Env* fe) {
if (m_ireg.kind == emitter::RegKind::XMM) {
return this;
} else {
throw std::runtime_error("RegVal::to_xmm NYI"); // todo
auto re = fe->make_xmm(m_ts);
fe->emit(std::make_unique<IR_RegSet>(re, this));
return re;
}
}

View file

@ -45,13 +45,13 @@ static const std::unordered_map<
{"seval", &Compiler::compile_seval},
//
// // CONTROL FLOW
// {"cond", &Compiler::compile_cond},
// {"when-goto", &Compiler::compile_when_goto},
{"cond", &Compiler::compile_cond},
{"when-goto", &Compiler::compile_when_goto},
//
// // DEFINITION
{"define", &Compiler::compile_define},
{"define-extern", &Compiler::compile_define_extern},
// {"set!", &Compiler::compile_set},
{"set!", &Compiler::compile_set},
// {"defun-extern", &Compiler::compile_defun_extern},
// {"declare-method", &Compiler::compile_declare_method},
//
@ -72,7 +72,7 @@ static const std::unordered_map<
{"inline", &Compiler::compile_inline},
// {"with-inline", &Compiler::compile_with_inline},
// {"rlet", &Compiler::compile_rlet},
// {"mlet", &Compiler::compile_mlet},
// {"get-ra-ptr", &Compiler::compile_get_ra_ptr},
//
//
@ -80,6 +80,7 @@ static const std::unordered_map<
// // MACRO
// {"print-type", &Compiler::compile_print_type},
{"quote", &Compiler::compile_quote},
{"mlet", &Compiler::compile_mlet},
// {"defconstant", &Compiler::compile_defconstant},
//
// // OBJECT
@ -113,14 +114,14 @@ static const std::unordered_map<
// {"logxor", &Compiler::compile_logxor},
// {"logand", &Compiler::compile_logand},
// {"lognot", &Compiler::compile_lognot},
// {"=", &Compiler::compile_condition_as_bool},
// {"!=", &Compiler::compile_condition_as_bool},
// {"eq?", &Compiler::compile_condition_as_bool},
// {"not", &Compiler::compile_condition_as_bool},
// {"<=", &Compiler::compile_condition_as_bool},
// {">=", &Compiler::compile_condition_as_bool},
// {"<", &Compiler::compile_condition_as_bool},
// {">", &Compiler::compile_condition_as_bool},
{"=", &Compiler::compile_condition_as_bool},
{"!=", &Compiler::compile_condition_as_bool},
{"eq?", &Compiler::compile_condition_as_bool},
{"not", &Compiler::compile_condition_as_bool},
{"<=", &Compiler::compile_condition_as_bool},
{">=", &Compiler::compile_condition_as_bool},
{"<", &Compiler::compile_condition_as_bool},
{">", &Compiler::compile_condition_as_bool},
//
// // BUILDER (build-dgo/build-cgo?)
{"build-dgos", &Compiler::compile_build_dgo},
@ -202,7 +203,14 @@ Val* Compiler::compile_symbol(const goos::Object& form, Env* env) {
return get_none();
}
// todo mlet
auto mlet_env = get_parent_env_of_type<SymbolMacroEnv>(env);
while (mlet_env) {
auto mlkv = mlet_env->macros.find(form.as_symbol());
if (mlkv != mlet_env->macros.end()) {
return compile_error_guard(mlkv->second, env);
}
mlet_env = get_parent_env_of_type<SymbolMacroEnv>(mlet_env->parent());
}
auto lexical = env->lexical_lookup(form);
if (lexical) {

View file

@ -0,0 +1,225 @@
#include "goalc/compiler/Compiler.h"
/*!
* Convert a condition expression into a GoalCondition for use in a conditional branch.
* The reason for this design is to allow an optimization for
* (if (< a b) ...) to be compiled without actually computing a true/false value for the (< a b)
* expression. Instead, it will generate a cmp + jle sequence of instructions, which is much faster.
*
* This can be applied to _any_ GOAL form, and will return a GoalCondition which can be used with a
* Branch IR to branch if the condition is true/false. When possible it applies the optimization
* mentioned above, but will be fine in other cases too. I believe the original GOAL compiler had a
* similar system.
*
* Will branch if the condition is true and the invert flag is false.
* Will branch if the condition is false and the invert flag is true.
*/
Condition Compiler::compile_condition(const goos::Object& condition, Env* env, bool invert) {
Condition gc;
// These are special conditions that can be optimized into a cmp + jxx instruction.
const std::unordered_map<std::string, ConditionKind> conditions_inverted = {
{"!=", ConditionKind::EQUAL}, {"eq?", ConditionKind::NOT_EQUAL},
{"neq?", ConditionKind::EQUAL}, {"=", ConditionKind::NOT_EQUAL},
{">", ConditionKind::LEQ}, {"<", ConditionKind::GEQ},
{">=", ConditionKind::LT}, {"<=", ConditionKind::GT}};
const std::unordered_map<std::string, ConditionKind> conditions_normal = {
{"!=", ConditionKind::NOT_EQUAL}, {"eq?", ConditionKind::EQUAL},
{"neq?", ConditionKind::NOT_EQUAL}, {"=", ConditionKind::EQUAL},
{">", ConditionKind::GT}, {"<", ConditionKind::LT},
{">=", ConditionKind::GEQ}, {"<=", ConditionKind::LEQ}};
// possibly a form with an optimizable condition?
if (condition.is_pair()) {
auto first = pair_car(condition);
auto rest = pair_cdr(condition);
if (first.is_symbol()) {
auto fas = first.as_symbol();
// if there's a not, we can just try again to get an optimization with the invert flipped.
if (fas->name == "not") {
auto arg = pair_car(rest);
if (!pair_cdr(rest).is_empty_list()) {
throw_compile_error(condition, "A condition with \"not\" can have only one argument");
}
return compile_condition(arg, env, !invert);
}
auto& conditions = invert ? conditions_inverted : conditions_normal;
auto nc_kv = conditions.find(fas->name);
if (nc_kv != conditions.end()) {
// it is an optimizable condition!
gc.kind = nc_kv->second;
// get args...
auto args = get_va(rest, rest);
va_check(rest, args, {{}, {}}, {});
auto first_arg = compile_error_guard(args.unnamed.at(0), env);
auto second_arg = compile_error_guard(args.unnamed.at(1), env);
if (is_number(first_arg->type())) {
// it's a numeric comparison, so we may need to coerce.
auto math_mode = get_math_mode(first_arg->type());
// there is no support for comparing bintegers, so we turn the binteger comparison into an
// integer.
if (is_binteger(first_arg->type())) {
first_arg = number_to_integer(first_arg, env);
}
// convert second one to appropriate type as needed
if (is_number(second_arg->type())) {
second_arg = to_math_type(second_arg, math_mode, env);
}
}
// use signed comparison only if first argument is a signed integer (or coerced binteger)
// (floating point ignores this)
gc.is_signed = is_singed_integer_or_binteger(first_arg->type());
// pick between a floating point and an integer comparison.
if (is_float(first_arg->type())) {
gc.a = first_arg->to_xmm(env);
gc.b = second_arg->to_xmm(env);
gc.is_float = true;
} else {
gc.a = first_arg->to_gpr(env);
gc.b = second_arg->to_gpr(env);
}
return gc;
}
}
}
// not something we can process more. Just check if we get false.
// todo - it's possible to optimize a false comparison because the false offset is zero
gc.kind = invert ? ConditionKind::EQUAL : ConditionKind::NOT_EQUAL;
gc.a = compile_error_guard(condition, env)->to_gpr(env);
gc.b = compile_get_sym_obj("#f", env)->to_gpr(env);
return gc;
}
Val* Compiler::compile_condition_as_bool(const goos::Object& form,
const goos::Object& rest,
Env* env) {
(void)rest;
auto c = compile_condition(form, env, true);
auto result = compile_get_sym_obj("#f", env)->to_gpr(env); // todo - can be optimized.
Label label(get_parent_env_of_type<FunctionEnv>(env), -5);
auto branch_ir = std::make_unique<IR_ConditionalBranch>(c, label);
auto branch_ir_ref = branch_ir.get();
env->emit(std::move(branch_ir));
// move true
env->emit(std::make_unique<IR_RegSet>(
result, compile_get_sym_obj("#t", env)->to_gpr(env))); // todo, can be optimized
branch_ir_ref->label.idx = branch_ir_ref->label.func->code().size();
branch_ir_ref->mark_as_resolved();
return result;
}
Val* Compiler::compile_when_goto(const goos::Object& form, const goos::Object& _rest, Env* env) {
(void)form;
auto* rest = &_rest;
auto condition_code = pair_car(*rest);
rest = &pair_cdr(*rest);
auto label = symbol_string(pair_car(*rest));
expect_empty_list(pair_cdr(*rest));
// compile as condition (will set flags register with a cmp instruction)
auto condition = compile_condition(condition_code, env, false);
auto branch = std::make_unique<IR_ConditionalBranch>(condition, Label());
get_parent_env_of_type<FunctionEnv>(env)->unresolved_cond_gotos.push_back({branch.get(), label});
env->emit(std::move(branch));
return get_none();
}
Val* Compiler::compile_cond(const goos::Object& form, const goos::Object& rest, Env* env) {
auto result = env->make_gpr(m_ts.make_typespec("object"));
auto fenv = get_parent_env_of_type<FunctionEnv>(env);
auto end_label = fenv->alloc_unnamed_label();
end_label->func = fenv;
end_label->idx = -3; // placeholder
bool got_else = false;
std::vector<TypeSpec> case_result_types;
for_each_in_list(rest, [&](const goos::Object& o) {
auto test = pair_car(o);
auto clauses = pair_cdr(o);
if (got_else) {
throw_compile_error(form, "cannot have anything after an else in a cond");
}
if (test.is_symbol() && symbol_string(test) == "else") {
got_else = true;
}
if (got_else) {
// just set the output to this.
Val* case_result = get_none();
for_each_in_list(clauses, [&](const goos::Object& clause) {
case_result = compile_error_guard(clause, env);
});
case_result_types.push_back(case_result->type());
// optimization - if we get junk, don't bother moving it, just leave junk in return.
if (!is_none(case_result)) {
env->emit(std::make_unique<IR_RegSet>(result, case_result->to_gpr(env)));
}
} else {
// CONDITION CHECK
auto condition = compile_condition(test, env, true);
// BRANCH FWD
auto branch_ir = std::make_unique<IR_ConditionalBranch>(condition, Label());
auto branch_ir_ref = branch_ir.get();
branch_ir->mark_as_resolved();
env->emit(std::move(branch_ir));
// CODE
Val* case_result = get_none();
for_each_in_list(clauses, [&](const goos::Object& clause) {
case_result = compile_error_guard(clause, env);
});
case_result_types.push_back(case_result->type());
if (!is_none(case_result)) {
env->emit(std::make_unique<IR_RegSet>(result, case_result->to_gpr(env)));
}
// GO TO END
auto ir_goto_end = std::make_unique<IR_GotoLabel>(end_label);
env->emit(std::move(ir_goto_end));
// PATCH BRANCH FWD
branch_ir_ref->label.idx = fenv->code().size();
}
});
if (!got_else) {
// if no else, clause, return #f. But don't retype. I don't know how I feel about this typing
// setup.
auto get_false = std::make_unique<IR_LoadSymbolPointer>(result, "#f");
env->emit(std::move(get_false));
}
result->set_type(m_ts.lowest_common_ancestor(case_result_types));
// PATCH END
end_label->idx = fenv->code().size();
return result;
}

View file

@ -61,3 +61,42 @@ Val* Compiler::compile_define_extern(const goos::Object& form, const goos::Objec
m_symbol_types[symbol_string(sym)] = new_type;
return get_none();
}
Val* Compiler::compile_set(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}, {}}, {});
auto& destination = args.unnamed.at(0);
auto source = compile_error_guard(args.unnamed.at(1), env)->to_reg(env);
if (destination.is_symbol()) {
// destination is just a symbol, so it's either a lexical variable or a global.
// first, attempt a lexical set:
auto lex_place = env->lexical_lookup(destination);
if (lex_place) {
// typecheck and set!
typecheck(form, lex_place->type(), source->type(), "set! lexical variable");
env->emit(std::make_unique<IR_RegSet>(lex_place, source));
return source;
} else {
// try to set symbol
auto existing = m_symbol_types.find(destination.as_symbol()->name);
if (existing == m_symbol_types.end()) {
throw_compile_error(
form, "could not find something called " + symbol_string(destination) + " to set!");
} else {
typecheck(form, existing->second, source->type(), "set! global symbol");
auto fe = get_parent_env_of_type<FunctionEnv>(env);
auto sym_val =
fe->alloc_val<SymbolVal>(symbol_string(destination), m_ts.make_typespec("symbol"));
auto result_in_gpr = source->to_gpr(env);
env->emit(std::make_unique<IR_SetSymbolValue>(sym_val, result_in_gpr));
return result_in_gpr;
}
}
} else {
throw_compile_error(form, "Set not implemented for this yet");
}
assert(false);
}

View file

@ -116,3 +116,21 @@ Val* Compiler::compile_defglobalconstant(const goos::Object& form,
return get_none();
}
Val* Compiler::compile_mlet(const goos::Object& form, const goos::Object& rest, Env* env) {
auto defs = pair_car(rest);
auto body = pair_cdr(rest);
auto fenv = get_parent_env_of_type<FunctionEnv>(env);
auto menv = fenv->alloc_env<SymbolMacroEnv>(env);
for_each_in_list(defs, [&](const goos::Object& o) {
auto def_args = get_va(form, o);
va_check(form, def_args, {goos::ObjectType::SYMBOL, {}}, {});
menv->macros[def_args.unnamed.at(0).as_symbol()] = def_args.unnamed.at(1);
});
Val* result = get_none();
for_each_in_list(body, [&](const goos::Object& o) { result = compile_error_guard(o, menv); });
return result;
}

View file

@ -48,7 +48,7 @@ Val* Compiler::number_to_integer(Val* in, Env* env) {
} else if (is_integer(ts)) {
return in;
} else {
assert(false);
throw std::runtime_error("Can't convert " + in->print() + " to an integer.");
}
}
@ -215,12 +215,31 @@ Val* Compiler::compile_div(const goos::Object& form, const goos::Object& rest, E
auto first_type = first_val->type();
auto math_type = get_math_mode(first_type);
switch (math_type) {
case MATH_FLOAT:
case MATH_INT: {
auto fe = get_parent_env_of_type<FunctionEnv>(env);
auto first_thing = first_val->to_gpr(env);
auto result = env->make_ireg(first_type, emitter::RegKind::GPR);
env->emit(std::make_unique<IR_RegSet>(result, first_thing));
{
IRegConstraint result_rax_constraint;
result_rax_constraint.instr_idx = fe->code().size() - 1;
result_rax_constraint.ireg = result->ireg();
result_rax_constraint.desired_register = emitter::RAX;
fe->constrain(result_rax_constraint);
env->emit(std::make_unique<IR_IntegerMath>(
IntegerMathKind::IDIV_32, result,
to_math_type(compile_error_guard(args.unnamed.at(1), env), math_type, env)->to_gpr(env)));
return result;
}
case MATH_FLOAT: {
auto result = env->make_xmm(first_type);
env->emit(std::make_unique<IR_RegSet>(result, first_val->to_xmm(env)));
env->emit(std::make_unique<IR_FloatMath>(FloatMathKind::DIV_SS, result, to_math_type(compile_error_guard(args.unnamed.at(1), env), math_type, env)->to_xmm(env))));
env->emit(std::make_unique<IR_FloatMath>(
FloatMathKind::DIV_SS, result,
to_math_type(compile_error_guard(args.unnamed.at(1), env), math_type, env)->to_xmm(env)));
return result;
}
case MATH_INVALID:

View file

@ -4,7 +4,7 @@
int main(int argc, char** argv) {
(void)argc;
(void)argv;
printf("goal compiler\n");
printf("GOAL Compiler\n");
Compiler compiler;
compiler.execute_repl();

View file

@ -225,6 +225,11 @@ TEST(CompilerAndRuntime, CompilerTests) {
runner.run_test("test-sub-1.gc", {"4\n"});
runner.run_test("test-sub-2.gc", {"4\n"});
runner.run_test("test-mul-1.gc", {"-12\n"});
runner.run_test("test-three-reg-add.gc", {"7\n"});
runner.run_test("test-three-reg-sub.gc", {"3\n"});
runner.run_test("test-three-reg-mult.gc", {"3\n"});
runner.run_test("test-div-1.gc", {"6\n"});
runner.run_test("test-div-2.gc", {"7\n"});
expected = "test-string";
runner.run_test("test-string-symbol.gc", {expected}, expected.size());
@ -234,6 +239,15 @@ TEST(CompilerAndRuntime, CompilerTests) {
// float
runner.run_test("test-floating-point-1.gc", {"1067316150\n"});
runner.run_test("test-mlet.gc", {"10\n"});
runner.run_test("test-set-symbol.gc", {"22\n"});
runner.run_test("test-defsmacro-defgmacro.gc", {"20\n"});
runner.run_test("test-desfun.gc", {"4\n"});
runner.run_test("test-factorial-recursive.gc", {"3628800\n"});
runner.run_test("test-factorial-loop.gc", {"3628800\n"});
runner.run_test("test-protect.gc", {"33\n"});
compiler.shutdown_target();
runtime_thread.join();
runner.print_summary();