💥Introduction
In this project, I have extended the functionality of my code to handle multiple sets of cloned functions and provide PRUNE / NOPRUNE recommendations. This stage also involved testing on both x86_64 and aarch64 architectures, using a variety of test cases that include multiple cloned functions. Below is an overview of my process, test results, and conclusions.
✅ Code Modifications for Multiple Cloned Functions
In Stage II, I assumed that there was only one cloned function per program. However, for Stage III, I updated the code to support multiple cloned functions. I made the following changes:
-
Refactored Function Handling: I adjusted the function analysis logic to iterate through each cloned function and process them individually, ensuring that each function gets its own PRUNE or NOPRUNE recommendation.
-
Recommendation Logic: I modified the recommendation algorithm to handle multiple cloned functions. Now, for each function, the code makes an individual recommendation (PRUNE or NOPRUNE) based on its operation type (e.g., simple scalar arithmetic or complex data processing).
The following is the final code for project stage 3:
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "tree.h"
#include "tree-pass.h"
#include "cgraph.h"
#include "function.h"
#include "basic-block.h"
#include "gimple.h"
#include "gimple-iterator.h"
#include "gimple-pretty-print.h"
#include "cfg.h"
#include <string>
#include <vector>
#include <map>
namespace {
const pass_data pass_data_project = {
GIMPLE_PASS, /* type */
"pass_project", /* name */
OPTGROUP_NONE, /* optinfo_flags */
TV_NONE, /* tv_id */
PROP_cfg, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
0, /* todo_flags_finish */
};
// Store cloned function information
std::vector<std::string> functions;
std::map<std::string, std::vector<int>> gimpleCodes;
std::map<std::string, int> bbCnts;
// Modified pass class
class pass_project : public gimple_opt_pass {
public:
pass_project(gcc::context* ctxt) : gimple_opt_pass(pass_data_project, ctxt) {}
unsigned int execute(function* fun) override {
cgraph_node* node = cgraph_node::get(fun->decl);
std::string currFunName = node->name();
basic_block bb;
int bb_cnt = 0;
int funIndex = findClonedFunction(currFunName);
if (dump_file) {
if (funIndex != EOF) {
fprintf(dump_file, "===== This %s is a cloned function =====\n", currFunName.c_str());
} else {
fprintf(dump_file, "===== This is not a cloned function =====\n");
}
}
// Store function names
functions.push_back(currFunName);
// Store Gimple code
FOR_EACH_BB_FN(bb, fun) {
bb_cnt++;
for (gimple_stmt_iterator gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
gimple* stmt = gsi_stmt(gsi);
gimpleCodes[node->name()].push_back(gimple_code(stmt));
}
}
bbCnts[currFunName] = bb_cnt;
// PRUNE/NOPRUNE logic for each function
if (dump_file) {
if (funIndex == EOF) {
fprintf(dump_file, "NOPRUNE: %s\n\n", removeSuffix(currFunName).c_str());
} else {
fprintf(dump_file, "%s: %s\n\n", isIdenticalFunction(currFunName, funIndex) ? "PRUNE" : "NOPRUNE", removeSuffix(currFunName).c_str());
}
}
return 0;
}
private:
int findClonedFunction(const std::string& checkingFunName) {
if (functions.empty()) {
return -1;
}
size_t dotPos = checkingFunName.find('.');
std::string funName = checkingFunName.substr(0, dotPos);
std::string suffix = "";
if (dotPos != std::string::npos) {
suffix = checkingFunName.substr(dotPos + 1);
}
for (size_t i = 0; i < functions.size(); ++i) {
if (funName == functions[i] && suffix != "resolver") {
return i;
}
}
return -1;
}
bool isIdenticalFunction(const std::string& currFunName, int orgFunIndex) {
fprintf(dump_file, "========== Comparing Original Function and the Cloned Function ==========\n");
fprintf(dump_file, "*** Compare Basic Block ***\n");
fprintf(dump_file, "Current Function Basic Block: %d\n"
"Original Function Basic Block: %d\n", bbCnts[currFunName], bbCnts[functions[orgFunIndex]]);
if (bbCnts[currFunName] != bbCnts[functions[orgFunIndex]]) {
fprintf(dump_file, "*** Different amount of Basic Blocks ***\n");
return false;
}
fprintf(dump_file, "*** They have the same amount of Basic Block ***\n");
fprintf(dump_file, "Cloned Function Gimple code(Current) | Original Function Gimple code\n");
for (size_t i = 0; i < gimpleCodes[currFunName].size(); ++i) {
if (gimpleCodes[currFunName][i] == gimpleCodes[functions[orgFunIndex]][i]) {
fprintf(dump_file, "%d | %d\n", gimpleCodes[currFunName][i], gimpleCodes[functions[orgFunIndex]][i]);
} else {
return false;
}
}
return true;
}
std::string removeSuffix(const std::string& fullName) {
size_t dotPos = fullName.find('.');
return dotPos != std::string::npos ? fullName.substr(0, dotPos) : fullName;
}
};
// Custom pass creation function
gimple_opt_pass* make_pass_project(gcc::context* ctxt) {
return new pass_project(ctxt);
}
} // namespace
✅ Test Cases
To test my updated code, I created several test cases containing multiple cloned functions:
-
Test Case : Simple Arithmetic Function (PRUNE): In this test case, we extend the functionality of our GCC pass to handle multiple cloned functions and make PRUNE/NOPRUNE recommendations for each. The goal is to evaluate how well the pass identifies simple functions that can be pruned and more complex functions that should be retained for optimization.
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
CLONE_ATTRIBUTE
int process_int16(int16_t *data, size_t len) {
int sum = 0;
for (size_t i = 0; i < len; ++i) {
sum += data[i] * 3 - data[i] / 2 + data[i] % 7; // Scalar arithmetic
}
return sum;
}
CLONE_ATTRIBUTE
float process_floats(float *data, size_t len) {
float sum = 0.0f;
for (size_t i = 0; i < len; ++i) {
sum += data[i] * 1.5f - data[i] / 3.0f + data[i] * data[i]; // Scalar + array processing
}
return sum;
}
CLONE_ATTRIBUTE
int process_int32(int32_t *data, size_t len) {
int result = 0;
for (size_t i = 0; i < len; ++i) {
result += (data[i] & 0xFF) + (data[i] >> 3) - (data[i] << 1); // Bitwise operations
}
return result;
}
CLONE_ATTRIBUTE
void process_io_operations() {
// Simulate I/O operations
printf("Processing I/O operations...\n");
for (int i = 0; i < 10; i++) {
// Simulating I/O operation by printing values
printf("Value: %d\n", i);
}
}
int main() {
int16_t a[128];
float b[128];
int32_t c[128];
// Initialize arrays
for (int i = 0; i < 128; ++i) {
a[i] = i;
b[i] = (float)i;
c[i] = i;
}
// Test cloned functions
printf("process_int16: %d\n", process_int16(a, 128));
printf("process_floats: %.2f\n", process_floats(b, 128));
printf("process_int32: %d\n", process_int32(c, 128));
// Test I/O operations
process_io_operations();
return 0;
}
✅RESULTS==>
The array-processing function (process_floats
) will be NOPRUNED because it is more complex and could benefit from optimization like vectorization.
The scalar arithmetic function (process_int16
) will be PRUNED because it’s simple and doesn’t involve complex operations or data structures.
This function performs bitwise operations on an array of 32-bit integers. While it is more complex than scalar arithmetic, bitwise operations can still be optimized and may benefit from additional techniques like parallelization. Therefore, this function should not be pruned.
This function performs I/O operations by printing values to the console. I/O operations are typically retained because they interact with external systems and cannot be pruned without affecting the program’s functionality.
✅REFLECTION:Through this project, I gained a deeper understanding of the intricacies involved in optimizing code through function analysis and pass implementation. Additionally, it was a valuable experience in working with low-level compiler operations and considering optimization strategies in the context of real-world applications.
Overall, I feel confident in my ability to implement and extend GCC passes and am excited to explore further opportunities in compiler optimization and function analysis.
Comments
Post a Comment