add more math

This commit is contained in:
water 2020-09-14 20:24:05 -04:00
parent f972cdcab5
commit a47620fd6d
17 changed files with 419 additions and 31 deletions

View File

@ -45,6 +45,12 @@
`(:exit)
)
(defmacro db ()
`(begin
(set-config! print-ir #t)
(set-config! print-regalloc #t)
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CONDITIONAL COMPILATION

View File

@ -69,9 +69,51 @@
(/ x y)
)
;; todo ash
;; todo mod
;; todo rem
(defun ash ((value integer) (shift-amount integer))
"Arithmetic shift value by shift-amount.
A positive shift-amount will shift to the left and a negative will shift to the right.
"
;; currently the compiler does not support "ash", so this function is also used to implement "ash".
;; in the future, the compiler should be able to use constant propagation to turn constant shifts
;; into x86 constant shifts when possible (which are faster). The GOAL compiler seems to do this.
;; The original implementation was inline assembly, to take advantage of branch delay slots:
;; (or v1 a0 r0) ;; likely inserted by register coloring, not entirely needed
;; (bgezl a1 end) ;; branch to function end if positive shift (left)...
;; (dsllv v0 v1 a1) ;; do left shift in delay slot
;;
;; (dsubu a0 r0 a1) ;; negative shift amount for right shift
;; (dsrav v0 v1 a0) ;; do right shift
;; (label end)
(declare (inline))
(if (> shift-amount 0)
;; these correspond to x86-64 variable shift instructions.
;; the exact behavior of GOAL shifts (signed/unsigned) are unknown so for now shifts must
;; be manually specified.
(shlv value shift-amount)
(sarv value (- shift-amount))
)
)
(defun mod ((a integer) (b integer))
"Compute mod. It does what you expect for positive numbers. For negative numbers, nobody knows what to expect.
This is a 32-bit operation. It uses an idiv on x86 and gets the remainder."
;; The original implementation is div, mfhi
;; todo - verify this is exactly the same as the PS2.
(mod a b)
)
(defun rem ((a integer) (b integer))
"Compute remainder (32-bit). It is identical to mod. It uses a idiv and gets the remainder"
;; The original implementation is div, mfhi
;; todo - verify this is exactly the same as the PS2.
(mod a b)
)
(defun abs ((a int))
"Take the absolute value of an integer"
@ -84,7 +126,7 @@
;; (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
@ -94,13 +136,13 @@
(defun min ((a integer) (b integer))
"Compute minimum."
;; The original implementation was inline assembly, to take advantage of branch delay slots:
;; (or v0 a0 r0) ;; move first arg to output (case of second arg being min)
;; (or v1 a1 r0) ;; move second arg to v1 (likely strange coloring)
;; (slt a0 v0 v1) ;; compare args
;; (movz v0 v1 a0) ;; conditional move the second arg to v0 if it's the minimum
(declare (inline))
(if (> a b) b a)
)
@ -111,10 +153,34 @@
(if (> a b) a b)
)
;; todo logior
;; todo lognor
;; todo logxor
;; todo lognot
(defun logior ((a integer) (b integer))
"Compute the bitwise inclusive-or"
(logior a b)
)
(defun logand ((a integer) (b integer))
"Compute the bitwise and"
(logand a b)
)
(defun lognor ((a integer) (b integer))
"Compute not or."
;; Note - MIPS has a 'nor' instruction, but x86 doesn't.
;; the GOAL x86 compiler therefore doesn't have a nor operation,
;; so lognor is implemented by this inline function instead.
(declare (inline))
(lognot (logior a b))
)
(defun logxor ((a integer) (b integer))
"Compute the logical exclusive-or"
(logxor a b)
)
(defun lognot ((a integer))
"Compute the bitwise not"
(lognot a)
)
(defun false-func ()
"Return false"
@ -135,6 +201,9 @@
;; format. (In OpenGOAL, there's actually two trampoline functions, to make the 8 arguments all work.)
;; For some reason, the C Kernel names this trampoline function _format. We need to set the value of format
;; _format in order for format to work.
;; I suspect this was to let us define (yet another) function here which set up C-style var args (supported from C Kernel)
;; or 128-bit arguments (unimplemented in C Kernel), but both of these were never finished.
(define format _format)
;; todo bfloat

10
goal_src/test/test-ash.gc Normal file
View File

@ -0,0 +1,10 @@
(defun ash ((value integer) (shift-amount integer))
(declare (inline))
(if (> shift-amount 0)
(shlv value shift-amount)
(sarv value (- shift-amount))
)
)
(+ (ash (+ 1 2) (/ 6 2)) (ash (- 12) (- 1)))

View File

@ -0,0 +1,49 @@
(define-extern _format function)
(define format _format)
(format #t "test newline~%newline~%")
(format #t "test tilde ~~ ~%")
;; test g, G
(format #t "test A print boxed-string: ~a~%" "boxed string!")
(format #t "test A print symbol: ~A~%" 'a-symbol)
(format #t "test A make boxed object longer: ~17A!~%" "srt")
(format #t "test A non-default pad: ~12,'za~%" 'pad-me)
(format #t "test A shorten(4): ~4a~%" 'a234567)
(format #t "test A don'tchange(4): ~4a~%" 'a234)
(format #t "test A shorten with pad(4): ~4,'za~%" 'shorten-me)
(format #t "test A a few things ~a ~a ~a ~a~%" "one thing" 'a-second integer print)
(format #t "test S ~s ~s ~s~%" "a string" 'a-symbol "another string!")
(format #t "test C ~c ~c~%" 41 #x5d)
(format #t "test P (no type) ~p~%" print)
(format #t "test P (with type) ~`integer`p~%" print)
(format #t "test I (no type) ~i" inspect)
(format #t "test I (with type) ~`integer`i" inspect)
(format #t "test X ~x ~10x ~10,'.x ~3x ~3,'.x~%" #xbaadbeef #x2 #x3 #x0badbeef #x0badbeef)
(format #t "test D ~d ~10d ~10,'.d ~3d ~3,'.d~%" #xbaadbeef #x2 #x3 #x0badbeef #x0badbeef)
(format #t "test B ~b ~10b ~10,'.b ~3b ~3,'.b~%" #xbaadbeef #x2 #x3 #x0badbeef #x0badbeef)
;; test f, F, r, R, m, M
(format #t "test E ~E ~e ~e~%" 1 20 301)
(format #t "test pass through ~10,'b,2W~%")
(format #t "test tab~Taftertab~%")
(format #t "test many ~d ~d ~d ~d ~d~%" 1 2 3 4 5)
;; test floats
(format #t "test f ~F ~12,'0,2f ~f~%" -1.234 1.234 -1.234)
(format #t "test r ~R ~12,'0,2r ~r~%" 1.234 1.567 1.987)
(format #t "test m ~M ~12,'0,2m ~m~%" 1.234 1.567 1.987)
(format #t "test as float ~`float`P~%" -99987623.23123)
0

View File

@ -0,0 +1 @@
(logand #b100100 #b011100) ; 4

View File

@ -0,0 +1 @@
(logior #b100100 #b011100)

View File

@ -0,0 +1 @@
(logxor #b100100 #b011100)

View File

@ -0,0 +1,3 @@
(let ((x 33))
(+ (mod x 10) 4)
)

View File

@ -0,0 +1,2 @@
(define a-negative-symbol -123)
a-negative-symbol

View File

@ -0,0 +1,16 @@
(defun first-function ((a integer))
(+ a 2)
(+ a 3)
(+ a 4)
(+ a 6)
(+ a 10)
)
(defun second-function ((b integer))
(if (> 2 b)
0
10
)
)
(second-function (second-function (second-function 3)))

View File

@ -0,0 +1,2 @@
(+ (shlv 2 3) (shlv 1 0) (shlv 0 4) (shrv 2 3) (shrv 10 2) (shlv -2 1) (sarv -16 2))
;; 16 1 0 0 2 -4 -4

View File

@ -101,6 +101,8 @@ class Compiler {
Val* to_math_type(Val* in, MathMode mode, Env* env);
bool is_none(Val* in);
Val* compile_variable_shift(const RegVal* in, const RegVal* sa, Env* env, IntegerMathKind kind);
public:
// Atoms
@ -146,6 +148,14 @@ class Compiler {
Val* compile_sub(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_mul(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_div(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_shlv(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_sarv(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_shrv(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_mod(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_logxor(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_lognot(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_logand(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_logior(const goos::Object& form, const goos::Object& rest, Env* env);
// Function
Val* compile_lambda(const goos::Object& form, const goos::Object& rest, Env* env);

View File

@ -394,6 +394,22 @@ std::string IR_IntegerMath::print() {
return fmt::format("imul {}, {}", m_dest->print(), m_arg->print());
case IntegerMathKind::IDIV_32:
return fmt::format("idiv {}, {}", m_dest->print(), m_arg->print());
case IntegerMathKind::IMOD_32:
return fmt::format("imod {}, {}", m_dest->print(), m_arg->print());
case IntegerMathKind::SARV_64:
return fmt::format("sarv {}, {}", m_dest->print(), m_arg->print());
case IntegerMathKind::SHLV_64:
return fmt::format("shlv {}, {}", m_dest->print(), m_arg->print());
case IntegerMathKind::SHRV_64:
return fmt::format("shrv {}, {}", m_dest->print(), m_arg->print());
case IntegerMathKind::AND_64:
return fmt::format("and {}, {}", m_dest->print(), m_arg->print());
case IntegerMathKind::OR_64:
return fmt::format("or {}, {}", m_dest->print(), m_arg->print());
case IntegerMathKind::XOR_64:
return fmt::format("xor {}, {}", m_dest->print(), m_arg->print());
case IntegerMathKind::NOT_64:
return fmt::format("not {}", m_dest->print());
default:
assert(false);
}
@ -403,9 +419,12 @@ RegAllocInstr IR_IntegerMath::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());
if (m_kind == IntegerMathKind::IDIV_32) {
if (m_kind != IntegerMathKind::NOT_64) {
rai.read.push_back(m_arg->ireg());
}
if (m_kind == IntegerMathKind::IDIV_32 || m_kind == IntegerMathKind::IMOD_32) {
rai.exclude.emplace_back(emitter::RDX);
}
return rai;
@ -423,6 +442,34 @@ void IR_IntegerMath::do_codegen(emitter::ObjectGenerator* gen,
gen->add_instr(
IGen::sub_gpr64_gpr64(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec);
break;
case IntegerMathKind::AND_64:
gen->add_instr(
IGen::and_gpr64_gpr64(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec);
break;
case IntegerMathKind::OR_64:
gen->add_instr(
IGen::or_gpr64_gpr64(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec);
break;
case IntegerMathKind::XOR_64:
gen->add_instr(
IGen::xor_gpr64_gpr64(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec);
break;
case IntegerMathKind::NOT_64:
gen->add_instr(IGen::not_gpr64(get_reg(m_dest, allocs, irec)), irec);
assert(!m_arg);
break;
case IntegerMathKind::SHLV_64:
gen->add_instr(IGen::shl_gpr64_cl(get_reg(m_dest, allocs, irec)), irec);
assert(get_reg(m_arg, allocs, irec) == emitter::RCX);
break;
case IntegerMathKind::SHRV_64:
gen->add_instr(IGen::shr_gpr64_cl(get_reg(m_dest, allocs, irec)), irec);
assert(get_reg(m_arg, allocs, irec) == emitter::RCX);
break;
case IntegerMathKind::SARV_64:
gen->add_instr(IGen::sar_gpr64_cl(get_reg(m_dest, allocs, irec)), irec);
assert(get_reg(m_arg, allocs, irec) == emitter::RCX);
break;
case IntegerMathKind::IMUL_32: {
auto dr = get_reg(m_dest, allocs, irec);
gen->add_instr(IGen::imul_gpr32_gpr32(dr, get_reg(m_arg, allocs, irec)), irec);
@ -434,6 +481,13 @@ void IR_IntegerMath::do_codegen(emitter::ObjectGenerator* gen,
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;
case IntegerMathKind::IMOD_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::RDX), irec);
} break;
default:
assert(false);
}

View File

@ -103,17 +103,17 @@ static const std::unordered_map<
{"-", &Compiler::compile_sub},
{"*", &Compiler::compile_mul},
{"/", &Compiler::compile_div},
// {"shlv", &Compiler::compile_shlv},
// {"shrv", &Compiler::compile_shrv},
// {"sarv", &Compiler::compile_sarv},
{"shlv", &Compiler::compile_shlv},
{"shrv", &Compiler::compile_shrv},
{"sarv", &Compiler::compile_sarv},
// {"shl", &Compiler::compile_shl},
// {"shr", &Compiler::compile_shr},
// {"sar", &Compiler::compile_sar},
// {"mod", &Compiler::compile_mod},
// {"logior", &Compiler::compile_logior},
// {"logxor", &Compiler::compile_logxor},
// {"logand", &Compiler::compile_logand},
// {"lognot", &Compiler::compile_lognot},
{"mod", &Compiler::compile_mod},
{"logior", &Compiler::compile_logior},
{"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},

View File

@ -222,7 +222,7 @@ Val* Compiler::compile_div(const goos::Object& form, const goos::Object& rest, E
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.instr_idx = fe->code().size();
result_rax_constraint.ireg = result->ireg();
result_rax_constraint.desired_register = emitter::RAX;
fe->constrain(result_rax_constraint);
@ -251,4 +251,145 @@ Val* Compiler::compile_div(const goos::Object& form, const goos::Object& rest, E
}
assert(false);
return get_none();
}
Val* Compiler::compile_shlv(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}, {}}, {});
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
return compile_variable_shift(first, second, env, IntegerMathKind::SHLV_64);
}
Val* Compiler::compile_sarv(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}, {}}, {});
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
return compile_variable_shift(first, second, env, IntegerMathKind::SARV_64);
}
Val* Compiler::compile_shrv(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}, {}}, {});
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
return compile_variable_shift(first, second, env, IntegerMathKind::SHRV_64);
}
Val* Compiler::compile_variable_shift(const RegVal* in,
const RegVal* sa,
Env* env,
IntegerMathKind kind) {
auto result = env->make_gpr(in->type());
auto sa_in = env->make_gpr(sa->type());
env->emit(std::make_unique<IR_RegSet>(result, in));
env->emit(std::make_unique<IR_RegSet>(sa_in, sa));
auto fenv = get_parent_env_of_type<FunctionEnv>(env);
IRegConstraint sa_con;
sa_con.ireg = sa_in->ireg();
sa_con.instr_idx = fenv->code().size();
sa_con.desired_register = emitter::RCX;
if (get_math_mode(in->type()) != MathMode::MATH_INT ||
get_math_mode(sa->type()) != MathMode::MATH_INT) {
throw std::runtime_error("Can't shift a " + in->type().print() + " by a " + sa->type().print());
}
fenv->constrain(sa_con);
env->emit(std::make_unique<IR_IntegerMath>(kind, result, sa_in));
return result;
}
Val* Compiler::compile_mod(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}, {}}, {});
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
auto fenv = get_parent_env_of_type<FunctionEnv>(env);
if (get_math_mode(first->type()) != MathMode::MATH_INT ||
get_math_mode(second->type()) != MathMode::MATH_INT) {
throw std::runtime_error("Can't mod a " + first->type().print() + " by a " +
second->type().print());
}
auto result = env->make_gpr(first->type());
env->emit(std::make_unique<IR_RegSet>(result, first));
IRegConstraint con;
con.ireg = result->ireg();
con.instr_idx = fenv->code().size();
con.desired_register = emitter::RAX;
fenv->constrain(con);
env->emit(std::make_unique<IR_IntegerMath>(IntegerMathKind::IMOD_32, result, second));
return result;
}
Val* Compiler::compile_logand(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}, {}}, {});
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
if (get_math_mode(first->type()) != MathMode::MATH_INT ||
get_math_mode(second->type()) != MathMode::MATH_INT) {
throw std::runtime_error("Can't logand a " + first->type().print() + " by a " +
second->type().print());
}
auto result = env->make_gpr(first->type());
env->emit(std::make_unique<IR_RegSet>(result, first));
env->emit(std::make_unique<IR_IntegerMath>(IntegerMathKind::AND_64, result, second));
return result;
}
Val* Compiler::compile_logior(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}, {}}, {});
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
if (get_math_mode(first->type()) != MathMode::MATH_INT ||
get_math_mode(second->type()) != MathMode::MATH_INT) {
throw std::runtime_error("Can't logior a " + first->type().print() + " by a " +
second->type().print());
}
auto result = env->make_gpr(first->type());
env->emit(std::make_unique<IR_RegSet>(result, first));
env->emit(std::make_unique<IR_IntegerMath>(IntegerMathKind::OR_64, result, second));
return result;
}
Val* Compiler::compile_logxor(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}, {}}, {});
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
if (get_math_mode(first->type()) != MathMode::MATH_INT ||
get_math_mode(second->type()) != MathMode::MATH_INT) {
throw std::runtime_error("Can't logxor a " + first->type().print() + " by a " +
second->type().print());
}
auto result = env->make_gpr(first->type());
env->emit(std::make_unique<IR_RegSet>(result, first));
env->emit(std::make_unique<IR_IntegerMath>(IntegerMathKind::XOR_64, result, second));
return result;
}
Val* Compiler::compile_lognot(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}}, {});
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
if (get_math_mode(first->type()) != MathMode::MATH_INT) {
throw std::runtime_error("Can't lognot a " + first->type().print());
}
auto result = env->make_gpr(first->type());
env->emit(std::make_unique<IR_RegSet>(result, first));
env->emit(std::make_unique<IR_IntegerMath>(IntegerMathKind::NOT_64, result, nullptr));
return result;
}

View File

@ -1477,27 +1477,30 @@ class IGen {
/*!
* Shift 64-bit gpr left by CL register
*/
static Instruction shl_gpr64_cl(uint8_t reg) {
static Instruction shl_gpr64_cl(Register reg) {
assert(reg.is_gpr());
Instruction instr(0xd3);
instr.set_modrm_and_rex(4, reg, 3, true);
instr.set_modrm_and_rex(4, reg.hw_id(), 3, true);
return instr;
}
/*!
* Shift 64-bit gpr right (logical) by CL register
*/
static Instruction shr_gpr64_cl(uint8_t reg) {
static Instruction shr_gpr64_cl(Register reg) {
assert(reg.is_gpr());
Instruction instr(0xd3);
instr.set_modrm_and_rex(5, reg, 3, true);
instr.set_modrm_and_rex(5, reg.hw_id(), 3, true);
return instr;
}
/*!
* Shift 64-bit gpr right (arithmetic) by CL register
*/
static Instruction sar_gpr64_cl(uint8_t reg) {
static Instruction sar_gpr64_cl(Register reg) {
assert(reg.is_gpr());
Instruction instr(0xd3);
instr.set_modrm_and_rex(7, reg, 3, true);
instr.set_modrm_and_rex(7, reg.hw_id(), 3, true);
return instr;
}

View File

@ -113,19 +113,16 @@ struct CompilerTestRunner {
} // namespace
TEST(CompilerAndRuntime, BuildGame) {
TEST(CompilerAndRuntime, BuildGameAndTest) {
std::thread runtime_thread([]() { exec_runtime(0, nullptr); });
Compiler compiler;
fprintf(stderr, "about to run test\n");
try {
compiler.run_test("goal_src/test/test-build-game.gc");
} catch (std::exception& e) {
fprintf(stderr, "caught exception %s\n", e.what());
EXPECT_TRUE(false);
}
fprintf(stderr, "DONE!\n");
// todo, tests after loading the game.
@ -230,6 +227,14 @@ TEST(CompilerAndRuntime, CompilerTests) {
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"});
runner.run_test("test-shiftvs.gc", {"11\n"});
runner.run_test("test-ash.gc", {"18\n"});
runner.run_test("test-negative-integer-symbol.gc", {"-123\n"});
runner.run_test("test-mod.gc", {"7\n"});
runner.run_test("test-nested-function-call-2.gc", {"10\n"});
runner.run_test("test-logand.gc", {"4\n"});
runner.run_test("test-logior.gc", {"60\n"});
runner.run_test("test-logxor.gc", {"56\n"});
expected = "test-string";
runner.run_test("test-string-symbol.gc", {expected}, expected.size());
@ -248,6 +253,21 @@ TEST(CompilerAndRuntime, CompilerTests) {
runner.run_test("test-factorial-loop.gc", {"3628800\n"});
runner.run_test("test-protect.gc", {"33\n"});
expected =
"test newline\nnewline\ntest tilde ~ \ntest A print boxed-string: \"boxed string!\"\ntest A "
"print symbol: a-symbol\ntest A make boxed object longer: \"srt\"!\ntest A "
"non-default pad: zzzzzzpad-me\ntest A shorten(4): a23~\ntest A don'tchange(4): a234\ntest A "
"shorten with pad(4): sho~\ntest A a few things \"one thing\" a-second integer #<compiled "
"function @ #x161544>\n";
expected += "test S a string a-symbol another string!\n";
expected += "test C ) ]\n";
expected += "test P (no type) #<compiled function @ #x161544>\n";
expected += "test P (with type) 1447236\n";
// todo, finish format testing.
runner.run_test("test-format.gc", {expected}, expected.size());
compiler.shutdown_target();
runtime_thread.join();
runner.print_summary();