In this blog, I will focus on the modifications and improvements I made to stage 2 of this project.
To learn about the basics of the program, its purpose, and the initial implementation, visit the following links:
Let's start with the additional features added to this program!
Review - GitHub Repository
In the previous stage, I made a tool that gets a main.c file, and a function.c file which includes one function.
Using the tool we make a new function file called function_altered.c which includes 3 different implementations of our function. The implementations are planned to use different auto-vectorization mechanisms based on the user's machine.
This tool is planned to work only on machines with Aarch64 structure.
The 3 auto-vectorization mechanisms include:
ASIMD
SVE
SVE2
This tool also requires the funcion.h file to run properly in stage 2, in which we used the makeheaders feature to create the file and find the prototype of the function. In stage 2 we assumed that the header file is non-existent and is already included in the main file. This is one of the limitations that I worked on and improved in stage 3.
Header Files Generator
One of the limitations of stage 2 was not being able to accept and process more than 1 function.c file. Also function.h file should've been created beforehand, assuming it is non-existent.
In stage 3, the user does not need to enter the makeheaders command manually. It is already implemented in our tool and it will create the header file using the python os module.
This is the part of the code that handles it:
# Find function name and store as string - remove .c
funcFile = num.split(".")[0]
# Add .h to the function name
funcHeader = funcFile + ".h"
# Create the header file if non-existant
if (os.path.isfile(funcHeader) == False):
makeFuncHeader = "makeheaders " + num
os.system(makeFuncHeader)
One interesting about this approach is that it won't overwrite any predefined header files. if a file, with the same name as our function file with the .h extension, exist in the same directory, the program will just ignore these lines of code.
Note: Both files (.c and .h) should have the same name and just different extensions, otherwise this wouldn't work correctly.
The next important detail about the related improvement is that it will also add the #ifndef FUNCTION_H, #define FUNCTION_H, and #endif lines to the header file.
These are the lines of the code that are responsible for adding these lines:
fheader = open(funcHeader, "r")
proto = fheader.read()
fheader.close()
headerDefine = "#ifndef " + funcFile.upper() + "_H\n" + "#define " +
funcFile.upper() + "_H\n\n" + proto + "\n\n#endif"
fheader = open(funcHeader, "w")
fheader.write(headerDefine)
fheader.close()
For example, a header file create for the file called function.c looks like this:
#ifndef FUNCTION_H
#define FUNCTION_H
/* This file was automatically generated. Do not edit! */
void adjust_channels(unsigned char *image,int x_size,int y_size,float red_factor,float green_factor,float blue_factor);
#endif
Everything included in this file is auto-generated based on the function name and the prototype.
main.c File modification
We can assume that the generated header files are already included in the main.c file, but in stage 3, I do not want to make any assumptions!
If we don't make any assumptions about the main.c file, it is possible that it does not include our generated header files. So we add them!
Modifying the main.c file might not be our best option, therefore this program creates a new file which is called main_test.c.
It is a copy of the original main.c file + the auto-generated header files inclusion.
main_test.c may look like this:
// Auto Generated Header File Includes
#include "function.h"
#include "function1.h"
// -----------------------------------------------------------------
Also here is the code that I wrote to create a new file, write the original main content to it, and add the included lines on top:
-------------------------------------- In a loop to get functions
newMain = open("main_test.c", "w")
mainIncludes = mainIncludes + '#include "' + funcHeader + '"\n'
newMain.write(mainIncludes)
newMain.close()
runScript += function_altered[idx] + " "
idx += 1
-------------------------------------- End of loop
newMain = open("main_test.c", "w")
newMain.write(mainIncludes + seperator + mainContent)
newMain.close()
Multiple Functions
One of our limitations in stage 2 is not being able to input more than one function file, which is a noticeable limitation, since in the real world and more importantly large-scale projects we require more than one function file.
In this stage, we allow the user to input as many as function files as needed.
Also, they do not need to input the header files anymore!
For this purpose, I used a loop to go through the received arguments, which are the function files, and make the required modifications one by one.
The required modifications are explained in Stage 2.1 Initial Implementation.
Error Message Update
If you remember from the previous stage, if the user enters any number of arguments except 2, would receive an error message that says: "Invalid Arguments"
This was not very clear or helpful to the user, as they might not know how to use and run this tool.
In stage 3, users can enter as many arguments as they need, but if they run the program with no argument the updated error message will appear.
This error message contains everything you need to know about this program.
From how to run it, how to use useful features, and how the program works, to the source file link in GitHub and even this blog!
This is the updated error message:
------------------------------Invalid Arguments---------------------------
This Program Requires at least 1 function to operate correctly
Add as many function files as needed after ./ifuncCreator
Example: ./ifuncCreator function.c function1.c function2.c ...
Note: As seen in the example above, function header files should not be included in the run command
Function header files may or may not be existant in the same directory
Existant header files will not be modified, but they should have the same name as the function file with .h extension
Non-existant header files will be automatically generated using makeheaders feature
generated header file will have the same name as the function file with .h extension and it will include the function prototype
It is necessary for main.c and resolver.txt files to be existant in the same directory
main.c file will not be modified, instead main_test.c will be created and used with the gcc compiler
For more information please visit:
https://github.com/Awrmani/Aarch64-iFunc-Vectorization
https://armanvalaee.wixsite.com/arman-valaee-asr/blog
------------------------------Enjoy Using This Tool-----------------------
Built-In Compilation
Normally, users need to compile this program, then use the execution files to run their main.c program regarding their machine, which this program will decide about.
In this stage, I added a feature to also compile their code after creating the required files using this tool. Built-In!
Using this feature user does not need to compile their files using the following command afterward!
gcc -g -O3 main.c function_altered.c -o ifuncMain
Also, since this program can handle an unlimited number of function files, if a huge number of files exist, it will make it even harder.
But the built-in compilation feature will handle that using every input file!
How to use it?
All you need to do is to write "compile" after the actual program run command.
For example:
./ifuncCreator function.c function1.c function2.c ... compile
You might have noticed that you no longer need to write "python" to run this command! Also, the .py extension has been removed from our file name.
Makefile
Finally, a makefile has been added to make the program running process much easier and more understandable.
Unfortunately, I did not have much time to make it perfect, so we can only run the program using one argument file, which is called function.c.
To be more exact, this Makefile has been created to make the process of testing easier and faster.
To use the complete features you can still use the following code:
./ifuncCreator function.c function1.c function2.c ... compile
, or this one to not compile the actual program and main.c
./ifuncCreator function.c function1.c function2.c ...
If the user chooses to use this code, a message that explains the next steps and some extra tips will appear on the screen which may help them during their process! Here is the message:
Use the following command tu compile your program:
gcc -g -o3 main_test.c function_altered.c function1_altered.c ... -o ifuncMain
Note: If you plan on using your original main function instead of the main_test.c,
(which is a copy of the original main.c + header files added on top), use main.c instead of main_test.c in the above commands.
To use the makefile with one function (function.c):
make ifuncTool
For no compilation:
make ifuncTool_noCompile
There are also features available to test the program with SIMD, SVE, and SVE2 using the bree.jpg file as the input, but I will get to that in the next blog which is dedicated to the testing process.
Here is the content of the makefile:
ifuncTool: function.c
./ifuncCreator function.c compile
ifuncTool_noCompile: function.c
./ifuncCreator function.c
testSIMD:
./ifuncMain tests/input/bree.jpg 1.0 1.0 1.0 tests/output/bree1a.jpg
./ifuncMain tests/input/bree.jpg 0.5 0.5 0.5 tests/output/bree2a.jpg
./ifuncMain tests/input/bree.jpg 2.0 2.0 2.0 tests/output/bree3a.jpg
testSVE2:
qemu-aarch64 ./ifuncMain tests/input/bree.jpg 1.0 1.0 1.0 tests/output/bree1b.jpg
qemu-aarch64 ./ifuncMain tests/input/bree.jpg 0.5 0.5 0.5 tests/output/bree2b.jpg
qemu-aarch64 ./ifuncMain tests/input/bree.jpg 2.0 2.0 2.0 tests/output/bree3b.jpg
clean:
rm tests/output/bree??.jpg
To see the testing process and results, please view the next blog, Stage 3.2.
Also here is the complete code of our tool, ifuncCreator, even though you can find it in my GitHub Repository:
#!/usr/bin/python3 import os import sys if len(sys.argv) < 2: print("------------------------------Invalid Arguments------------------------------\n") print("This Program Requires at least 1 function to operate correctly\n") print("Add as many function files as needed after ./ifuncCreator\n") print("Example: ./ifuncCreator function.c function1.c function2.c ...\n") print("Note: As seen in the example above, function header files should not be included in the run command\n\n") print("Function header files may or may not be existant in the same directory\n") print("Existant header files will not be modified, but they should have the same name as the function file with .h extension\n") print("Non-existant header files will be automatically generated using makeheaders feature\n") print("generated header file will have the same name as the function file with .h extension and it will include the function prototype\n") print("It is necessary for main.c and resolver.txt files to be existant in the same directory\n") print("main.c file will not be modified, instead main_test.c will be created and used with the gcc compiler\n") print("For more information please visit:\n") print(" https://github.com/Awrmani/Aarch64-iFunc-Vectorization\n") print(" https://armanvalaee.wixsite.com/arman-valaee-asr/blog\n") print("------------------------------Enjoy Using This Tool------------------------------") sys.exit(1) main = open("main.c", "r") mainContent = main.read() main.close() mainIncludes = "// Auto Generated Header File Includes\n" runScript = "gcc -g -O3 main_test.c " seperator = "\n\n// -----------------------------------------------------------------\n\n" function_altered = [] idx = -1 for num in sys.argv: if (idx == -1): # Ignore first input idx += 1 continue if (num == "compile"): break funcFile = num.split(".")[0] # Find function name and store as string - remove .c funcHeader = funcFile + ".h" # Add .h to the function name if (os.path.isfile(funcHeader) == False): # Create the header file if non-existant makeFuncHeader = "makeheaders " + num os.system(makeFuncHeader) fheader = open(funcHeader, "r") proto = fheader.read() fheader.close() if (proto.find("#ifndef") == -1): headerDefine = "#ifndef " + funcFile.upper() + "_H\n" + "#define " + funcFile.upper() + "_H\n\n" + proto + "\n\n#endif" fheader = open(funcHeader, "w") fheader.write(headerDefine) fheader.close() proto = str(proto) start = proto.find("*/") end = proto.find(";") proto = proto[start + 3 : end + 1] # Original function prototype start = proto.find(" ") end = proto.find("(") funcName = proto[start + 1 : end] # Original function name # Adjusting function names based on different implementations funcNameSIMD = funcName + "_SIMD" funcNameSVE = funcName + "_SVE" funcNameSVE2 = funcName + "_SVE2" ffunction = open(num, "r") funcOrigin = ffunction.read() ffunction.close() funcOrigin = str(funcOrigin) # Original function.c file content includeSysAux = "#include <sys/auxv.h>\n\n" iFuncProto = '__attribute__ (( ifunc("resolve_' + funcName + '") )) ' + proto # Function prototype with the added assembly ifunc attribute funcOriginSIMD = funcOrigin.replace(funcName, funcNameSIMD) pragmaSIMD = '\n\n#pragma GCC target "arch=armv8-a"\n\n' # Pragma Used for SIMD function funcOriginSVE = funcOrigin.replace(funcName, funcNameSVE) pragmaSVE = '\n\n#pragma GCC target "arch=armv8-a+sve"\n\n' # Pragma Used for SVE function funcOriginSVE2 = funcOrigin.replace(funcName, funcNameSVE2) pragmaSVE2 = '\n\n#pragma GCC target "arch=armv8-a+sve2"\n\n' # Pragma Used for SVE2 function fresolver = open("resolver.txt", "r") # Using resolver.txt as iFunc resolver function resolverFunc = fresolver.read() fresolver.close() resolverFunc = str(resolverFunc) # Altering iFunc function values based on the function names in function.c file resolverFunc = resolverFunc.replace("<<function_name>>", funcName) resolverFunc = resolverFunc.replace("<<function_sve2>>", funcNameSVE2) resolverFunc = resolverFunc.replace("<<function_sve>>", funcNameSVE) resolverFunc = resolverFunc.replace("<<function_simd>>", funcNameSIMD) function_altered.append(funcFile + "_altered.c") # Creating final function_altered.c filed with 3 implementations of function.c, including their pragmas and the ifunc resolver ffunctionAltered = open(function_altered[idx], "w") ffunctionAltered.write(includeSysAux + iFuncProto + pragmaSIMD + funcOriginSIMD + seperator + pragmaSVE + funcOriginSVE + seperator + pragmaSVE2 + funcOriginSVE2 + seperator + pragmaSIMD + seperator + resolverFunc) ffunctionAltered.close() newMain = open("main_test.c", "w") mainIncludes = mainIncludes + '#include "' + funcHeader + '"\n' newMain.write(mainIncludes) newMain.close() runScript += function_altered[idx] + " " idx += 1 newMain = open("main_test.c", "w") newMain.write(mainIncludes + seperator + mainContent) newMain.close() runScript += "-o ifuncMain" if (sys.argv[-1] == "compile"): print("Using command: ") print(runScript) os.system(runScript) else: print("Altered function(s) using iFunc Resolver have been created\n") print("Use the following command tu compile your program:\n") print(" gcc -g -o3 main_test.c function_altered.c function1_altered.c ... -o ifuncMain\n\n") print("Note: If you plan on using your original main function instead of the main_test.c,\n") print("(which is a copy of the original main.c + header files added on top), use main.c instead of main_test.c in the above commands.")
Comments