Hi folks,
please check out our RFC: Supported Optimizations attribute
https://docs.google.com/document/d/1s0n-JVaSNML1Z9SCZVg2bgisxswIJAK2Tp9DahucW10/edit?usp=sharing
Pasting it here for the record:
RFC: supported_optimizations attribute
Piotr Padlewski - piotr.padlewski@gmail.com
Krzysztof Pszeniczny - kpszeniczny@google.com
December 2018
Introduction
Sometimes a possible class of optimizations requires very precise use of metadata and/or intrinsics, at least to achieve a clean implementation thereof.
Our primary example is devirtualization[RFC: Devirtualization v2], which requires that the LLVM IR be annotated with certain intrinsic calls, viz. launder.invariant.group and strip.invariant.group, in certain places, e.g. when the dynamic type of an object changes.
This currently makes it impossible to merge IR created with this kind of optimization in mind and without it, as this can lead to miscompilation when the optimizer relies on the lack of intrinsics and/or metadata to make certain assumptions about the code in question. For now, in case of devirtualization, we plainly refuse to merge such pieces of IR. This predominantly affects cross-language link-time optimization.
Solution
We propose a fine-grained, function-level solution to this problem, originally suggested by John McCall: mark each function with a list of such optimization requirements it complies with, by the means of an attribute for now called supported_optimizations. The exact naming of this attribute is of course subject to bikeshedding, so we kindly request the community to provide us with a better name, if found.
When performing inter-procedural optimizations (mostly inlining), the list of supported optimizations by the both functions should be considered to be to the intersection of their previous lists of supported optimizations. This effectively hinders any optimization relying on precise annotations from happening if such precise annotations can no longer be guaranteed.
More precisely, when doing an IPO other than inlining, such a pass must treat the functions in question as if they had the supported optimizations lists set to the intersection of their original ones. This in general obviously does not require modifying those lists.
In the case of inlining, the list of optimizations supported by the callee is left intact, but the list of optimizations supported by the caller must be set to the said intersection.
invariant.group
The !invariant.group metadata should be equipped with an argument referring to the supported_optimization it is related to. In the existing case, it will be set to “clang.cxx.devirtualization”. If this supported_optimization is not present in the list of optimizations supported by the enclosing function, the !invariant.group metadata has no effect and cannot be relied upon by the optimizer. Of course, in this case the metadata in question is not useful anymore and can be safely removed. We kindly thank Richard Smith for suggesting this approach.
As a transitional measure and for backwards compatibility reasons, any !invariant.group metadata with an empty argument (i.e. as before this RFC), shall not be subject to the above restrictions and shall remain applicable even when there is no supported_optimizations list provided for the enclosing function.
Example:
define void @with_devirtualization(i8* %p) supported_optimizations = “clang.cxx.devirtualization” {
%v1 = load i8, i8* %p, !invariant.group !0
%p2 = call i8* @llvm.launder.invariant.group.p0i8(i8* %p)
call void @without_devirtualization(i8* %p, i8* %p2)
%v2 = load i8, i8* %p2, !invariant.group !0
ret void
}
define void @without_devirtualization(i8* %p, i8* %p2) {
%b = icmp eq i8* %p, %p2
call void @llvm.assume(i1 %b)
ret void
}
!0 = !{!“clang.cxx.devirtualization”}
declare void @llvm.assume(i1)
declare i8* @llvm.launder.invariant.group.p0i8(i8*)
After inlining:
define void @with_devirtualization(i8* %p) {
; !invariant.group is inactive now, so it can be stripped.
%v1 = load i8, i8* %p, !invariant.group !0
%p2 = call i8* @llvm.launder.invariant.group.p0i8(i8* %p)
%b = icmp eq i8* %p, %p2
call void @llvm.assume(i1 %b)
%v2 = load i8, i8* %p2, !invariant.group !0
ret void
}
Possible extensions
Instead of indiscriminately taking the intersection of supported optimizations’ lists, we may imagine that some of such optimizations may be able to provide a conservative set of annotations to a function lacking them. By doing so, we may retain at least some level of information available to the optimizer, by preserving the annotations already present in the optimization-compliant function.
For example, devirtualization may conservatively pass every load and function argument through a strip and every store and return value through a launder, which is a way of conservatively making an arbitrary function compliant with the requirements of this particular optimization.
Bikeshedding
Other possible names:
-
compilant_with
-
brittle_representations (John McCall’s original suggestion)