SPO600 Project : Stage 2
In this stage, I will examine the intermediate code output and propose a design for how the automatic ifunc capability could operate from a user's POV.
Part 1: Experiment with the Proof-of-Concept
In this part, I will be experimenting with the Proof-of-Concept Automatic ifunc tool and the intermediate Representation produced by the GCC compiler.
Unpacking and building the code
First, I obtained the autoifunc proof-of-concept code from the file path provided by our professor. I then unpacked the tar file into my own directory.
I then build the code code using $make
After building the code, function_ifunc.c is generated.
Now, I will be comparing the output of function_ifunc.cand function.c
Compiling function.c
Compiling function_ifunc.c
Identifying the resolver function
Identifying the 2 copies of adjust_channels function
Identifying the pragma statements
Identifying the indirect function prototype
Adding the compiler option -fdump-tree-gimple
function-main.c.006t.gimple
function_ifunc-main.c.006t.gimple
Researching about gimple
- What is gimple?
- Gimple is an intermediate representation of a program
- used for optimization purposes because it makes code easier to analyze and refactor
Examining the gimple file for function_ifunc.c
- Are there any differences between the SVE and ASIMD versions of the function?
void adjust_channels__sve (unsigned char * image, int x_size, int y_size, float red_factor, float green_factor, float blue_factor)
{
unsigned char iftmp.0;
unsigned char iftmp.1;
unsigned char iftmp.2;
{
int i;
i = 0;
goto ;
:
_1 = (sizetype) i;
_2 = image + _1;
_3 = *_2;
_4 = (float) _3;
_5 = red_factor * _4;
if (_5 < 2.55e+2) goto ; else goto ;
:
_6 = (sizetype) i;
_7 = image + _6;
_8 = *_7;
_9 = (float) _8;
_10 = red_factor * _9;
iftmp.0 = (unsigned char) _10;
goto ;
:
iftmp.0 = 255;
:
_11 = (sizetype) i;
_12 = image + _11;
*_12 = iftmp.0;
_13 = (sizetype) i;
_14 = _13 + 1;
_15 = image + _14;
_16 = *_15;
_17 = (float) _16;
_18 = blue_factor * _17;
if (_18 < 2.55e+2) goto ; else goto ;
:
_19 = (sizetype) i;
_20 = _19 + 1;
_21 = image + _20;
_22 = *_21;
_23 = (float) _22;
_24 = blue_factor * _23;
iftmp.1 = (unsigned char) _24;
goto ;
:
iftmp.1 = 255;
:
_25 = (sizetype) i;
_26 = _25 + 1;
_27 = image + _26;
*_27 = iftmp.1;
_28 = (sizetype) i;
_29 = _28 + 2;
_30 = image + _29;
_31 = *_30;
_32 = (float) _31;
_33 = green_factor * _32;
if (_33 < 2.55e+2) goto ; else goto ;
:
_34 = (sizetype) i;
_35 = _34 + 2;
_36 = image + _35;
_37 = *_36;
_38 = (float) _37;
_39 = green_factor * _38;
iftmp.2 = (unsigned char) _39;
goto ;
:
iftmp.2 = 255;
:
_40 = (sizetype) i;
_41 = _40 + 2;
_42 = image + _41;
*_42 = iftmp.2;
i = i + 3;
:
_43 = x_size * y_size;
_44 = _43 * 3;
if (i < _44) goto ; else goto ;
:
}
}
void adjust_channels__asimd (unsigned char * image, int x_size, int y_size, float red_factor, float green_factor, float blue_factor)
{
unsigned char iftmp.3;
unsigned char iftmp.4;
unsigned char iftmp.5;
{
int i;
i = 0;
goto ;
:
_1 = (sizetype) i;
_2 = image + _1;
_3 = *_2;
_4 = (float) _3;
_5 = red_factor * _4;
if (_5 < 2.55e+2) goto ; else goto ;
:
_6 = (sizetype) i;
_7 = image + _6;
_8 = *_7;
_9 = (float) _8;
_10 = red_factor * _9;
iftmp.3 = (unsigned char) _10;
goto ;
:
iftmp.3 = 255;
:
_11 = (sizetype) i;
_12 = image + _11;
*_12 = iftmp.3;
_13 = (sizetype) i;
_14 = _13 + 1;
_15 = image + _14;
_16 = *_15;
_17 = (float) _16;
_18 = blue_factor * _17;
if (_18 < 2.55e+2) goto ; else goto ;
:
_19 = (sizetype) i;
_20 = _19 + 1;
_21 = image + _20;
_22 = *_21;
_23 = (float) _22;
_24 = blue_factor * _23;
iftmp.4 = (unsigned char) _24;
goto ;
:
iftmp.4 = 255;
:
_25 = (sizetype) i;
_26 = _25 + 1;
_27 = image + _26;
*_27 = iftmp.4;
_28 = (sizetype) i;
_29 = _28 + 2;
_30 = image + _29;
_31 = *_30;
_32 = (float) _31;
_33 = green_factor * _32;
if (_33 < 2.55e+2) goto ; else goto ;
:
_34 = (sizetype) i;
_35 = _34 + 2;
_36 = image + _35;
_37 = *_36;
_38 = (float) _37;
_39 = green_factor * _38;
iftmp.5 = (unsigned char) _39;
goto ;
:
iftmp.5 = 255;
:
_40 = (sizetype) i;
_41 = _40 + 2;
_42 = image + _41;
*_42 = iftmp.5;
i = i + 3;
:
_43 = x_size * y_size;
_44 = _43 * 3;
if (i < _44) goto ; else goto ;
:
}
}
- The major difference between the two functions is how each performs the calculations. The syntax and structure of the code between the functions are very similar.
- Has autovectorization been applied to the code at this point in the compilation process? How do you know?
- Autovectorization has not yet been applied to the code because there is no SIMD instructions used within the function
- How are the pragma lines in the source file reflected in the gimple?
- __attribute__((target ("arch=armv8-a+sve", "arch=armv8-a")))
- __attribute__((target ("arch=armv8-a+sve")))
- __attribute__((target ("arch=armv8-a+sve", "arch=armv8-a")))
- __attribute__((target ("arch=armv8-a+sve")))
Transforming the gimple representation of function.c into the gimple representation of function_ifunc.c
- Add the ifunc attribute to adjust_channels
Part 2: Designing an automatic ifunc implimentation
In this part, I was tasked to show how an automatic ifunc implementation within the GCC compiler could work from a users point-of-view.
1. To enable automatic ifunc implementation, the user needs to use compiler flags and options.
- A user for example can use the flag -fifunc to generate the code that automatically supports ifunc implementation.
- To specify the list of architecture variants the user wants to target, they can use the flags -march and -mtune. Some examples are:
-march=<baseArch> -mtune=<extentedArch_1>,<extendedArch_2
-march=armv8-a -mtune=armv8-a+sve,armv8-a+sve2
- There should be a detection mechanism at runtime to check if the targeted architecture variants are compatible
- The architecture variants should share a common base architecture and have compatible instruction sets
- Automatic ifunc capability should be added to functions that have multiple versions. In the case of the code example, the automatic ifunc capability can be added in the adjust_channels function as it is architecture-based
- There are many factors as to whether the user should explicitly specify the functions to be affected.
- functions that have multiple versions catered to specific architectures should automatically have automatic ifunc capabilities
- functions that are optimized for multiple architectures must also automatically have automatic ifunc capabilities
- library functions or frameworks can be user specified on the other hand
- A user can specify which functions should have automatic ifunc capabilities using the syntax:
__attribute__((ifunc("resolver_function")))void specified_function_1() {}
- A user should get a warning when the target architecture variants are incompatible
- A user should also get a warning statement if the target variants are not supported by the runtime environment
- A user should also get an error indicating syntax errors
- It is also beneficial for the user to receive informational messages that contain information about the compilation process, optimizations that were used, and information about the target architectures.















Comments
Post a Comment