/*! * @file GoalControlFlow.cpp * Branching control flow implementation. * Contains "cond", the only control flow structure known to the compiler, and branch condition * optimizations. */ #include "Goal.h" #include "util.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. */ GoalCondition Goal::compile_condition(Object condition, std::shared_ptr env, bool invert) { GoalCondition gc; // These are special conditions that can be optimized into a cmp + jxx instruction. const std::unordered_map conditions_inverted = { {"!=", ConditionKind::EQUAL_64}, {"eq?", ConditionKind::NOT_EQUAL_64}, {"neq?", ConditionKind::EQUAL_64}, {"=", ConditionKind::NOT_EQUAL_64}, {">", ConditionKind::LEQ_64}, {"<", ConditionKind::GEQ_64}, {">=", ConditionKind::LT_64}, {"<=", ConditionKind::GT_64}}; const std::unordered_map conditions_normal = { {"!=", ConditionKind::NOT_EQUAL_64}, {"eq?", ConditionKind::EQUAL_64}, {"neq?", ConditionKind::NOT_EQUAL_64}, {"=", ConditionKind::EQUAL_64}, {">", ConditionKind::GT_64}, {"<", ConditionKind::LT_64}, {">=", ConditionKind::GEQ_64}, {"<=", ConditionKind::LEQ_64}}; // possibly a form with an optimizable condition? if (condition.type == PAIR) { auto first = pair_car(condition); auto rest = pair_cdr(condition); if (first.type == 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).type != 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 = goos.get_uneval_args_no_rest(rest, rest, 2); if (!args.named_args.empty() || args.unnamed_args.size() != 2) { throw_compile_error(rest, "invalid arguments to " + nc_kv->first); } auto first_arg = compile_error_guard(args.unnamed_args.at(0), env); auto second_arg = compile_error_guard(args.unnamed_args.at(1), env); if (is_number(first_arg->type)) { // it's a numeric comparison, so we may need to coerce. // there is no support for comparing bintegers, so we turn the binteger comparison into an // integer. if (is_binteger(first_arg->type)) { first_arg = to_integer(first_arg, env); } // convert second one to appropriate type as needed if (is_number(second_arg->type)) { second_arg = to_same_numeric_type(second_arg, first_arg->type, env); } } // use signed comparison only if first argument is a signed integer (or coerced binteger) // (floating point ignores this) gc.is_signed = is_signed_integer(first_arg->type); // pick between a floating point and an integer comparison. if (is_float(first_arg->type)) { gc.a = resolve_to_xmm(first_arg, env); gc.b = resolve_to_xmm(second_arg, env); gc.is_float = true; } else { gc.a = resolve_to_gpr(first_arg, env); gc.b = resolve_to_gpr(second_arg, env); } return gc; } } } // not something we can optimize. Just check if we get false. // todo - it's possible to optimize a false comparison because the false offset is zero gc.kind = invert ? EQUAL_64 : NOT_EQUAL_64; gc.a = resolve_to_gpr(compile_error_guard(condition, env), env); gc.b = compile_get_sym_obj("#f", env); return gc; } /*! * In the event that we have an expression like (< 1 2) that's _not_ a branch condition, * we can reuse the logic of the above comparison, and just set up an (if cond #t #f)-like program. */ std::shared_ptr Goal::compile_condition_as_bool(const Object& form, Object rest, std::shared_ptr env) { (void)rest; auto c = compile_condition(form, env, true); auto result = compile_get_sym_obj("#f", env); // todo - can be optimized. auto branch_ir = make_unique(); auto branch_ir_ref = branch_ir.get(); branch_ir->cond = c; branch_ir->label = std::make_shared