-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FMV] Runtime Resolver Function #74
Conversation
Relate patch: |
cc @kito-cheng |
Is two word "FullFill" supposed to be the single word "Fulfill"? |
Do we intend to support __builtin_cpu_supports which is built on the same interface as function multiversioning on other targets like X86? That will require a reasonably fast query mechanism. String processing may be too much for that. |
Oops, I think there is a typo here. Updated. |
If we only allow one extension each time. Does it provide a reasonably fast query mechanism? Or must it be some kind of bit operation to determine support? For example, compiler generate this resolver function base on
|
My concern is that each time you pass a string into the compiler-rt interface, it will need to execute multiple strcmps to compare the input string against every extension name the library knows about to figure out which extension is being asked for. That gets expensive if called very often. On x86, builtin_cpu_supports calls the library the first time to update some global variables. After the first time it is a load and a bit test |
If you use a sensible data structure like a trie you can do it linearly in the length of the input string |
To enhance both the performance(compare to string base) and portability(compare to hwprobe base), I have updated the runtime interface with a new layer for each queryable extension. This approach is similar to approach 2 described in the PR's description. This comment aims to explain it with a concrete example using the IFUNC resolver function and Two structures are defined in the runtime library to store the status of hardware-enabled extensions: Each queryable extension has a unique position inside the structure bit to represent whether it is enabled. For example: extension m enable bit could be stored inside
Additionally, there is a function to initialize these two structures using a system-provided mechanism:
In summary, this approach uses When the compiler emits the IFUNC resolver function, it can use these structures to check whether all extension requirements are fulfilled. Here is a simple example for a resolver:
func_ptr foo1.resolver() {
__init_riscv_features_bit();
if (MAX_QUERY_LENGTH > __riscv_feature_bits.length)
raise_error();
// Try arch=rv64im
unsigned long long rv64im_require_feature_0 = constant_build_during_compiation_time();
unsigned long long rv64im_require_feature_1 = constant_build_during_compiation_time();
...
if (
((rv64im_require_feature_0 & __riscv_feature_bits.features[0]) == rv64im_require_feature_0) &&
((rv64im_require_feature_1 & __riscv_feature_bits.features[1]) == rv64im_require_feature_1) &&
...)
return foo1.rv64im;
return foo1.default;
} |
Who's specifying which bit is what? |
My idea is that bit is only meaningful for runtime function and compiler that using The remaining problem is how to synchronize the extension bitmask across LLVM, compiler-rt, GCC, and libgcc. I don't have a solution for this yet. @kito-cheng Any ideas on how we can achieve this synchronization? |
Update: add the extension |
This proposal got positive feedback from RISC-V GNU community :) |
Base on riscv-non-isa/riscv-c-api-doc#74. This patch defines the groupid/bitmask in RISCVFeatures.td and generates the corresponding table in RISCVTargetParserDef.inc. The groupid/bitmask of extensions provides an abstraction layer between the compiler and runtime functions.
…ature_bits/__init_riscv_features_bit Base on riscv-non-isa/riscv-c-api-doc#74, this patch defines the __riscv_feature_bits and __riscv_vendor_feature_bits structures to store the enabled feature bits at runtime. It also introduces the __init_riscv_features_bit function to update these structures based on the platform query mechanism. Additionally, the groupid/bitmask definitions from riscv-non-isa/riscv-c-api-doc#74 are declared and used to update the __riscv_feature_bits and __riscv_vendor_feature_bits structures.
Base on riscv-non-isa/riscv-c-api-doc#74. This patch defines the groupid/bitmask in RISCVFeatures.td and generates the corresponding table in RISCVTargetParserDef.inc. The groupid/bitmask of extensions provides an abstraction layer between the compiler and runtime functions.
Base on riscv-non-isa/riscv-c-api-doc#74. This patch defines the groupid/bitmask in RISCVFeatures.td and generates the corresponding table in RISCVTargetParserDef.inc. The groupid/bitmask of extensions provides an abstraction layer between the compiler and runtime functions.
…ature_bits/__init_riscv_features_bit Base on riscv-non-isa/riscv-c-api-doc#74, this patch defines the __riscv_feature_bits and __riscv_vendor_feature_bits structures to store the enabled feature bits at runtime. It also introduces the __init_riscv_features_bit function to update these structures based on the platform query mechanism. Additionally, the groupid/bitmask definitions from riscv-non-isa/riscv-c-api-doc#74 are declared and used to update the __riscv_feature_bits and __riscv_vendor_feature_bits structures.
There are two updates
|
TODO:
|
Added and relate LLVM PR llvm/llvm-project#101632 |
The spec can be found at riscv-non-isa/riscv-c-api-doc#74. 1. Add the new extension GroupID/Bitmask with latest hwprobe key. 2. Update the `initRISCVFeature ` 3. Update `EmitRISCVCpuSupports` due to not only group0 now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe @BeMg has already addressed all concerns so far, except for the performance features/data that haven't been added yet. However, we don't have the corresponding HWprobe bits available at the moment. HWprobe is still using a bitmap to represent the speed of misaligned access (RISCV_HWPROBE_MISALIGNED_*
), and the cache block size (RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE
) has a corresponding extension (Zic64b
) to represent it. So, there's nothing that we can't model for now. I guess we MAY need that in the future, but let's hold off on defining that interface until we find a use case for it.
GNU/Linux has historically had the following two resolver prototypes: 1. Elf_Addr(uint64_t, void *) 2. Elf_Addr(uint64_t, void *, void *) For the former, AT_HWCAP is passed in the first argument, and NULL in the second. For the latter, AT_HWCAP is still passed, and the second argument is a pointer to their home-grown __riscv_hwprobe function. Should they want to use the third argument in future, they'll have to introduce yet another prototype to allow for later expansion, and then all users will have to check whether the second argument is NULL to know if the third argument really exists. This is all rather silly and will surely prove fun in the face of type-checking CFI. Instead, be like arm64 and just define all 8 possible general purpose register arguments up front. To naive source code that forgets non-Linux OSes exist this will be compatible with prototype 1 above, since the second argument will be 0 and it won't look further (though should we start using the second argument for something that wouldn't be true any more and it might think it's __riscv_hwprobe, but that incompatibility is one we can defer committing to, and can choose to never adopt). Until the standard interface for querying extension information[1] is settled and implemented in FreeBSD there's not much you can do in a resolver other than use HWCAP_ISA_B, but this gets the infrastructure in place for when that day comes. [1] riscv-non-isa/riscv-c-api-doc#74 Reviewed by: kib, mhorne MFC after: 1 month Differential Revision: https://reviews.freebsd.org/D46278
The spec could be found here riscv-non-isa/riscv-c-api-doc#74 This patch updates the following symbol: ``` mVendorID -> mvendorid mArchID -> marchid mImplID -> mimpid ```
I think this is ready to land, but could you convert this PR into adoc format which used in main branch now.... |
Rebased and use the adoc format now. |
This PR proposes an Extension Bitmask to represent environment information. Since this Extension Bitmask is expected to be available and interchangeable for both libgcc and compiler-rt, a formal specification for the Extension Bitmask interface is necessary.
b7ed193
to
dba4dad
Compare
squash commit and rewrite the commit message |
Let move on! |
|
||
The `__init_riscv_feature_bits` function updates `length`, `mvendorid`, `marchid`, `mimpid` and the `features` in `__riscv_feature_bits` and `__riscv_vendor_feature_bits` according to the enabled extensions in the system. | ||
|
||
The `__init_riscv_feature_bits` function accepts an argument of type `void *`. This argument allows the platform to provide pre-computed data and access it without additional effort. For example, Linux could pass the vDSO object to avoid an extra system call. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Who's expected to call this, and so who's expected to know what magic opaque blob to pass here? Surely you don't expect the compiler to be generating OS-specific code to get data out of the vDSO and pass it to the function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ping...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BeMg ping
|
||
The `__init_riscv_feature_bits` function accepts an argument of type `void *`. This argument allows the platform to provide pre-computed data and access it without additional effort. For example, Linux could pass the vDSO object to avoid an extra system call. | ||
|
||
NOTE: To detect failure of the `__init_riscv_feature_bits` function, it is recommended to check the bitmask for the `I` extension. The `I` extension must be supported in all valid RISC-V implementations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not true? RVE exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could say check "__riscv_feature_bits.features[0]" is non zero?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now that works, until such time as a base extension other than I and E exists which therefore can't fit in misa, e.g. depending on how CHERI's defined that could end up true (though probably that should be represented in misa as I or E still being set). Alternatively you could just say the length is set to 0 on failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively you could just say the length is set to 0 on failure.
That's good idea, @BeMg could you create a PR for that?
…d __riscv_vendor_feature_bits This provides a common abstraction layer to probe the available extensions at run-time. These functions can be used to implement function multi-versioning or to detect available extensions. The advantages of providing this abstraction layer are: - Easy to port to other new platforms. - Easier to maintain in GCC for function multi-versioning. - For example, maintaining platform-dependent code in C code/libgcc is much easier than maintaining it in GCC by creating GIMPLEs... This API is intended to provide the capability to query minimal common available extensions on the system. Proposal in riscv-c-api-doc: riscv-non-isa/riscv-c-api-doc#74 Full function multi-versioning implementation will come later. We are posting this first because we intend to backport it to the GCC 14 branch to unblock LLVM 19 to use this with GCC 14.2, rather than waiting for GCC 15. Changes since v3: - Fix non-linux build. - Let __init_riscv_feature_bits become constructor Changes since v2: - Prevent it initialize more than once. Changes since v1: - Fix the format. - Prevented race conditions by introducing a local variable to avoid load/store operations during the computation of the feature bit. libgcc/ChangeLog: * config/riscv/feature_bits.c: New. * config/riscv/t-elf (LIB2ADD): Add feature_bits.c.
…d __riscv_vendor_feature_bits This provides a common abstraction layer to probe the available extensions at run-time. These functions can be used to implement function multi-versioning or to detect available extensions. The advantages of providing this abstraction layer are: - Easy to port to other new platforms. - Easier to maintain in GCC for function multi-versioning. - For example, maintaining platform-dependent code in C code/libgcc is much easier than maintaining it in GCC by creating GIMPLEs... This API is intended to provide the capability to query minimal common available extensions on the system. Proposal in riscv-c-api-doc: riscv-non-isa/riscv-c-api-doc#74 Full function multi-versioning implementation will come later. We are posting this first because we intend to backport it to the GCC 14 branch to unblock LLVM 19 to use this with GCC 14.2, rather than waiting for GCC 15. Changes since v3: - Fix non-linux build. - Let __init_riscv_feature_bits become constructor Changes since v2: - Prevent it initialize more than once. Changes since v1: - Fix the format. - Prevented race conditions by introducing a local variable to avoid load/store operations during the computation of the feature bit. libgcc/ChangeLog: * config/riscv/feature_bits.c: New. * config/riscv/t-elf (LIB2ADD): Add feature_bits.c.
…d __riscv_vendor_feature_bits This provides a common abstraction layer to probe the available extensions at run-time. These functions can be used to implement function multi-versioning or to detect available extensions. The advantages of providing this abstraction layer are: - Easy to port to other new platforms. - Easier to maintain in GCC for function multi-versioning. - For example, maintaining platform-dependent code in C code/libgcc is much easier than maintaining it in GCC by creating GIMPLEs... This API is intended to provide the capability to query minimal common available extensions on the system. Proposal in riscv-c-api-doc: riscv-non-isa/riscv-c-api-doc#74 Full function multi-versioning implementation will come later. We are posting this first because we intend to backport it to the GCC 14 branch to unblock LLVM 19 to use this with GCC 14.2, rather than waiting for GCC 15. Changes since v5: - Minor fixes on indentation. Changes since v4: - Bump to newest riscv-c-api-doc with some new extensions like Zve*, Zc* Zimop, Zcmop, Zawrs. - Rename the return variable name of hwprobe syscall. - Minor fixes on indentation. Changes since v3: - Fix non-linux build. - Let __init_riscv_feature_bits become constructor Changes since v2: - Prevent it initialize more than once. Changes since v1: - Fix the format. - Prevented race conditions by introducing a local variable to avoid load/store operations during the computation of the feature bit. libgcc/ChangeLog: * config/riscv/feature_bits.c: New. * config/riscv/t-elf (LIB2ADD): Add feature_bits.c. Co-Developed-by: Yangyu Chen <[email protected]> Signed-off-by: Yangyu Chen <[email protected]>
…d __riscv_vendor_feature_bits This provides a common abstraction layer to probe the available extensions at run-time. These functions can be used to implement function multi-versioning or to detect available extensions. The advantages of providing this abstraction layer are: - Easy to port to other new platforms. - Easier to maintain in GCC for function multi-versioning. - For example, maintaining platform-dependent code in C code/libgcc is much easier than maintaining it in GCC by creating GIMPLEs... This API is intended to provide the capability to query minimal common available extensions on the system. Proposal in riscv-c-api-doc: riscv-non-isa/riscv-c-api-doc#74 Full function multi-versioning implementation will come later. We are posting this first because we intend to backport it to the GCC 14 branch to unblock LLVM 19 to use this with GCC 14.2, rather than waiting for GCC 15. Changes since v5: - Minor fixes on indentation. Changes since v4: - Bump to newest riscv-c-api-doc with some new extensions like Zve*, Zc* Zimop, Zcmop, Zawrs. - Rename the return variable name of hwprobe syscall. - Minor fixes on indentation. Changes since v3: - Fix non-linux build. - Let __init_riscv_feature_bits become constructor Changes since v2: - Prevent it initialize more than once. Changes since v1: - Fix the format. - Prevented race conditions by introducing a local variable to avoid load/store operations during the computation of the feature bit. libgcc/ChangeLog: * config/riscv/feature_bits.c: New. * config/riscv/t-elf (LIB2ADD): Add feature_bits.c. Co-Developed-by: Yangyu Chen <[email protected]> Signed-off-by: Yangyu Chen <[email protected]>
GNU/Linux has historically had the following two resolver prototypes: 1. Elf_Addr(uint64_t, void *) 2. Elf_Addr(uint64_t, void *, void *) For the former, AT_HWCAP is passed in the first argument, and NULL in the second. For the latter, AT_HWCAP is still passed, and the second argument is a pointer to their home-grown __riscv_hwprobe function. Should they want to use the third argument in future, they'll have to introduce yet another prototype to allow for later expansion, and then all users will have to check whether the second argument is NULL to know if the third argument really exists. This is all rather silly and will surely prove fun in the face of type-checking CFI. Instead, be like arm64 and just define all 8 possible general purpose register arguments up front. To naive source code that forgets non-Linux OSes exist this will be compatible with prototype 1 above, since the second argument will be 0 and it won't look further (though should we start using the second argument for something that wouldn't be true any more and it might think it's __riscv_hwprobe, but that incompatibility is one we can defer committing to, and can choose to never adopt). Until the standard interface for querying extension information[1] is settled and implemented in FreeBSD there's not much you can do in a resolver other than use HWCAP_ISA_B, but this gets the infrastructure in place for when that day comes. [1] riscv-non-isa/riscv-c-api-doc#74 Reviewed by: kib, mhorne MFC after: 1 month Differential Revision: https://reviews.freebsd.org/D46278
This PR proposes a runtime resolver function that retrieves the environment information. Since this resolver function is expected to be available and interchangeable for both
libgcc
andcompiler-rt
, a formal specification for the resolver function interface is necessary.When generating the resolver function for function multiversioning, a mechanism is necessary to obtain the environment information.
To achieve this goal, several steps need to be taken:
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 step 2.
Here is a example
In this example, there are two versions of function
bar
. One for default, another for "rv64gcv".If the environment meets the requirements, then bar can utilize the
arch=rv64gcv
version. Otherwise, it will invoke the default version.This process be controlled by the
ifunc
resolver function.The version
arch=rv64gcv
requireThe 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.