void try_simplify()

in service/constant-propagation/ConstantPropagationTransform.cpp [152:469]


void try_simplify(const ConstantEnvironment& env,
                  const cfg::InstructionIterator& cfg_it,
                  const Transform::Config& config,
                  cfg::CFGMutation& mutation) {
  auto* insn = cfg_it->insn;

  auto reg_is_exact = [&env](reg_t reg, int64_t val) {
    auto value = env.get(reg).maybe_get<SignedConstantDomain>();
    if (!value || !value->get_constant() || *value->get_constant() != val) {
      return false;
    }
    return true;
  };

  auto reg_fits_lit8 = [&env](reg_t reg) -> std::optional<int8_t> {
    auto value = env.get(reg).maybe_get<SignedConstantDomain>();
    if (!value || !value->get_constant()) {
      return std::nullopt;
    }
    int64_t val = *value->get_constant();
    if (val < -128 || val > 127) {
      return std::nullopt;
    }
    return (int8_t)val;
  };

  auto maybe_reduce_lit8 = [&](size_t idx) -> bool {
    if (!config.to_int_lit8) {
      return false;
    }

    auto val = reg_fits_lit8(insn->src(idx));
    if (!val) {
      return false;
    }

    auto new_op = [&]() -> IROpcode {
      switch (insn->opcode()) {
      case OPCODE_ADD_INT:
        return OPCODE_ADD_INT_LIT8;
      // TODO: SUB to RSUB
      case OPCODE_MUL_INT:
        return OPCODE_MUL_INT_LIT8;
      case OPCODE_AND_INT:
        return OPCODE_AND_INT_LIT8;
      case OPCODE_OR_INT:
        return OPCODE_OR_INT_LIT8;
      case OPCODE_XOR_INT:
        return OPCODE_XOR_INT_LIT8;
      default:
        always_assert(false);
      }
      not_reached();
    }();

    auto repl = new IRInstruction(new_op);
    repl->set_src(0, insn->src(idx == 0 ? 1 : 0));
    repl->set_dest(insn->dest());
    repl->set_literal(*val);
    mutation.replace(cfg_it, {repl});
    return true;
  };

  auto maybe_reduce_lit8_both = [&]() {
    if (maybe_reduce_lit8(0)) {
      return true;
    }
    if (maybe_reduce_lit8(1)) {
      return true;
    }
    return false;
  };

  auto reg_fits_lit16 = [&env](reg_t reg) -> std::optional<int16_t> {
    auto value = env.get(reg).maybe_get<SignedConstantDomain>();
    if (!value || !value->get_constant()) {
      return std::nullopt;
    }
    int64_t val = *value->get_constant();
    if (val < -32768 || val > 32767) {
      return std::nullopt;
    }
    return (int16_t)val;
  };

  auto maybe_reduce_lit16 = [&](size_t idx) -> bool {
    if (!config.to_int_lit16) {
      return false;
    }

    auto val = reg_fits_lit16(insn->src(idx));
    if (!val) {
      return false;
    }

    auto new_op = [&]() -> IROpcode {
      switch (insn->opcode()) {
      case OPCODE_ADD_INT:
        return OPCODE_ADD_INT_LIT16;
      // TODO: SUB to RSUB
      case OPCODE_MUL_INT:
        return OPCODE_MUL_INT_LIT16;
      case OPCODE_AND_INT:
        return OPCODE_AND_INT_LIT16;
      case OPCODE_OR_INT:
        return OPCODE_OR_INT_LIT16;
      case OPCODE_XOR_INT:
        return OPCODE_XOR_INT_LIT16;
      default:
        always_assert(false);
      }
      not_reached();
    }();

    auto repl = new IRInstruction(new_op);
    repl->set_src(0, insn->src(idx == 0 ? 1 : 0));
    repl->set_dest(insn->dest());
    repl->set_literal(*val);
    mutation.replace(cfg_it, {repl});
    return true;
  };

  auto maybe_reduce_lit16_both = [&]() {
    if (maybe_reduce_lit16(0)) {
      return true;
    }
    if (maybe_reduce_lit16(1)) {
      return true;
    }
    return false;
  };

  auto replace_with_move = [&](reg_t src_reg) {
    auto* move = new IRInstruction(OPCODE_MOVE);
    move->set_src(0, src_reg);
    move->set_dest(insn->dest());
    mutation.replace(cfg_it, {move});
  };

  auto replace_with_const = [&](int64_t val) {
    auto* c = new IRInstruction(OPCODE_CONST);
    c->set_dest(insn->dest());
    c->set_literal(val);
    mutation.replace(cfg_it, {c});
  };

  auto replace_with_neg = [&](reg_t src_reg) {
    auto* neg = new IRInstruction(OPCODE_NEG_INT);
    neg->set_src(0, src_reg);
    neg->set_dest(insn->dest());
    mutation.replace(cfg_it, {neg});
  };

  switch (insn->opcode()) {
    // These should have been handled by PeepHole, really.

  case OPCODE_ADD_INT_LIT16:
  case OPCODE_ADD_INT_LIT8: {
    if (insn->get_literal() == 0) {
      replace_with_move(insn->src(0));
    }
    break;
  }

  case OPCODE_RSUB_INT:
  case OPCODE_RSUB_INT_LIT8: {
    if (insn->get_literal() == 0) {
      replace_with_neg(insn->src(0));
    }
    break;
  }

  case OPCODE_MUL_INT_LIT16:
  case OPCODE_MUL_INT_LIT8: {
    if (insn->get_literal() == 1) {
      replace_with_move(insn->src(0));
      break;
    }
    if (insn->get_literal() == 0) {
      replace_with_const(0);
      break;
    }
    if (insn->get_literal() == -1) {
      replace_with_neg(insn->src(0));
      break;
    }
    break;
  }
  case OPCODE_AND_INT_LIT16:
  case OPCODE_AND_INT_LIT8: {
    if (insn->get_literal() == 0) {
      replace_with_const(0);
      break;
    }
    if (insn->get_literal() == -1) {
      replace_with_move(insn->src(0));
      break;
    }
    break;
  }
  case OPCODE_OR_INT_LIT16:
  case OPCODE_OR_INT_LIT8: {
    if (insn->get_literal() == 0) {
      replace_with_move(insn->src(0));
      break;
    }
    if (insn->get_literal() == -1) {
      replace_with_const(-1);
      break;
    }
    break;
  }
  case OPCODE_XOR_INT_LIT16:
  case OPCODE_XOR_INT_LIT8: {
    // TODO
    break;
  }

  case OPCODE_SHL_INT_LIT8:
  case OPCODE_USHR_INT_LIT8:
  case OPCODE_SHR_INT_LIT8: {
    // Can at most simplify the operand, but doesn't make much sense.
    break;
  }

  case OPCODE_ADD_INT: {
    if (reg_is_exact(insn->src(0), 0)) {
      replace_with_move(insn->src(1));
    } else if (reg_is_exact(insn->src(1), 0)) {
      replace_with_move(insn->src(0));
    } else if (maybe_reduce_lit8_both()) {
      break;
    } else if (maybe_reduce_lit16_both()) {
      break;
    }
    break;
  }

  case OPCODE_SUB_INT: {
    if (reg_is_exact(insn->src(0), 0)) {
      replace_with_neg(insn->src(1));
    } else if (reg_is_exact(insn->src(1), 0)) {
      replace_with_move(insn->src(0));
    }
    break;
  }

  case OPCODE_MUL_INT: {
    if (reg_is_exact(insn->src(0), 1)) {
      replace_with_move(insn->src(1));
    } else if (reg_is_exact(insn->src(1), 1)) {
      replace_with_move(insn->src(0));
    } else if (reg_is_exact(insn->src(0), 0) || reg_is_exact(insn->src(1), 0)) {
      replace_with_const(0);
    } else if (reg_is_exact(insn->src(0), -1)) {
      replace_with_neg(insn->src(1));
    } else if (reg_is_exact(insn->src(1), -1)) {
      replace_with_neg(insn->src(0));
    } else if (maybe_reduce_lit8_both()) {
      break;
    } else if (maybe_reduce_lit16_both()) {
      break;
    }
    break;
  }

  case OPCODE_AND_INT: {
    if (reg_is_exact(insn->src(0), -1)) {
      replace_with_move(insn->src(1));
    } else if (reg_is_exact(insn->src(1), -1)) {
      replace_with_move(insn->src(0));
    } else if (reg_is_exact(insn->src(0), 0) || reg_is_exact(insn->src(1), 0)) {
      replace_with_const(0);
    } else if (maybe_reduce_lit8_both()) {
      break;
    } else if (maybe_reduce_lit16_both()) {
      break;
    }
    break;
  }

  case OPCODE_OR_INT: {
    if (reg_is_exact(insn->src(0), 0)) {
      replace_with_move(insn->src(1));
    } else if (reg_is_exact(insn->src(1), 0)) {
      replace_with_move(insn->src(0));
    } else if (reg_is_exact(insn->src(0), -1) ||
               reg_is_exact(insn->src(1), -1)) {
      replace_with_const(-1);
    } else if (maybe_reduce_lit8_both()) {
      break;
    } else if (maybe_reduce_lit16_both()) {
      break;
    }
    break;
  }

  case OPCODE_XOR_INT:
    if (maybe_reduce_lit8_both()) {
      break;
    } else if (maybe_reduce_lit16_both()) {
      break;
    }
    break;

  case OPCODE_ADD_LONG:
  case OPCODE_SUB_LONG:
  case OPCODE_MUL_LONG:
  case OPCODE_AND_LONG:
  case OPCODE_OR_LONG:
  case OPCODE_XOR_LONG:
    // TODO: More complicated version of the above.
    break;

  default:
    return;
  }
}