top of page
Writer's picturearman valaee

GNU iFunc mechanism on Aarch64

Now is an excellent time to learn more about iFunc mechanism as we discussed in Software Portability Project - Stage 1.1 Background blog.

iFunc will help programmers choose an appropriately implemented function or a piece of code regarding the system's SIMD mechanism during runtime.



When to use iFunc


iFunc mechanism uses a resolver attribute to determine between the implemented functions based on the system's architecture.

The exciting part of this mechanism is being able to determine the appropriate function during the runtime.

If we create a program based on a single system, we won't have a problem since we already know the system specs. But if we want to create the same program in a way to be able to run on various machines, we cannot predict the specification of those systems.

This is exactly what software portability and compatibility are about.

This is where iFunc comes in handy.



How it Works


First let's take a look at this piece of code that I took from my professor, Chris Tyler's ifunc-aarch64-demo GitHub repository:


// Three implementations of foo() for three HW configurations
//
// (Obviously, these don't take advantage of the hardware,
// they're just for demonstration purposes!)
//
void *foo_sve2() {
	printf("  Using SVE2 implementation.\n");
};
          
void *foo_sve() {
	printf("  Using SVE implementation.\n");
};
          
void *foo_nonsve() {
	printf("  Using non-SVE/SVE2 implementation.\n");
};
          

// Resolver function - this function picks which of the
// implementations will be executed when foo() is called
//
// The resolver function is only run once, the first time
// that foo() is called.
//
static void (*resolve_foo(void)) {
	// Each of these two variables is populated with
	// a bitfield indicating specific hardware 
	// capabilities. hwcaps includes a bit for SVE,
	// and hwcaps2 includes a bit for SVE2
	//
	long hwcaps  = getauxval(AT_HWCAP);
	long hwcaps2 = getauxval(AT_HWCAP2);

	printf("\n### Resolver function - selecting the implementation to use for foo()\n");
	if (hwcaps2 & HWCAP2_SVE2) {
		return foo_sve2;
	} else if (hwcaps & HWCAP_SVE) {
		return foo_sve;
	} else {
		return foo_nonsve;
	}
};

// Prototype for function foo(), which will resolve to
// one of the three implementations depending on system
// capabilities     
void *foo () __attribute__((ifunc("resolve_foo")));

This is a test code for the iFunc, so the SVE, SVE2, and non-SVE implementation of the functions are not available. Instead, we have printf lines to test our code*.


As you can see there are 3 different functions with 3 different implementation methods, called foo_sve(), foo_sve2(), and foo_nonsve()**.


As the program runs, by having the following function prototype,

	void *foo () __attribute__((ifunc("resolve_foo")));

The following function will be called:

static void (*resolve_foo(void)) { ... }

In this function, we have several if-else statements to determine the type of system that is running the program.

But where do we get the information and how do we check it?

By using the getauxval() function!


* To see the complete code please visit ctyler/ifunc-aarch64-demo (github.com).


** This piece of code is only valid for systems with Aarch64 architecture and will display an error message when run by systems with x86_64 architecture.



getauxval(AT_HWCAP) & getauxval(AT_HWCAP2)


These functions help to get some information from the system that is running the program.

#include<sys/auxv.h> is Required to use these functions.


This function will return 0 if the targeted argument feature is not available in the system. This is a good indicator to check if the running machine supports the SIMD mechanism or not.

Storing the output will make it easier for us to check the system compatibility using the following if-else statements.


In the if-else statements, we start checking from the top-level mechanisms to not supported.

If the system supports the SVE2, the resolver function redirects to foo_sve2().

If it supports SVE, it will redirect to foo_sve(), and if it doesn't support any of them, it will be redirected to foo_nonsve().

An output value of 0 from the getauxval() function means that the system does not support the mechanism.


To learn more about the getauxval() function, I suggest you read the articles in the following links:


8 views0 comments

Comments


bottom of page