/* CRC optimization. Copyright (C) 2022-2024 Free Software Foundation, Inc. Contributed by Mariam Arutunian This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ /* This pass performs CRC optimization. */ #include "config.h" #include "system.h" #include "coretypes.h" #include "backend.h" #include "tree.h" #include "gimple.h" #include "tree-pass.h" #include "ssa.h" #include "gimple-iterator.h" #include "tree-cfg.h" #include "cfgloop.h" #include "tree-scalar-evolution.h" #include "crc-verification.h" class crc_optimization { private: /* Record of statements already seen. */ bitmap m_visited_stmts; /* Input CRC of the loop. */ tree m_crc_arg; /* Input data of the loop. */ tree m_data_arg; /* The statement doing shift 1 operation before/after xor operation. */ gimple *m_shift_stmt; /* Phi statement from the head of the loop for CRC. */ gphi *m_phi_for_crc; /* Phi statement for the data from the head of the loop if exists, otherwise - nullptr. */ gphi *m_phi_for_data; /* The loop, which probably calculates CRC. */ class loop *m_crc_loop; /* Polynomial used in CRC calculation. */ unsigned HOST_WIDE_INT m_polynomial; /* Depending on the value of M_IS_BIT_FORWARD, may be forward or reversed CRC. If M_IS_BIT_FORWARD, then it is bit-forward implementation, otherwise bit-reversed. */ bool m_is_bit_forward; /* Sets initial values for CRC analyses. */ void set_initial_values (); /* This is the main function that checks whether the given LOOP calculates CRC and extracts details of the CRC calculation. The main idea is to find the innermost loop with 8, 16, 24, 32, 64 iterations and xor instruction (xor is the key operation for naive CRC calculation). Then, checks that the variable is shifted by one before/after being used in xor. Xor must be done under the condition of MSB/LSB being 1. */ bool loop_may_calculate_crc (class loop *loop); /* Symbolically executes the loop and checks that LFSR and resulting states match. Returns true if it is verified that the loop calculates CRC. Otherwise, return false. OUTPUT_CRC is the phi statement which will hold the calculated CRC value after the loop exit. */ bool loop_calculates_crc (gphi *output_crc, std::pair calc_polynom); /* Returns true if there is only two conditional blocks in the loop (one may be for the CRC bit check and the other for the loop counter). This may filter out some real CRCs, where more than one condition is checked for the CRC calculation. */ static bool loop_contains_two_conditional_bb (basic_block *loop_bbs, unsigned loop_num_nodes); /* Checks the FUNC_LOOP loop's iteration number. The loop for CRC calculation may do 8, 16, 24, 32, 64 iterations. */ bool satisfies_crc_loop_iteration_count (class loop *func_loop); /* This function checks if the XOR_STMT is used for CRC calculation. It verifies the presence of a shift operation in the CRC_FUN function inside the CRC loop. It examines operands of XOR, its dependencies, the relative position of the shift operation, and the existence of a shift operation in the opposite branch of conditional statements. It also checks if XOR is performed when MSB/LSB is one. If these conditions are met, the XOR operation may be part of a CRC calculation. The function returns true if these conditions are fulfilled, otherwise, it returns false. */ bool xor_calculates_crc (function *crc_fun, const gimple *xor_stmt); /* Returns true if we can get definition of the VARIABLE, and the definition it's not outside the loop. Otherwise, returns false. */ bool passes_checks_for_def_chain (tree variable); /* This function goes up through the def-use chains of the parameter NAME. Gathers all the statements within the loop, from which the variable depends on and adds to the USE_DEFS. Returns false, if there is a statement that may not exist in the CRC loop. Otherwise, returns true. */ bool set_defs (tree name, auto_vec& use_defs, bool keep_only_header_phis); /* Set M_PHI_FOR_CRC and M_PHI_FOR_DATA fields. Returns false if there are more than two (as in CRC calculation only CRC's and data's phi may exist) or no phi statements in STMTS (at least there must be CRC's phi). Otherwise, returns true. */ bool set_crc_and_data_phi (auto_vec& stmts); /* Returns true if the variable checked in the condition depends on possible CRC value. Otherwise, returns false. */ bool cond_depends_on_crc (auto_vec& stmts); /* Checks that the condition is for checking CRC. Returns true if xor is done under the condition of MSB/LSB being 1, and the condition's variable and xor-ed variable depend on the same variable. Otherwise, returns false. XOR_BB is the basic block, where the xor operation is done. PRED_BB is the predecessor basic block of the XOR_BB, it is assumed that the last stmt of PRED_BB checks the condition under which xor is done. */ bool crc_cond (basic_block pred_bb, basic_block xor_bb); /* Returns true if xor is done in case the MSB/LSB is 1. Otherwise, returns false. In CRC calculation algorithms CRC is xor-ed with the polynomial only if MSB/LSB is 1. PRED_BB is the block containing the condition for the xor. XOR_BB is the one of the successor blocks of PRED_BB, it is assumed that CRC is xor-ed with the polynomial in XOR_BB. COND is the condition, which is checked to satisfy the CRC condition. */ bool is_crc_satisfiable_cond (basic_block pred_bb, basic_block xor_bb, gcond *cond); /* Checks that the variable used in the condition COND is the assumed CRC (or depends on the assumed CRC). Also sets data member m_phi_for_data if it isn't set and exists. */ bool is_crc_checked (gcond *cond); /* Returns true if condition COND checks MSB/LSB bit is 1. Otherwise, return false. */ static bool cond_true_is_checked_for_bit_one (const gcond *cond); /* Returns opposite block of the XOR_BB from PRED_BB's dest blocks. */ static basic_block get_xor_bb_opposite (basic_block pred_bb, basic_block xor_bb); /* Checks whether the pair of xor's shift exists in the opposite basic block (OPPOSITE_BB). If there is a shift and xor in the same block, then in the opposite block must be another shift. */ bool exists_shift_for_opp_xor_shift (basic_block opposite_bb); /* Follow def-use chains of XORED_CRC and return the statement where XORED_CRC is shifted by one bit position. Only PHI statements are allowed between XORED_CRC and the shift in the def-use chain. If no such statement is found, return NULL. */ gimple *find_shift_after_xor (tree xored_crc); /* Returns the statement which does shift 1 operation. If there is no such statement, returns nullptr. STMTS - are the statements within the loop before xor operation on which possible CRC variable depends. */ gimple *find_shift_before_xor (const auto_vec &stmts); /* Returns true if ASSIGN_STMT does shift with 1. Otherwise, returns false. */ bool can_be_crc_shift (gimple *assign_stmt); /* Returns true if the operation done in ASSIGN_STMT can occur during CRC calculation. Otherwise, returns false. */ bool can_not_be_crc_stmt (gimple *assign_stmt); /* Returns true if the statement with STMT_CODE may occur in CRC calculation. Otherwise, returns false. */ static bool is_acceptable_stmt_code (const tree_code &stmt_code); /* Prints extracted details of CRC calculation. */ void dump_crc_information (); /* Returns true if OUTPUT_CRC's result is the input of m_phi_for_crc. Otherwise, returns false. */ bool is_output_crc (gphi *output_crc); /* Swaps m_phi_for_crc and m_phi_for_data if they are mixed. */ void swap_crc_and_data_if_needed (gphi *output_crc); /* Validates CRC and data arguments and sets them for potential CRC loop replacement. The function extracts the CRC and data arguments from PHI nodes and performs several checks to ensure that the CRC and data are suitable for replacing the CRC loop with a more efficient implementation. Returns true if the arguments are valid and the loop replacement is possible, false otherwise. */ bool validate_crc_and_data (); /* Convert polynomial to unsigned HOST_WIDE_INT. */ void construct_constant_polynomial (value *polynomial); /* Returns phi statement which may hold the calculated CRC. */ gphi *get_output_phi (); /* Attempts to optimize a CRC calculation loop by replacing it with a call to an internal function (IFN_CRC or IFN_CRC_REV). Returns true if replacement is succeeded, otherwise false. */ bool optimize_crc_loop (gphi *output_crc); public: crc_optimization () : m_visited_stmts (BITMAP_ALLOC (NULL)), m_crc_loop (nullptr), m_polynomial (0) { set_initial_values (); } ~crc_optimization () { BITMAP_FREE (m_visited_stmts); } unsigned int execute (function *fun); }; /* Prints extracted details of CRC calculation. */ void crc_optimization::dump_crc_information () { if (dump_file) { fprintf (dump_file, "Loop iteration number is " HOST_WIDE_INT_PRINT_UNSIGNED ".\n", tree_to_uhwi (m_crc_loop->nb_iterations)); if (m_is_bit_forward) fprintf (dump_file, "Bit forward.\n"); else fprintf (dump_file, "Bit reversed.\n"); } } /* Goes down by def-use chain (within the CRC loop) and returns the statement where variable (dependent on xor-ed variable) is shifted with 1. Between xor and shift operations only phi statements are allowed. Otherwise, returns nullptr. */ gimple * crc_optimization::find_shift_after_xor (tree xored_crc) { imm_use_iterator imm_iter; use_operand_p use_p; gcc_assert (TREE_CODE (xored_crc) == SSA_NAME); unsigned v = SSA_NAME_VERSION (xored_crc); if (bitmap_bit_p (m_visited_stmts, v)) return nullptr; bitmap_set_bit (m_visited_stmts, v); /* Iterate through the immediate uses of the XORED_CRC. If there is a shift return true, if before shift there is other instruction (besides phi) return false. */ FOR_EACH_IMM_USE_FAST (use_p, imm_iter, xored_crc) { gimple *stmt = USE_STMT (use_p); // Consider only statements within the loop if (!flow_bb_inside_loop_p (m_crc_loop, gimple_bb (stmt))) continue; /* If encountered phi statement, check immediate use of its result. Otherwise, if encountered assign statement, check whether it does shift (some other operations are allowed to be between shift and xor). */ if (gimple_code (stmt) == GIMPLE_PHI) { /* Don't continue searching if encountered the loop's beginning. */ if (bb_loop_header_p (gimple_bb (stmt))) continue; return find_shift_after_xor (gimple_phi_result (stmt)); } else if (is_gimple_assign (stmt)) { /* Check that stmt does shift by 1. There are no other statements between xor and shift, in CRC cases we detect. */ if (can_be_crc_shift (stmt)) return stmt; return nullptr; } else if (!is_gimple_debug (stmt)) return nullptr; } return nullptr; } /* Returns opposite block of the XOR_BB from PRED_BB's dest blocks. */ basic_block crc_optimization::get_xor_bb_opposite (basic_block pred_bb, basic_block xor_bb) { /* Check that the predecessor block has exactly two successors. */ if (EDGE_COUNT (pred_bb->succs) != 2) return nullptr; edge e0 = EDGE_SUCC (pred_bb, 0); edge e1 = EDGE_SUCC (pred_bb, 1); /* Ensure neither outgoing edge is marked as complex. */ if ((e0->flags & EDGE_COMPLEX) || (e1->flags & EDGE_COMPLEX)) return nullptr; /* Check that one of the successors is indeed XOR_BB. */ gcc_assert ((e0->dest == xor_bb) || (e1->dest == xor_bb)); /* Return the opposite block of XOR_BB. */ if (EDGE_SUCC (pred_bb, 0)->dest != xor_bb) return e0->dest; return e1->dest; } /* Checks whether the pair of xor's shift exists in the opposite basic block (OPPOSITE_BB). If there is a shift and xor in the same block, then in the opposite block must be another shift. */ bool crc_optimization::exists_shift_for_opp_xor_shift (basic_block opposite_bb) { if (!opposite_bb) return false; /* Walk through the instructions of given basic block. */ for (gimple_stmt_iterator bsi = gsi_start_nondebug_bb (opposite_bb); !gsi_end_p (bsi); gsi_next_nondebug (&bsi)) { gimple *stmt = gsi_stmt (bsi); /* Find assignment statement with shift operation. Check that shift's direction is same with the shift done on the path with xor, and it is a shift by one. */ if (is_gimple_assign (stmt)) { if ((gimple_assign_rhs_code (stmt) == gimple_assign_rhs_code (m_shift_stmt)) && integer_onep (gimple_assign_rhs2 (stmt))) return true; } } /* If there is no shift, return false. */ return false; } /* Returns true if condition COND checks MSB/LSB bit is 1. Otherwise, return false. */ bool crc_optimization::cond_true_is_checked_for_bit_one (const gcond *cond) { if (!cond) return false; tree rhs = gimple_cond_rhs (cond); enum tree_code code = gimple_cond_code (cond); /* If the condition is something == 1 -> return true. */ if (code == EQ_EXPR && integer_onep (rhs)) return true; /* If the condition is something != 0 or something < 0 -> return true. */ if ((code == NE_EXPR || code == LT_EXPR) && integer_zerop (rhs)) return true; return false; } /* Returns true if xor is done in case the MSB/LSB is 1. Otherwise, returns false. In CRC calculation algorithms CRC is xor-ed with the polynomial only if MSB/LSB is 1. PRED_BB is the block containing the condition for the xor. XOR_BB is the one of the successor blocks of PRED_BB, it is assumed that CRC is xor-ed with the polynomial in XOR_BB. COND is the condition, which is checked to satisfy the CRC condition. */ bool crc_optimization::is_crc_satisfiable_cond (basic_block pred_bb, basic_block xor_bb, gcond *cond) { edge true_edge; edge false_edge; extract_true_false_edges_from_block (pred_bb, &true_edge, &false_edge); bool cond_is_checked_for_bit_one = cond_true_is_checked_for_bit_one (cond); /* Check that xor is done in case the MSB/LSB is 1. */ if (cond_is_checked_for_bit_one && true_edge->dest == xor_bb) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Xor is done on true branch.\n"); } else if (!cond_is_checked_for_bit_one && false_edge->dest == xor_bb) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Xor is done on false branch.\n"); } else { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Xor is done if MSB/LSB is not one, not CRC.\n"); return false; } return true; } /* Checks that the variable used in the condition COND is the assumed CRC (or depends on the assumed CRC). Also sets data member m_phi_for_data if it isn't set and exists. */ bool crc_optimization::is_crc_checked (gcond *cond) { tree lhs = gimple_cond_lhs (cond); /* As conditions are in canonical form, only left part must be an SSA_NAME. */ if (TREE_CODE (lhs) == SSA_NAME) { /* Return whether there is a dependence between if condition's variable and xor-ed variable. Also set phi statement of data if it is not determined earlier and is used in the loop. */ auto_vec cond_dep_stmts (m_crc_loop->num_nodes); bool set_defs_succeeded = set_defs (lhs, cond_dep_stmts, true); bitmap_clear (m_visited_stmts); if (!set_defs_succeeded) return false; return cond_depends_on_crc (cond_dep_stmts); } /* Return false if there is no dependence between if condition's variable and xor-ed variable. */ return false; } /* Checks that the condition is for checking CRC. Returns true if xor is done under the condition of MSB/LSB being 1, and the condition's variable and xor-ed variable depend on the same variable. Otherwise, returns false. XOR_BB is the basic block, where the xor operation is done. PRED_BB is the predecessor basic block of the XOR_BB, it is assumed that the last stmt of PRED_BB checks the condition under which xor is done. */ bool crc_optimization::crc_cond (basic_block pred_bb, basic_block xor_bb) { /* Check whether PRED_BB contains condition. We will consider only those cases when xor is done immediately under the condition. */ gcond *cond = safe_dyn_cast (gsi_stmt (gsi_last_bb (pred_bb))); if (!cond) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "No condition.\n"); return false; } /* Check that xor is done in case the MSB/LSB is 1. */ if (!is_crc_satisfiable_cond (pred_bb, xor_bb, cond)) return false; /* Check that CRC's MSB/LSB is checked in the condition. Set data member if not set and exists. */ if (!is_crc_checked (cond)) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "The condition is not related to the CRC check.\n"); return false; } return true; } /* Returns true if the statement with STMT_CODE may occur in CRC calculation. Otherwise, returns false. */ bool crc_optimization::is_acceptable_stmt_code (const tree_code &stmt_code) { return (stmt_code == BIT_IOR_EXPR) || (stmt_code == BIT_AND_EXPR) || (stmt_code == BIT_XOR_EXPR) || (stmt_code == MINUS_EXPR) || (stmt_code == PLUS_EXPR) || (stmt_code == RSHIFT_EXPR) || (stmt_code == LSHIFT_EXPR) || (TREE_CODE_CLASS (stmt_code) == tcc_unary); } /* Returns true if ASSIGN_STMT does shift with 1. Otherwise, returns false. */ bool crc_optimization::can_be_crc_shift (gimple *assign_stmt) { tree_code stmt_code = gimple_assign_rhs_code (assign_stmt); if (stmt_code == LSHIFT_EXPR || stmt_code == RSHIFT_EXPR) { m_is_bit_forward = (stmt_code == LSHIFT_EXPR); /* Check that shift one is done, keep shift statement. */ if (integer_onep (gimple_assign_rhs2 (assign_stmt))) { if (m_shift_stmt) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Already there is one shift.\n"); return false; } if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Found <<1 or >>1.\n"); return true; } /* This filters out cases, when xor-ed variable is shifted by values other than 1. */ } return false; } /* Returns true if the operation done in ASSIGN_STMT can occur during CRC calculation. Otherwise, returns false. */ bool crc_optimization::can_not_be_crc_stmt (gimple *assign_stmt) { tree_code stmt_code = gimple_assign_rhs_code (assign_stmt); if (!is_acceptable_stmt_code (stmt_code)) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "\nStmt with the following operation " "code %s between xor and shift, " "may not be CRC.\n", get_tree_code_name (stmt_code)); return true; } return false; } /* Returns true if we can get definition of the VARIABLE, and the definition is not outside the loop. Otherwise, returns false. */ bool crc_optimization::passes_checks_for_def_chain (tree variable) { if (!(variable && TREE_CODE (variable) == SSA_NAME)) return false; /* No definition chain for default defs. */ if (SSA_NAME_IS_DEFAULT_DEF (variable)) return false; gimple *stmt = SSA_NAME_DEF_STMT (variable); if (!stmt) return false; /* Don't go outside the loop. */ if (!flow_bb_inside_loop_p (m_crc_loop, gimple_bb (stmt))) return false; return true; } /* This function goes up through the def-use chains of the parameter NAME. Gathers all the statements within the loop, from which the variable depends on and adds to the USE_DEFS. Returns false, if there is a statement that may not exist in the CRC loop. Otherwise, returns true. */ bool crc_optimization::set_defs (tree name, auto_vec &use_defs, bool keep_only_header_phis = false) { if (!passes_checks_for_def_chain (name)) return true; /* Don't consider already visited names. */ unsigned v = SSA_NAME_VERSION (name); if (bitmap_bit_p (m_visited_stmts, v)) return true; bitmap_set_bit (m_visited_stmts, v); /* In CRC implementations with constant polynomial maximum 12 use_def statements may occur. This limit is based on an analysis of various CRC implementations as well as theoretical possibilities. TODO: Find a better solution. */ if (use_defs.length () > 12) return false; gimple *stmt = SSA_NAME_DEF_STMT (name); /* If it's not specified to keep only header phi's, then keep all statements. */ if (!keep_only_header_phis) use_defs.safe_push (stmt); /* If it is an assignment statement, get and check def-use chain for the first and second operands. */ if (is_a (stmt)) { if (can_not_be_crc_stmt (stmt)) return false; tree ssa1 = gimple_assign_rhs1 (stmt); tree ssa2 = gimple_assign_rhs2 (stmt); if (!set_defs (ssa1, use_defs, keep_only_header_phis)) return false; if (!set_defs (ssa2, use_defs, keep_only_header_phis)) return false; return true; } /* If it's a phi statement, not declared in loop's header, get and check def-use chain for its values. For the one declared in loop's header just return true and keep it, if keep_only_header_phis is true. */ else if (is_a (stmt)) { if (bb_loop_header_p (gimple_bb (stmt))) { /* If it's specified to keep only header phi's, keep it. */ if (keep_only_header_phis) use_defs.safe_push (stmt); } else { for (unsigned i = 0; i < gimple_phi_num_args (stmt); i++) { tree val = gimple_phi_arg_def (stmt, i); if (!set_defs (val, use_defs, keep_only_header_phis)) return false; } } return true; } /* Return false for other than assigment and phi statement. */ return false; } /* Returns the statement which does shift 1 operation. If there is no such statement, returns nullptr. STMTS - are the statements within the loop before xor operation on which possible CRC variable depends. */ gimple * crc_optimization::find_shift_before_xor (const auto_vec &stmts) { for (auto stmt_it = stmts.begin (); stmt_it != stmts.end (); stmt_it++) { /* If it is an assignment statement, check that is does shift 1. */ if (is_a (*stmt_it)) { if (can_be_crc_shift (*stmt_it)) return *stmt_it; } } return nullptr; } /* This function sets M_PHI_FOR_CRC and M_PHI_FOR_DATA fields. At this step phi nodes for CRC and data may be mixed in places. It is fixed later with the "swap_crc_and_data_if_needed" function. The function returns false if there are more than two (as in CRC calculation only CRC's and data's phi may exist) or no phi statements in STMTS (at least there must be CRC's phi). Otherwise, returns true. */ bool crc_optimization::set_crc_and_data_phi (auto_vec &stmts) { for (auto stmt_it = stmts.begin (); stmt_it != stmts.end (); stmt_it++) { if (is_a (*stmt_it) && bb_loop_header_p (gimple_bb (*stmt_it))) { if (!m_phi_for_crc) m_phi_for_crc = as_a (*stmt_it); else if (!m_phi_for_data) m_phi_for_data = as_a (*stmt_it); else { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Xor-ed variable depends on more than 2 " "phis.\n"); return false; } } } return m_phi_for_crc; } /* Returns true if the variable checked in the condition depends on possible CRC value. Otherwise, returns false. */ bool crc_optimization::cond_depends_on_crc (auto_vec& stmts) { bool con_depends_on_crc = false; /* The condition may depend maximum on data and CRC phi's. */ if (stmts.length () > 2) return false; for (auto stmt_it = stmts.begin (); stmt_it != stmts.end (); stmt_it++) { if (is_a (*stmt_it) && bb_loop_header_p (gimple_bb (*stmt_it))) { /* Check whether variable checked in the condition depends on M_PHI_FOR_CRC. Here don't stop the check, to set data if needed. */ if (m_phi_for_crc == (*stmt_it)) con_depends_on_crc = true; else if (m_phi_for_data && m_phi_for_data == (*stmt_it)) return true; /* If M_PHI_FOR_DATA isn't determined, the phi statement maybe for the data. Just set it. */ else if (!m_phi_for_data) m_phi_for_data = as_a (*stmt_it); } } return con_depends_on_crc; } /* Sets initial values for the CRC analysis. This function is used multiple times during the analyses. */ void crc_optimization::set_initial_values () { m_crc_arg = nullptr; m_data_arg = nullptr; m_shift_stmt = nullptr; m_phi_for_crc = nullptr; m_phi_for_data = nullptr; m_is_bit_forward = false; } /* This function checks if the XOR_STMT is used for CRC calculation. It verifies the presence of a shift operation in the CRC_FUN function inside the CRC loop. It examines operands of XOR, its dependencies, the relative position of the shift operation, and the existence of a shift operation in the opposite branch of conditional statements. It also checks if XOR is performed when MSB/LSB is one. If these conditions are met, the XOR operation may be part of a CRC calculation. The function returns true if these conditions are fulfilled, otherwise, it returns false. */ bool crc_optimization::xor_calculates_crc (function *crc_fun, const gimple *xor_stmt) { tree crc_var = gimple_assign_lhs (xor_stmt); set_initial_values (); tree ssa1 = gimple_assign_rhs1 (xor_stmt); tree ssa2 = gimple_assign_rhs2 (xor_stmt); if (TREE_CODE (ssa2) != INTEGER_CST) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Second operand of the " "xor statement isn't an integer constant.\n"); return false; } /* Get the statements within the loop on which xor-ed variable depends. */ auto_vec xor_dep_stmts (m_crc_loop->num_nodes); bool set_defs_succeeded = set_defs (ssa1, xor_dep_stmts); bitmap_clear (m_visited_stmts); if (!set_defs_succeeded) { xor_dep_stmts.release (); return false; } m_shift_stmt = find_shift_before_xor (xor_dep_stmts); if (!set_crc_and_data_phi (xor_dep_stmts)) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Xor isn't used for CRC calculation.\n"); return false; } /* Check the case when shift is done after xor. */ if (!m_shift_stmt) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "No shift before xor, trying to find after xor.\n"); m_shift_stmt = find_shift_after_xor (crc_var); bitmap_clear (m_visited_stmts); if (!m_shift_stmt) return false; } /* Get the basic block where xor operation is done. */ basic_block xor_bb = gimple_bb (xor_stmt); /* Get the predecessor basic block of xor's block. */ if (!single_pred_p (xor_bb)) return false; basic_block block_of_condition = single_pred (xor_bb); /* If the found shift operation is in the same block as the xor operation, verify whether another shift exists in the opposite branch of the conditional statements. */ if (m_shift_stmt && gimple_bb (m_shift_stmt) == xor_bb) { basic_block opposite_block = get_xor_bb_opposite (block_of_condition, xor_bb); if (!exists_shift_for_opp_xor_shift (opposite_block)) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Opposite block doesn't contain shift's pair.\n"); return false; } } /* Check that xor is done if MSB/LSB is one. If all checks succeed, then it may be a CRC. */ if (crc_cond (block_of_condition, xor_bb)) { if (dump_file) fprintf (dump_file, "\n%s function maybe contains CRC calculation.\n", function_name (crc_fun)); return true; } return false; } /* Returns true if there is only two conditional blocks in the loop, one may be for the CRC bit check and the other for the loop counter. This may filter out some real CRCs, where more than one condition is checked for the CRC calculation and branch-less CRCs. */ bool crc_optimization::loop_contains_two_conditional_bb (basic_block *loop_bbs, unsigned loop_num_nodes) { unsigned conditional_bb_count = 0; /* Iterate through the loop until the conditional branches count is below 3. */ for (unsigned i = 0; i < loop_num_nodes && conditional_bb_count <= 2; i++) { basic_block bb = loop_bbs[i]; if (!single_succ_p (bb)) conditional_bb_count++; } return conditional_bb_count == 2; } /* Checks the FUNC_LOOP loop's iteration number. The loop for CRC calculation may do 8, 16, 24, 32, 64 iterations. */ bool crc_optimization::satisfies_crc_loop_iteration_count (class loop *func_loop) { /* Calculate the number of times the latch of the loop is executed. The function sets NB_ITERATIONS field of the loop. */ number_of_latch_executions (func_loop); tree n_inters = func_loop->nb_iterations; if (n_inters == NULL_TREE || n_inters == chrec_dont_know) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Loop iteration number is chrec_dont_know.\n"); return false; } else if (tree_fits_uhwi_p (n_inters)) { unsigned HOST_WIDE_INT loop_iteration_number = tree_to_uhwi (n_inters); if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Loop iteration number is " HOST_WIDE_INT_PRINT_UNSIGNED ".\n", loop_iteration_number); if ((loop_iteration_number == 7 || loop_iteration_number == 15 || loop_iteration_number == 23 || loop_iteration_number == 31 || loop_iteration_number == 63)) return true; } if (stderr && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Loop iteration number isn't a constant.\n"); return false; } /* This is the main function that checks whether the given LOOP calculates CRC and extracts details of the CRC calculation. The main idea is to find the innermost loop with 8, 16, 24, 32, 64 iterations and xor instruction (xor is the key operation for naive CRC calculation). Then, checks that the variable is shifted by one before/after being used in xor. Xor must be done under the condition of MSB/LSB being 1. */ bool crc_optimization::loop_may_calculate_crc (class loop *loop) { /* Only examine innermost loops. */ if (!loop || loop->inner) return false; if (!satisfies_crc_loop_iteration_count (loop)) return false; m_crc_loop = loop; basic_block *loop_bbs = get_loop_body_in_dom_order (m_crc_loop); /* Filter out the cases, which don't have exactly two conditions in the loop. One for the CRC bit check, the other for the loop counter. */ if (!loop_contains_two_conditional_bb (loop_bbs, m_crc_loop->num_nodes)) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "The number of conditional " "branches in the loop isn't 2.\n"); return false; } unsigned short checked_xor_count = 0; /* Walk bbs of the loop. */ for (unsigned int i = 0; i < m_crc_loop->num_nodes; i++) { basic_block bb = loop_bbs[i]; /* Walk instructions of the bb. */ for (gimple_stmt_iterator bsi = gsi_start_nondebug_bb (bb); !gsi_end_p (bsi); gsi_next_nondebug (&bsi)) { gimple *stmt = gsi_stmt (bsi); /* If there is an xor instruction, check that it is calculating CRC. */ if (is_gimple_assign (stmt) && gimple_assign_rhs_code (stmt) == BIT_XOR_EXPR) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Found xor, " "checking whether it is for CRC calculation.\n"); if (xor_calculates_crc (cfun, stmt)) { dump_crc_information (); free (loop_bbs); return true; } if (++checked_xor_count == 2) return false; } } } free (loop_bbs); return false; } /* Symbolically executes the loop and checks that LFSR and resulting states match. Returns true if it is verified that the loop calculates CRC. Otherwise, return false. OUTPUT_CRC is the phi statement which will hold the calculated CRC value after the loop exit. */ bool crc_optimization::loop_calculates_crc (gphi *output_crc, std::pair calc_polynom) { /* Create LFSR state using extracted polynomial. */ value * lfsr = state::create_lfsr (calc_polynom.first, calc_polynom.second, m_is_bit_forward); if (!lfsr) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Couldn't create LFSR!\n"); return false; } if (dump_file && (dump_flags & TDF_DETAILS)) { fprintf (dump_file, "\nLFSR value is \n"); state::print_value (lfsr); } /* Execute the loop with symbolic values (symbolic value is assigned to the variable when its value isn't known) to keep states, for further comparison. */ bool is_crc = true; crc_symbolic_execution loop_executor (m_crc_loop, output_crc); while (!loop_executor.is_last_iteration ()) { if (!loop_executor.symb_execute_crc_loop ()) { if (dump_file) fprintf (dump_file, "\nCRC verification didn't succeed " "during symbolic execution!\n"); is_crc = false; break; } /* Check whether LFSR and obtained states are same. */ tree calculated_crc = PHI_ARG_DEF_FROM_EDGE (output_crc, single_exit (m_crc_loop)); if (!all_states_match_lfsr (lfsr, m_is_bit_forward, calculated_crc, loop_executor.get_final_states ())) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Returned state and LFSR differ.\n"); is_crc = false; break; } } delete lfsr; return is_crc; } /* Returns true if OUTPUT_CRC's result is the input of M_PHI_FOR_CRC. Otherwise, returns false. */ bool crc_optimization::is_output_crc (gphi *output_crc) { tree crc_of_exit = PHI_ARG_DEF_FROM_EDGE (output_crc, single_exit (m_crc_loop)); tree crc_of_latch = PHI_ARG_DEF_FROM_EDGE (m_phi_for_crc, loop_latch_edge (m_crc_loop)); if (crc_of_exit == crc_of_latch) { if (dump_file && (dump_flags & TDF_DETAILS)) { fprintf (dump_file, "Output CRC is "); print_gimple_expr (dump_file, (gimple *) output_crc, dump_flags); fprintf (dump_file, "\n"); } return true; } else { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Output CRC and determined input CRC " "differ.\n"); return false; } } /* Swaps M_PHI_FOR_CRC and M_PHI_FOR_DATA if they are mixed. */ void crc_optimization::swap_crc_and_data_if_needed (gphi *output_crc) { tree crc_of_exit = PHI_ARG_DEF_FROM_EDGE (output_crc, single_exit (m_crc_loop)); edge crc_loop_latch = loop_latch_edge (m_crc_loop); if (crc_of_exit != PHI_ARG_DEF_FROM_EDGE (m_phi_for_crc, crc_loop_latch)) { if (m_phi_for_data && crc_of_exit == PHI_ARG_DEF_FROM_EDGE (m_phi_for_data, crc_loop_latch)) { std::swap (m_phi_for_crc, m_phi_for_data); } } } /* Validates CRC and data arguments and sets them for potential CRC loop replacement. The function extracts the CRC and data arguments from PHI nodes and performs several checks to ensure that the CRC and data are suitable for replacing the CRC loop with a more efficient implementation. Returns true if the arguments are valid and the loop replacement is possible, false otherwise. */ bool crc_optimization::validate_crc_and_data () { /* Set m_crc_arg and check if fits in word_mode. */ gcc_assert (m_phi_for_crc); m_crc_arg = PHI_ARG_DEF_FROM_EDGE (m_phi_for_crc, loop_preheader_edge (m_crc_loop)); gcc_assert (m_crc_arg); unsigned HOST_WIDE_INT data_size = tree_to_uhwi (m_crc_loop->nb_iterations) + 1; /* We don't support the case where data is larger than the CRC. */ if (TYPE_PRECISION (TREE_TYPE (m_crc_arg)) < data_size) return false; /* Set m_data_arg if a PHI node for data exists, and check its size against loop iterations. This is the case when data and CRC are XOR-ed in the loop. */ if (m_phi_for_data) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Data and CRC are xor-ed in the for loop. Initializing data " "with its value.\n"); m_data_arg = PHI_ARG_DEF_FROM_EDGE (m_phi_for_data, loop_preheader_edge (m_crc_loop)); gcc_assert (m_data_arg); if (TYPE_PRECISION (TREE_TYPE (m_data_arg)) != data_size) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Loop iteration number and data's size differ.\n"); return false; } return true; } return true; } /* Convert polynomial to unsigned HOST_WIDE_INT. */ void crc_optimization::construct_constant_polynomial (value *polynomial) { m_polynomial = 0; for (unsigned i = 0; i < (*polynomial).length (); i++) { value_bit *const_bit; if (m_is_bit_forward) const_bit = (*polynomial)[(*polynomial).length () - 1 - i]; else const_bit = (*polynomial)[i]; m_polynomial <<= 1; m_polynomial ^= (as_a (const_bit))->get_val () ? 1 : 0; } } /* Returns phi statement which may hold the calculated CRC. */ gphi * crc_optimization::get_output_phi () { edge loop_exit = single_exit (m_crc_loop); if (!loop_exit) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "The loop doesn't have single exit.\n"); return nullptr; } basic_block bb = loop_exit->dest; gphi *output_crc = nullptr; int phi_count = 0; /* We are only interested in cases when there is only one phi at the loop exit, and that phi can potentially represent the CRC. If there are other phis present, it indicates that additional values are being calculated within the loop that are used outside it. */ for (gphi_iterator gsi = gsi_start_phis (bb); !gsi_end_p (gsi); gsi_next (&gsi)) { tree phi_result = gimple_phi_result (gsi.phi ()); /* Don't consider virtual operands. */ if (!virtual_operand_p (phi_result)) { if (phi_count < 1) { output_crc = gsi.phi (); phi_count++; } else { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "There is more than one output phi.\n"); return nullptr; } } } if (output_crc) { if (gimple_phi_num_args (output_crc) == 1) return output_crc; } if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Couldn't determine output CRC.\n"); return nullptr; } /* Attempts to optimize a CRC calculation loop by replacing it with a call to an internal function (IFN_CRC or IFN_CRC_REV). Returns true if replacement is succeeded, otherwise false. */ bool crc_optimization::optimize_crc_loop (gphi *output_crc) { if (!output_crc) { if (dump_file) fprintf (dump_file, "Couldn't determine output CRC.\n"); return false; } if (!m_data_arg) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "Data and CRC are xor-ed before for loop. Initializing data " "with 0.\n"); /* Create a new variable for the data. Determine the data's size with the loop iteration count. */ unsigned HOST_WIDE_INT data_size = tree_to_uhwi (m_crc_loop->nb_iterations) + 1; tree type = build_nonstandard_integer_type (data_size, 1); /* For the CRC calculation, it doesn't matter CRC is calculated for the (CRC^data, 0) or (CRC, data). */ m_data_arg = build_int_cstu (type, 0); } /* Build tree node for the polynomial from its constant value. */ tree polynomial_arg = build_int_cstu (TREE_TYPE (m_crc_arg), m_polynomial); gcc_assert (polynomial_arg); internal_fn ifn; if (m_is_bit_forward) ifn = IFN_CRC; else ifn = IFN_CRC_REV; tree phi_result = gimple_phi_result (output_crc); location_t loc; loc = EXPR_LOCATION (phi_result); /* Add IFN call and write the return value in the phi_result. */ gcall *call = gimple_build_call_internal (ifn, 3, m_crc_arg, m_data_arg, polynomial_arg); gimple_call_set_lhs (call, phi_result); gimple_set_location (call, loc); gimple_stmt_iterator si = gsi_start_bb (output_crc->bb); gsi_insert_before (&si, call, GSI_SAME_STMT); /* Remove phi statement, which was holding CRC result. */ gimple_stmt_iterator tmp_gsi = gsi_for_stmt (output_crc); remove_phi_node (&tmp_gsi, false); /* Alter the exit condition of the loop to always exit. */ gcond* loop_exit_cond = get_loop_exit_condition (m_crc_loop); gimple_cond_make_false (loop_exit_cond); update_stmt (loop_exit_cond); return true; } unsigned int crc_optimization::execute (function *fun) { if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, "\nExamining %s function.\n", function_name (fun)); if (number_of_loops (fun) <= 1) return 0; /* Get loops of the function. */ auto loop_list = loops_list (fun, LI_ONLY_INNERMOST); for (auto loop: loop_list) { /* Perform initial checks to filter out non-CRCs. */ if (loop_may_calculate_crc (loop)) { /* Get the phi which will hold the calculated CRC. */ gphi *output_crc = get_output_phi (); if (!output_crc) break; swap_crc_and_data_if_needed (output_crc); if (!is_output_crc (output_crc)) break; if (!validate_crc_and_data ()) break; edge loop_latch = loop_latch_edge (m_crc_loop); tree calced_crc = PHI_ARG_DEF_FROM_EDGE (m_phi_for_crc, loop_latch); crc_symbolic_execution execute_loop (m_crc_loop, nullptr); /* Execute the loop assigning specific values to CRC and data for extracting the polynomial. */ std::pair calc_polynom = execute_loop.extract_polynomial (m_phi_for_crc, m_phi_for_data, calced_crc, m_is_bit_forward); value *polynom_value = calc_polynom.second; /* Stop analysis if we couldn't get the polynomial's value. */ if (!polynom_value) break; /* Stop analysis in case optimize_size is specified and table-based would be generated. This check is only needed for TARGET_CRC case, as polynomial's value isn't known in the beginning. */ construct_constant_polynomial (polynom_value); if (!loop_calculates_crc (output_crc, calc_polynom)) break; if (dump_file) fprintf (dump_file, "The loop with %d header BB index " "calculates CRC!\n", m_crc_loop->header->index); if (!optimize_crc_loop (output_crc)) { if (dump_file) fprintf (dump_file, "Couldn't generate faster CRC code.\n"); } } } return 0; } namespace { const pass_data pass_data_crc_optimization = { GIMPLE_PASS, /* type */ "crc", /* name */ OPTGROUP_NONE, /* optinfo_flags */ TV_GIMPLE_CRC_OPTIMIZATION, /* tv_id */ (PROP_cfg | PROP_ssa), /* properties_required */ 0, /* properties_provided */ 0, /* properties_destroyed */ 0, /* todo_flags_start */ 0, /* todo_flags_finish */ }; class pass_crc_optimization : public gimple_opt_pass { public: pass_crc_optimization (gcc::context *ctxt) : gimple_opt_pass (pass_data_crc_optimization, ctxt) {} /* opt_pass methods: */ virtual bool gate (function *) { return flag_optimize_crc; } virtual unsigned int execute (function *); }; // class pass_crc_optimization unsigned int pass_crc_optimization::execute (function *fun) { return crc_optimization ().execute (fun); } } // anon namespace gimple_opt_pass * make_pass_crc_optimization (gcc::context *ctxt) { return new pass_crc_optimization (ctxt); }