Skip to content

[RFC] Function multiversion resolver function implementation #72

@BeMg

Description

@BeMg

When generating the resolver function for function multiversioning, a mechanism is needed to retrieve the environment information.

To achieve this goal, several steps need to be taken:

  1. Collect the required extensions for a particular function.
  2. Transform these required extensions into a platform-dependent form.
  3. Query whether the environment fulfills these requirements during runtime.

Step 1 is handled by the compiler, while step 3 must follow the necessary steps from the platform during runtime.

This RFC aims to propose how the compiler and runtime function can tackle problem 2.

Here is a example

__attribute__((target_clones("default", "arch=rv64gcv"))) int bar() {
    return 1;
}

In this example, there are two versions of function bar. One for default, another for "rv64gcv".

If environment fullfills the requirement, then bar could use the version arch=rv64gcv. Otherwise, It invokes with default version.

This process be controlled by the ifunc resolver function.

ptr bar.resolver() {
   if (isFullFill(...))
      return "bar.arch=rv64gcv";
   return bar.default;
}

The isFullFill should available during the program runtime.

The version arch=rv64gcv require

i, m, a, f, d, c, v, zicsr, zifencei, zve32f, zve32x, zve64d, zve64f, zve64x, zvl128b, zvl32b, zvl64b,

The problem 2 is about where to maintain the relationship between extension names and platform-dependent probe forms.

Here are three possible approach to achieve goal.

  1. Encode all required extensions into a string format, then let the platform implement its own probe approach based on the string inside the runtime function. This approach maintains the relationship between extension names and platform-dependent probe forms inside the runtime function.
ptr bar.resolver() {
   if (isFullFill("i;m;a;f;d;c;v;zicsr;zifencei;zve32f;zve32x;zve64d;zve64f;zve64x;zvl128b;zvl32b;zvl64b"))
      return bar.arch=rv64gcv;
   return bar.default;
}

bool isFullFill(char *ReqExts) {
    if (isLinux())
       return doLinuxRISCVExtensionProbe(ReqExts);
    if (isFreeBSD())
       return doFreeBSDRISCVExtensionProbe(ReqExts);
    // Other platform
    ....
    return false;
}
  • Pros
    • Human readable
    • Relatively high portability
    • Provides a uniform interface for all platforms
  • Cons
    • Requires extra effort for string processing in the runtime function.
  1. Encode all required extensions into a compiler-defined key, then let the platform implement its own probe approach inside the runtime. This approach maintains the relationship between the compiler-defined key for extensions and the platform-dependent probe form inside the runtime function.
// Assume compiler define
// i -> 1
// m -> 2
...

ptr bar.resolver() {
   if (isFullFill([1, 2, 3, 8, ...], length))
      return bar.arch=rv64gcv;
   return bar.default;
}

bool isFullFill(int *ReqExts, length) {
    if (isLinux())
       return doLinuxRISCVExtensionProbe(ReqExts, length);
    if (isFreeBSD())
       return doFreeBSDRISCVExtensionProbe(ReqExts, length);
    // Other platform
    ....
    return false;
}
  • Pros
    • Doesn't require string processing during runtime
    • Provides a uniform interface for all platforms
  • Cons
    • Requires maintaining the relationship between the compiler-defined key for extensions and the concrete extension names inside runtime function.
  1. Define a different runtime function for each platform and construct any necessary information during compilation time if necessary for the platform. This approach maintains the relationship between extension names and platform-dependent probe forms inside the compiler.
// If compiler compile for linux, then use bar.resolver.linux
ptr bar.resolver.linux() {
   if (isFullFillLinux(LinuxProbeObject))
      return bar.arch=rv64gcv;
   return bar.default;
}

ptr bar.resolver.freebsd() {
   if (isFullFillFreeBSD(FreeBSDProbeObject))
      return bar.arch=rv64gcv;
   return bar.default;
}

// Other platform bar.resolver
...

bool isFullFillLinux(LinuxProbeObject Obj) {
   return doLinuxProbe(Obj);
}

bool isFullFillFreeBSD(FreeBSDProbeObject Obj) {
   return doFreeBSDProbe(Obj);
}

// Other platform isFullFill
...

  • Pros
    • Relatively simple implementation for the runtime function
  • Cons
    • Does not provide a uniform interface for all platforms

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions