text: #include "goalc/compiler/Compiler.h"
  #include "common/util/BitUtils.h"
  #include <cfloat>
  
  MathMode Compiler::get_math_mode(const TypeSpec& ts) {
    if (m_ts.tc(m_ts.make_typespec("binteger"), ts)) {
      return MATH_BINT;
    }
  
    if (m_ts.tc(m_ts.make_typespec("integer"), ts)) {
      return MATH_INT;
    }
  
    if (m_ts.tc(m_ts.make_typespec("float"), ts)) {
      return MATH_FLOAT;
    }
  
    return MATH_INVALID;
  }
  
  bool Compiler::is_number(const TypeSpec& ts) {
    return m_ts.tc(m_ts.make_typespec("number"), ts);
  }
  
  bool Compiler::is_float(const TypeSpec& ts) {
    return m_ts.tc(m_ts.make_typespec("float"), ts);
  }
  
  bool Compiler::is_integer(const TypeSpec& ts) {
    return m_ts.tc(m_ts.make_typespec("integer"), ts) && !m_ts.tc(m_ts.make_typespec("binteger"), ts);
  }
  
  bool Compiler::is_binteger(const TypeSpec& ts) {
    return m_ts.tc(m_ts.make_typespec("binteger"), ts);
  }
  
  bool Compiler::is_singed_integer_or_binteger(const TypeSpec& ts) {
    return m_ts.tc(m_ts.make_typespec("integer"), ts) && !m_ts.tc(m_ts.make_typespec("uinteger"), ts);
  }
  
  Val* Compiler::number_to_integer(const goos::Object& form, Val* in, Env* env) {
    (void)env;
    auto ts = in->type();
    if (is_binteger(ts)) {
      throw_compiler_error(form, "Cannot convert {} (a binteger) to an integer yet.", in->print());
    } else if (is_float(ts)) {
      auto fe = env->function_env();
      auto result = fe->make_gpr(m_ts.make_typespec("int"));
      env->emit_ir<IR_FloatToInt>(form, result, in->to_fpr(form, env));
      return result;
    } else if (is_integer(ts)) {
      return in;
    }
    throw_compiler_error(form, "Cannot convert a {} to an integer.", in->type().print());
    return nullptr;
  }
  
  Val* Compiler::number_to_binteger(const goos::Object& form, Val* in, Env* env) {
    (void)env;
    auto ts = in->type();
    if (is_binteger(ts)) {
      return in;
    } else if (is_float(ts)) {
      throw_compiler_error(form, "Cannot convert {} (a float) to an integer yet.", in->print());
    } else if (is_integer(ts)) {
      auto fe = env->function_env();
      RegVal* input = in->to_reg(form, env);
      auto sa = fe->make_gpr(m_ts.make_typespec("int"));
      env->emit_ir<IR_LoadConstant64>(form, sa, 3);
      auto result = compile_variable_shift(form, input, sa, env, IntegerMathKind::SHLV_64);
      result->set_type(m_ts.make_typespec("binteger"));
      return result;
    }
    throw_compiler_error(form, "Cannot convert a {} to a binteger.", in->type().print());
    return nullptr;
  }
  
  Val* Compiler::number_to_float(const goos::Object& form, Val* in, Env* env) {
    (void)env;
    auto ts = in->type();
    if (is_binteger(ts)) {
      throw_compiler_error(form, "Cannot convert {} (a binteger) to an float yet.", in->print());
    } else if (is_float(ts)) {
      return in;
    } else if (is_integer(ts)) {
      auto fe = env->function_env();
      auto result = fe->make_fpr(m_ts.make_typespec("float"));
      env->emit_ir<IR_IntToFloat>(form, result, in->to_gpr(form, env));
      return result;
    }
    throw_compiler_error(form, "Cannot convert a {} to a float.", in->type().print());
    return nullptr;
  }
  
  Val* Compiler::to_math_type(const goos::Object& form, Val* in, MathMode mode, Env* env) {
    switch (mode) {
      case MATH_BINT:
        return number_to_binteger(form, in, env);
      case MATH_INT:
        return number_to_integer(form, in, env);
      case MATH_FLOAT:
        return number_to_float(form, in, env);
      default:
        throw_compiler_error(form, "Cannot do math on a {}.", in->type().print());
    }
    return nullptr;
  }
  
  Val* Compiler::compile_add(const goos::Object& form, const goos::Object& rest, Env* env) {
    auto args = get_va(form, rest);
    if (!args.named.empty() || args.unnamed.empty()) {
      throw_compiler_error(form, "Invalid + form");
    }
  
    // look at the first value to determine the math mode
    auto first_val = compile_error_guard(args.unnamed.at(0), env);
    auto first_type = first_val->type();
    auto math_type = get_math_mode(first_type);
    switch (math_type) {
      case MATH_INT:
      case MATH_BINT: {
        auto result = env->make_gpr(first_type);
        env->emit_ir<IR_RegSet>(form, result, first_val->to_gpr(form, env));
  
        for (size_t i = 1; i < args.unnamed.size(); i++) {
          env->emit_ir<IR_IntegerMath>(
              form, IntegerMathKind::ADD_64, result,
              to_math_type(form, compile_error_guard(args.unnamed.at(i), env), math_type, env)
                  ->to_gpr(form, env));
        }
        return result;
      }
  
      case MATH_FLOAT: {
        auto result = env->make_fpr(first_type);
        env->emit_ir<IR_RegSet>(form, result, first_val->to_fpr(form, env));
  
        for (size_t i = 1; i < args.unnamed.size(); i++) {
          env->emit_ir<IR_FloatMath>(
              form, FloatMathKind::ADD_SS, result,
              to_math_type(form, compile_error_guard(args.unnamed.at(i), env), math_type, env)
                  ->to_fpr(form, env));
        }
        return result;
      }
      case MATH_INVALID:
        throw_compiler_error(form, "Cannot do math on a {}.", first_type.print());
        break;
      default:
        ASSERT(false);
    }
    ASSERT(false);
    return get_none();
  }
  
  Val* Compiler::compile_mul(const goos::Object& form, const goos::Object& rest, Env* env) {
    auto args = get_va(form, rest);
    if (!args.named.empty() || args.unnamed.empty()) {
      throw_compiler_error(form, "Invalid * form");
    }
  
    // look at the first value to determine the math mode
    auto first_val = compile_error_guard(args.unnamed.at(0), env);
    auto first_type = first_val->type();
    auto math_type = get_math_mode(first_type);
    switch (math_type) {
      case MATH_INT: {
        auto result = env->make_gpr(first_type);
        env->emit_ir<IR_RegSet>(form, result, first_val->to_gpr(form, env));
  
        for (size_t i = 1; i < args.unnamed.size(); i++) {
          auto val = compile_error_guard(args.unnamed.at(i), env);
          auto val_as_int = dynamic_cast<IntegerConstantVal*>(val);
          int power_of_two = -1;
          if (val_as_int && val_as_int->value().uses_gpr() && val_as_int->value().value_64() > 0) {
            auto p = get_power_of_two(val_as_int->value().value_64());
            if (p) {
              power_of_two = *p;
            }
          }
  
          if (power_of_two >= 0) {
            env->emit_ir<IR_IntegerMath>(form, IntegerMathKind::SHL_64, result, power_of_two);
          } else {
            env->emit_ir<IR_IntegerMath>(form, IntegerMathKind::IMUL_32, result,
                                         to_math_type(form, val, math_type, env)->to_gpr(form, env));
          }
        }
        return result;
      }
      case MATH_FLOAT: {
        auto result = env->make_fpr(first_type);
        env->emit_ir<IR_RegSet>(form, result, first_val->to_fpr(form, env));
  
        for (size_t i = 1; i < args.unnamed.size(); i++) {
          env->emit_ir<IR_FloatMath>(
              form, FloatMathKind::MUL_SS, result,
              to_math_type(form, compile_error_guard(args.unnamed.at(i), env), math_type, env)
                  ->to_fpr(form, env));
        }
        return result;
      }
      case MATH_INVALID:
        throw_compiler_error(form, "Cannot do math on a {}.", first_type.print());
        break;
      default:
        ASSERT(false);
    }
    ASSERT(false);
    return get_none();
  }
  
  Val* Compiler::compile_fmin(const goos::Object& form, const goos::Object& rest, Env* env) {
    auto args = get_va(form, rest);
    if (!args.named.empty() || args.unnamed.empty()) {
      throw_compiler_error(form, "Invalid fmin form");
    }
  
    // look at the first value to determine the math mode
    auto first_val = compile_error_guard(args.unnamed.at(0), env);
    if (get_math_mode(first_val->type()) != MATH_FLOAT) {
      throw_compiler_error(form, "Must use floats in fmin");
    }
    auto result = env->make_fpr(first_val->type());
    env->emit_ir<IR_RegSet>(form, result, first_val->to_fpr(form, env));
    for (size_t i = 1; i < args.unnamed.size(); i++) {
      auto val = compile_error_guard(args.unnamed.at(i), env);
      if (get_math_mode(val->type()) != MATH_FLOAT) {
        throw_compiler_error(form, "Must use floats in fmin");
      }
      env->emit_ir<IR_FloatMath>(form, FloatMathKind::MIN_SS, result, val->to_fpr(form, env));
    }
    return result;
  }
  
  Val* Compiler::compile_fmax(const goos::Object& form, const goos::Object& rest, Env* env) {
    auto args = get_va(form, rest);
    if (!args.named.empty() || args.unnamed.empty()) {
      throw_compiler_error(form, "Invalid fmax form");
    }
  
    // look at the first value to determine the math mode
    auto first_val = compile_error_guard(args.unnamed.at(0), env);
    if (get_math_mode(first_val->type()) != MATH_FLOAT) {
      throw_compiler_error(form, "Must use floats in fmax");
    }
    auto result = env->make_fpr(first_val->type());
    env->emit_ir<IR_RegSet>(form, result, first_val->to_fpr(form, env));
    for (size_t i = 1; i < args.unnamed.size(); i++) {
      auto val = compile_error_guard(args.unnamed.at(i), env);
      if (get_math_mode(val->type()) != MATH_FLOAT) {
        throw_compiler_error(form, "Must use floats in fmax");
      }
      env->emit_ir<IR_FloatMath>(form, FloatMathKind::MAX_SS, result, val->to_fpr(form, env));
    }
    return result;
  }
  
  Val* Compiler::compile_sqrtf(const goos::Object& form, const goos::Object& rest, Env* env) {
    auto args = get_va(form, rest);
    va_check(form, args, {{}}, {});
  
    auto first_val = compile_error_guard(args.unnamed.at(0), env);
    if (get_math_mode(first_val->type()) != MATH_FLOAT) {
      throw_compiler_error(form, "Must use a float for sqrtf");
    }
    auto result = env->make_fpr(first_val->type());
    env->emit_ir<IR_FloatMath>(form, FloatMathKind::SQRT_SS, result, first_val->to_fpr(form, env));
    return result;
  }
  
  Val* Compiler::compile_imul64(const goos::Object& form, const goos::Object& rest, Env* env) {
    auto args = get_va(form, rest);
    if (!args.named.empty() || args.unnamed.empty()) {
      throw_compiler_error(form, "Invalid imul64 form");
    }
  
    // look at the first value to determine the math mode
    auto first_val = compile_error_guard(args.unnamed.at(0), env);
    auto first_type = first_val->type();
    auto math_type = get_math_mode(first_type);
    switch (math_type) {
      case MATH_INT: {
        auto result = env->make_gpr(first_type);
        env->emit_ir<IR_RegSet>(form, result, first_val->to_gpr(form, env));
  
        for (size_t i = 1; i < args.unnamed.size(); i++) {
          env->emit_ir<IR_IntegerMath>(
              form, IntegerMathKind::IMUL_64, result,
              to_math_type(form, compile_error_guard(args.unnamed.at(i), env), math_type, env)
                  ->to_gpr(form, env));
        }
        return result;
      }
      case MATH_FLOAT:
      case MATH_INVALID:
      case MATH_BINT:
        throw_compiler_error(form, "Cannot do imul64 on a {}.", first_type.print());
        break;
      default:
        ASSERT(false);
    }
    ASSERT(false);
    return get_none();
  }
  
  Val* Compiler::compile_sub(const goos::Object& form, const goos::Object& rest, Env* env) {
    auto args = get_va(form, rest);
    if (!args.named.empty() || args.unnamed.empty()) {
      throw_compiler_error(form, "Invalid - form");
    }
  
    auto first_val = compile_error_guard(args.unnamed.at(0), env);
    auto first_type = first_val->type();
    auto math_type = get_math_mode(first_type);
    switch (math_type) {
      case MATH_INT:
        if (args.unnamed.size() == 1) {
          auto result = compile_integer(0, env)->to_gpr(form, env);
          env->emit_ir<IR_IntegerMath>(
              form, IntegerMathKind::SUB_64, result,
              to_math_type(form, compile_error_guard(args.unnamed.at(0), env), math_type, env)
                  ->to_gpr(form, env));
          return result;
        } else {
          auto result = env->make_gpr(first_type);
          env->emit_ir<IR_RegSet>(
              form, result,
              to_math_type(form, compile_error_guard(args.unnamed.at(0), env), math_type, env)
                  ->to_gpr(form, env));
  
          for (size_t i = 1; i < args.unnamed.size(); i++) {
            env->emit_ir<IR_IntegerMath>(
                form, IntegerMathKind::SUB_64, result,
                to_math_type(form, compile_error_guard(args.unnamed.at(i), env), math_type, env)
                    ->to_gpr(form, env));
          }
          return result;
        }
  
      case MATH_FLOAT:
        if (args.unnamed.size() == 1) {
          auto result = compile_float(0, env, env->function_env()->segment)->to_fpr(form, env);
          env->emit_ir<IR_FloatMath>(
              form, FloatMathKind::SUB_SS, result,
              to_math_type(form, compile_error_guard(args.unnamed.at(0), env), math_type, env)
                  ->to_fpr(form, env));
          return result;
        } else {
          auto result = env->make_fpr(first_type);
          env->emit_ir<IR_RegSet>(
              form, result,
              to_math_type(form, compile_error_guard(args.unnamed.at(0), env), math_type, env)
                  ->to_fpr(form, env));
  
          for (size_t i = 1; i < args.unnamed.size(); i++) {
            env->emit_ir<IR_FloatMath>(
                form, FloatMathKind::SUB_SS, result,
                to_math_type(form, compile_error_guard(args.unnamed.at(i), env), math_type, env)
                    ->to_fpr(form, env));
          }
          return result;
        }
  
      case MATH_INVALID:
        throw_compiler_error(form, "Cannot do math on a {}.", first_type.print());
        break;
      default:
        ASSERT(false);
    }
    ASSERT(false);
    return get_none();
  }
  
  Val* Compiler::compile_floating_point_division(const goos::Object& form,
                                                 const TypeSpec& result_type,
                                                 RegVal* a,
                                                 RegVal* b,
                                                 Env* env) {
    constexpr bool use_accurate = true;
    auto result = env->make_fpr(result_type);
  
    if (use_accurate) {
      auto fenv = env->function_env();
      auto end_label = fenv->alloc_unnamed_label();
      end_label->func = fenv;
      end_label->idx = -10;  // placeholder
  
      auto zero = compile_float(0.0, env, fenv->segment_for_static_data())->to_fpr(form, env);
      Condition zero_check;
      zero_check.kind = ConditionKind::EQUAL;
      zero_check.a = zero;
      zero_check.b = b;
      zero_check.is_float = true;
  
      // check for divide by zero
      auto branch_ir = std::make_unique<IR_ConditionalBranch>(zero_check, Label());
      auto branch_ir_ref = branch_ir.get();
      env->emit(form, std::move(branch_ir));
  
      // code for not dividing by zero
      env->emit_ir<IR_RegSet>(form, result, a);
      env->emit_ir<IR_FloatMath>(form, FloatMathKind::DIV_SS, result, b);
  
      env->emit_ir<IR_GotoLabel>(form, end_label);
  
      branch_ir_ref->mark_as_resolved();
      branch_ir_ref->label.idx = fenv->code().size();
  
      auto flt_max = compile_integer(0x7f7fffff, env)->to_gpr(form, env);
      auto mask = compile_integer(0xf0000000, env)->to_gpr(form, env);
      auto temp_int = env->make_gpr(result_type);
      env->emit_ir<IR_RegSet>(form, temp_int, a);
      env->emit_ir<IR_IntegerMath>(form, IntegerMathKind::AND_64, temp_int, mask);
      env->emit_ir<IR_IntegerMath>(form, IntegerMathKind::XOR_64, flt_max, temp_int);
      env->emit_ir<IR_RegSet>(form, temp_int, b);
      env->emit_ir<IR_IntegerMath>(form, IntegerMathKind::AND_64, temp_int, mask);
      env->emit_ir<IR_IntegerMath>(form, IntegerMathKind::XOR_64, flt_max, temp_int);
      env->emit_ir<IR_RegSet>(form, result, flt_max);
  
      end_label->idx = fenv->code().size();
  
    } else {
      env->emit_ir<IR_RegSet>(form, result, a);
      env->emit_ir<IR_FloatMath>(form, FloatMathKind::DIV_SS, result, b);
    }
    return result;
  }
  
  Val* Compiler::compile_div(const goos::Object& form, const goos::Object& rest, Env* env) {
    auto args = get_va(form, rest);
    if (!args.named.empty() || args.unnamed.size() != 2) {
      throw_compiler_error(form, "Invalid / form");
    }
  
    auto first_val = compile_error_guard(args.unnamed.at(0), env);
    auto first_type = first_val->type();
    auto math_type = get_math_mode(first_type);
    switch (math_type) {
      case MATH_INT: {
        auto fe = env->function_env();
        auto first_thing = first_val->to_gpr(form, env);
        auto result = env->make_gpr(first_type);
        env->emit_ir<IR_RegSet>(form, result, first_thing);
  
        auto val = compile_error_guard(args.unnamed.at(1), env);
        auto val_as_int = dynamic_cast<IntegerConstantVal*>(val);
        int power_of_two = -1;
        if (val_as_int && val_as_int->value().uses_gpr() && val_as_int->value().value_64() > 0) {
          auto p = get_power_of_two(val_as_int->value().value_64());
          if (p) {
            power_of_two = *p;
          }
        }
  
        if (power_of_two >= 0) {
          if (is_singed_integer_or_binteger(first_type)) {
            env->emit_ir<IR_IntegerMath>(form, IntegerMathKind::SAR_64, result, power_of_two);
          } else {
            env->emit_ir<IR_IntegerMath>(form, IntegerMathKind::SHR_64, result, power_of_two);
          }
        } else {
          IRegConstraint result_rax_constraint;
          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);
  
          if (is_singed_integer_or_binteger(first_type)) {
            env->emit_ir<IR_IntegerMath>(form, IntegerMathKind::IDIV_32, result,
                                         to_math_type(form, val, math_type, env)->to_gpr(form, env));
          } else {
            env->emit_ir<IR_IntegerMath>(form, IntegerMathKind::UDIV_32, result,
                                         to_math_type(form, val, math_type, env)->to_gpr(form, env));
          }
  
          auto result_moved = env->make_gpr(first_type);
          env->emit_ir<IR_RegSet>(form, result_moved, result);
          return result_moved;
        }
  
        return result;
      }
  
      case MATH_FLOAT: {
        auto a = first_val->to_fpr(form, env);
        auto b = to_math_type(form, compile_error_guard(args.unnamed.at(1), env), math_type, env)
                     ->to_fpr(form, env);
        return compile_floating_point_division(form, first_type, a, b, env);
      }
  
      case MATH_INVALID:
        throw_compiler_error(form, "Cannot do math on a {}.", first_type.print());
        break;
      default:
        ASSERT(false);
    }
    ASSERT(false);
    return get_none();
  }
  
  Val* Compiler::compile_variable_shift(const goos::Object& form,
                                        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_ir<IR_RegSet>(form, result, in);
    env->emit_ir<IR_RegSet>(form, sa_in, sa);
    auto fenv = env->function_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_compiler_error(form, "Cannot shift a {} by a {}", in->type().print(), sa->type().print());
    }
  
    fenv->constrain(sa_con);
    env->emit_ir<IR_IntegerMath>(form, kind, result, sa_in);
    return result;
  }
  
  Val* Compiler::compile_shl(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(form, env);
  
    auto sa = get_constant_integer_or_variable(args.unnamed.at(1), env);
    if (sa.is_constant()) {
      if (sa.constant < 0 || sa.constant > 64) {
        throw_compiler_error(form, "Cannot shift by more than 64, or by a negative amount.");
      }
      return compile_fixed_shift(form, first, sa.constant, env, IntegerMathKind::SHL_64);
    } else {
      auto second = sa.val->to_gpr(form, env);
      return compile_variable_shift(form, first, second, env, IntegerMathKind::SHLV_64);
    }
  }
  
  Val* Compiler::compile_shr(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(form, env);
  
    auto sa = get_constant_integer_or_variable(args.unnamed.at(1), env);
    if (sa.is_constant()) {
      if (sa.constant < 0 || sa.constant > 64) {
        throw_compiler_error(form, "Cannot shift by more than 64, or by a negative amount.");
      }
      return compile_fixed_shift(form, first, sa.constant, env, IntegerMathKind::SHR_64);
    } else {
      auto second = sa.val->to_gpr(form, env);
      return compile_variable_shift(form, first, second, env, IntegerMathKind::SHRV_64);
    }
  }
  
  Val* Compiler::compile_sar(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(form, env);
  
    auto sa = get_constant_integer_or_variable(args.unnamed.at(1), env);
    if (sa.is_constant()) {
      if (sa.constant < 0 || sa.constant > 64) {
        throw_compiler_error(form, "Cannot shift by more than 64, or by a negative amount.");
      }
      return compile_fixed_shift(form, first, sa.constant, env, IntegerMathKind::SAR_64);
    } else {
      auto second = sa.val->to_gpr(form, env);
      return compile_variable_shift(form, first, second, env, IntegerMathKind::SARV_64);
    }
  }
  
  Val* Compiler::compile_fixed_shift(const goos::Object& form,
                                     const RegVal* in,
                                     u8 sa,
                                     Env* env,
                                     IntegerMathKind kind) {
    // type check
    if (get_math_mode(in->type()) != MathMode::MATH_INT) {
      throw_compiler_error(form, "Cannot shift a {}.", in->type().print());
    }
  
    if (sa > 64) {
      throw_compiler_error(form, "Cannot shift by more than 64.");
    }
  
    // copy to result register
    auto result = env->make_gpr(in->type());
    env->emit_ir<IR_RegSet>(form, result, in);
    // do the shift
    env->emit_ir<IR_IntegerMath>(form, kind, result, sa);
    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(form, env);
    auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(form, env);
    auto fenv = env->function_env();
  
    if (get_math_mode(first->type()) != MathMode::MATH_INT ||
        get_math_mode(second->type()) != MathMode::MATH_INT) {
      throw_compiler_error(form, "Cannot mod a {} by a {}.", first->type().print(),
                           second->type().print());
    }
  
    auto result = env->make_gpr(first->type());
    env->emit_ir<IR_RegSet>(form, result, first);
  
    IRegConstraint con;
    con.ireg = result->ireg();
    con.instr_idx = fenv->code().size();
    con.desired_register = emitter::RAX;
  
    fenv->constrain(con);
    env->emit_ir<IR_IntegerMath>(form, IntegerMathKind::IMOD_32, result, second);
  
    auto result_moved = env->make_gpr(first->type());
    env->emit_ir<IR_RegSet>(form, result_moved, result);
    return result_moved;
  }
  
  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(form, env);
    auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(form, env);
    auto math_1 = get_math_mode(first->type());
    auto math_2 = get_math_mode(second->type());
    if (!((math_1 == MathMode::MATH_INT && math_2 == MathMode::MATH_INT) ||
          (math_1 == MathMode::MATH_INT && m_ts.tc(TypeSpec("pointer"), second->type())) ||
          (m_ts.tc(TypeSpec("pointer"), first->type()) && math_2 == MathMode::MATH_INT))) {
      throw_compiler_error(form, "Cannot logand a {} by a {}.", first->type().print(),
                           second->type().print());
    }
  
    // kind of a hack, but make (logand int pointer) return pointer.
    auto result =
        env->make_gpr(m_ts.tc(TypeSpec("pointer"), second->type()) ? second->type() : first->type());
    env->emit_ir<IR_RegSet>(form, result, first);
    env->emit_ir<IR_IntegerMath>(form, 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);
    if (!args.named.empty() || args.unnamed.empty()) {
      throw_compiler_error(form, "Invalid logior form");
    }
  
    auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(form, env);
  
    auto result = env->make_gpr(first->type());
    env->emit_ir<IR_RegSet>(form, result, first);
  
    for (size_t i = 1; i < args.unnamed.size(); i++) {
      auto sec = compile_error_guard(args.unnamed.at(i), env);
      if (!is_integer(sec->type())) {
        throw_compiler_error(form, "Cannot logior a {} by a {}.", first->type().print(),
                             sec->type().print());
      }
      env->emit_ir<IR_IntegerMath>(form, IntegerMathKind::OR_64, result, sec->to_gpr(form, env));
    }
    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(form, env);
    auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(form, env);
    if (get_math_mode(first->type()) != MathMode::MATH_INT ||
        get_math_mode(second->type()) != MathMode::MATH_INT) {
      throw_compiler_error(form, "Cannot logxor a {} by a {}.", first->type().print(),
                           second->type().print());
    }
  
    auto result = env->make_gpr(first->type());
    env->emit_ir<IR_RegSet>(form, result, first);
    env->emit_ir<IR_IntegerMath>(form, 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(form, env);
    if (get_math_mode(first->type()) != MathMode::MATH_INT) {
      throw_compiler_error(form, "Cannot lognot a {}.", first->type().print());
    }
  
    auto result = env->make_gpr(first->type());
    env->emit_ir<IR_RegSet>(form, result, first);
    env->emit_ir<IR_IntegerMath>(form, IntegerMathKind::NOT_64, result, nullptr);
    return result;
  }
  