[FunctionSpecialization] Strip ptrauth bundles from devirtualized calls - #17
Draft
oskarwirga wants to merge 1 commit into
Draft
Conversation
Downstream apply of a proposed upstream LLVM fix needed to unblock the
arm64e Kotlin/Native target. Without it, FunctionSpecializationPass
fails the IR verifier with:
Direct call cannot have a ptrauth bundle
invoke void @"sym"() [ "ptrauth"(i32 0, i64 0) ]
to label %52 unwind label %55
Root cause: when FunctionSpecialization clones a function with a
constant function-pointer argument substituted into the body, an
indirect call
invoke void %fn_ptr_param() [ "ptrauth"(i32 0, i64 0) ] ; LEGAL indirect
becomes a direct call after IPSCCP's V->replaceAllUsesWith(Const):
invoke void @"sym"() [ "ptrauth"(i32 0, i64 0) ] ; ILLEGAL direct
The verifier rejects: the ptrauth bundle is only meaningful on indirect
calls (it tells the backend which key/discriminator to use to
authenticate the loaded function pointer before branching).
replaceAllUsesWith only rewrites the called operand; it does not touch
operand bundles.
Concrete reproducer from K/N arm64e bring-up: CallInitGlobalPossiblyLock
(a runtime helper) takes a fn-pointer arg and invokes it with a ptrauth
bundle. FunctionSpecialization produces
@CallInitGlobalPossiblyLock.specialized.1 and rewrites the indirect
invoke into a direct invoke to @"kfun:kotlin.text.\$init_global#internal.2",
with the bundle still attached. Compilation aborts at the verifier.
Fix: in FunctionSpecializer::createSpecialization, after cloning the
function, walk the clone body and strip ptrauth operand bundles from
any CallBase whose callee is one of the formal Arguments being
specialized to a Function constant. This pre-empts the IPSCCP rewrite
so the bundle is gone before the indirect-to-direct transition. Other
operand bundles are preserved; non-PAC targets are unaffected (no calls
carry ptrauth bundles).
Test: llvm/test/Transforms/FunctionSpecialization/ptrauth-bundle-strip.ll.
Triggers via -passes="ipsccp<func-spec>" -force-specialization with
callers passing different Function constants (forces the FuncSpec
clone path). Without the fix, opt aborts with the verifier error above.
With the fix, specialized clones contain direct calls with no ptrauth
bundle; a sibling negative case confirms bundles are preserved on
still-indirect calls.
Verified end-to-end against upstream llvm/llvm-project main:
- Test fails (verifier abort) without the fix.
- Test passes with the fix.
- llvm/test/Transforms/FunctionSpecialization/ all pass (60/62, 2
pre-existing UNSUPPORTED).
- llvm/test/Transforms/IPO/ and SCCP/ all pass (175/176, 1
pre-existing UNSUPPORTED).
- clang-format clean.
Filed upstream: TBD (llvm/llvm-project PR). This downstream apply lets
Kotlin/Native delete its StripDirectCallPtrauthBundlesPass workaround
(kotlin-native/backend.native/.../optimizations/StripDirectCallPtrauthBundlesPass.kt,
libllvmext/src/main/cpp/StripDirectCallPtrauthBundles.cpp, and the
Bitcode.kt phase wiring).
Note: a related variant of this bug exists on the IPSCCP in-place
constant-propagation path (when all callers pass the same Function
constant, FuncSpec is bypassed and IPSCCP rewrites the original
function in-place). That path is not addressed here and would need a
corresponding fix in SCCPSolver. The Kotlin/Native trigger is the
FuncSpec clone path.
|
Relevant Kotlin PR: JetBrains/kotlin#6306 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Downstream apply of a proposed upstream LLVM fix needed to unblock the arm64e Kotlin/Native target. Without it, FunctionSpecializationPass fails the IR verifier with:
Direct call cannot have a ptrauth bundle
invoke void @"sym"() [ "ptrauth"(i32 0, i64 0) ]
to label %52 unwind label %55
Root cause: when FunctionSpecialization clones a function with a constant function-pointer argument substituted into the body, an indirect call
invoke void %fn_ptr_param() [ "ptrauth"(i32 0, i64 0) ] ; LEGAL indirect
becomes a direct call after IPSCCP's V->replaceAllUsesWith(Const):
invoke void @"sym"() [ "ptrauth"(i32 0, i64 0) ] ; ILLEGAL direct
The verifier rejects: the ptrauth bundle is only meaningful on indirect calls (it tells the backend which key/discriminator to use to authenticate the loaded function pointer before branching). replaceAllUsesWith only rewrites the called operand; it does not touch operand bundles.
Concrete reproducer from K/N arm64e bring-up: CallInitGlobalPossiblyLock (a runtime helper) takes a fn-pointer arg and invokes it with a ptrauth bundle. FunctionSpecialization produces
@CallInitGlobalPossiblyLock.specialized.1 and rewrites the indirect invoke into a direct invoke to @"kfun:kotlin.text.$init_global#internal.2", with the bundle still attached. Compilation aborts at the verifier.
Fix: in FunctionSpecializer::createSpecialization, after cloning the function, walk the clone body and strip ptrauth operand bundles from any CallBase whose callee is one of the formal Arguments being specialized to a Function constant. This pre-empts the IPSCCP rewrite so the bundle is gone before the indirect-to-direct transition. Other operand bundles are preserved; non-PAC targets are unaffected (no calls carry ptrauth bundles).
Test: llvm/test/Transforms/FunctionSpecialization/ptrauth-bundle-strip.ll. Triggers via -passes="ipsccp" -force-specialization with callers passing different Function constants (forces the FuncSpec clone path). Without the fix, opt aborts with the verifier error above. With the fix, specialized clones contain direct calls with no ptrauth bundle; a sibling negative case confirms bundles are preserved on still-indirect calls.
Verified end-to-end against upstream llvm/llvm-project main:
Filed upstream: TBD (llvm/llvm-project PR). This downstream apply lets Kotlin/Native delete its StripDirectCallPtrauthBundlesPass workaround (kotlin-native/backend.native/.../optimizations/StripDirectCallPtrauthBundlesPass.kt, libllvmext/src/main/cpp/StripDirectCallPtrauthBundles.cpp, and the Bitcode.kt phase wiring).
Note: a related variant of this bug exists on the IPSCCP in-place constant-propagation path (when all callers pass the same Function constant, FuncSpec is bypassed and IPSCCP rewrites the original function in-place). That path is not addressed here and would need a corresponding fix in SCCPSolver. The Kotlin/Native trigger is the FuncSpec clone path.