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


This function determines which adjust_channels function will be used.

Identifying the 2 copies of adjust_channels function


This function uses SIMD instructions



This function uses Scalable Vector Extension instructions

Identifying the pragma statements

These pragma statements define the architecture to be used during the compilation



Identifying the indirect function prototype


Adding the compiler option -fdump-tree-gimple



You will notice that an additional output file with an extension of .gimple was produced in both instances.
  • function-main.c.006t.gimple

  • function_ifunc-main.c.006t.gimple

Researching about gimple

  1. 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")))

Transforming the gimple representation of function.c into the gimple representation of function_ifunc.c

To transition the gimple representation of function.c, it requires the transformation of the existing code to incorporate an ifunc declaration. 

  • Add the ifunc attribute to adjust_channels

  • add the refactored adjust_channels_resolver to return a function pointer to the desired adjust_channels versions


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: 
                - If a user wants to target a base architecture with extended architectures, they can use the flag                      format:                    
-march=<baseArch> -mtune=<extentedArch_1>,<extendedArch_2

                - If a user wants to target a base architecture of "armv8-a" and extended architectures of                              "armv8-a+sve" and "armv8-a+sve2", they can use
-march=armv8-a -mtune=armv8-a+sve,armv8-a+sve2

2. Constraints to the list of architecture variants
  • 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
3.  Where should automatic ifunc capability be added?
  • 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 
4. Should the user specify the functions to be affected? If so, how?
  • 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() {}

5.  Diagnostics during the compilation process
  • 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

Popular Posts