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