Move vtable function pointers into the generic address space - #1376
Draft
pvelesko wants to merge 5 commits into
Draft
Move vtable function pointers into the generic address space#1376pvelesko wants to merge 5 commits into
pvelesko wants to merge 5 commits into
Conversation
Clang emits C++ vtables for SPIR-V targets with every slot in the global address space, so each virtual function slot is a constant expression addrspacecast (ptr @_ZN7DerivedD2Ev to ptr addrspace(1)) SPIR-V only permits casts from a named storage class into Generic, never into another named one, so any translation unit that dispatches virtually on the device was rejected by the translator with InvalidModule: Invalid SPIR-V module: Casts from private/local/global address space are allowed only to generic HipFunctionPointerASPass retypes such tables so their slots hold generic pointers, then repairs the slot loads and the indirect call sites that read them. The vptr stored inside the object is an ordinary data pointer and stays in the global address space. Non function slots such as the RTTI descriptor move to generic as well, which is the legal global to generic direction. The trigger is semantic rather than the Itanium _ZTV name prefix: any global whose initializer casts a Function into a non generic address space. That also covers hand written dispatch tables. SPV_INTEL_function_pointers is already requested by the driver and the translator only emits the OpExtension when the module actually contains function pointers, so modules without virtual dispatch are unaffected on targets lacking the extension. Refs: #1373
Two parts, because executing an indirect call needs SPV_INTEL_function_pointers and only Intel's stack implements it today. tests/compiler is compile only and runs on every target. -c still performs the device link that translates to SPIR-V, which is exactly where the unfixed compiler aborted, so it catches a regression of the pass without launching anything. Verified against a pre-fix install: it fails there with the original address space diagnostic and passes with the fix. tests/runtime additionally executes the dispatch and checks the values numerically, exercising both a base pointer whose dynamic type comes from a runtime value and one round tripped through memory. It gates itself on device capability and reports the ctest skip code otherwise. Support cannot be probed: Intel accepts such modules without advertising cl_intel_function_pointers, rusticl rejects them only once the device JIT runs, and PoCL neither runs nor rejects them, it hangs. Since the hang rules out deciding from a failed build, the gate allow-lists the stacks known to implement the extension, mirroring the isIntelGPU idiom the OpenCL backend already uses. Requiring a GPU as well as an Intel vendor matters: an Intel OpenCL CPU device reports the same vendor string. Refs: #1373
pvelesko
force-pushed
the
vtable-generic-as
branch
from
July 28, 2026 10:03
42ccee9 to
283957b
Compare
A constant getelementptr carries its source element type explicitly, so the
replaceAllUsesWith in replaceGlobalInitializer left GEPs claiming the old
value type while pointing at the retyped global.
Classes with virtual bases are where this shows: the VTT (_ZTT...) holds one
'getelementptr inrange(...) ({...}, ptr @_ZTC...)' per construction vtable,
and once a _ZTC table moves its slots to the generic address space the
SPIR-V writer aborts on the disagreement.
Rebuild those GEPs against the new value type, preserving indices, nowrap
flags and inrange.
Refs: #1373
Clang emits the vtable, VTT, construction vtables and RTTI of every
polymorphic class that merely appears in device code, even when no device
code dispatches through them. Trilinos is full of such classes:
Tpetra::RowMatrix is host-only polymorphic with virtual bases, so each HIP
explicit instantiation carries a dead _ZTV/_ZTT/_ZTC set whose slots are
almost all null and whose virtual base offsets are inttoptr constants.
Translating those aborts llvm-spirv:
SPIRVWriter.cpp:1359: transConstantUse: Assertion
'(C->getType()->isPointerTy() || ExpectedType->isTypeUntypedPointerKHR())
&& "Only pointer type mismatches should be possible"' failed.
which took out Tpetra_RowMatrix_{INT_INT,DOUBLE_INT,LONG_LONG_INT}_LONG_LONG_HIP
and several Xpetra/Galeri instantiations. GlobalDCE leaves them alone because
they sit in comdats.
A chipStar device module is self-contained, so nothing outside it can link
against these symbols and dropping the unreferenced ones is safe. Only the
Itanium vtable prefixes are matched, so runtime visible globals such as
__chip_var_* are untouched. Iterate to a fixed point: erasing a VTT releases
the construction vtables it pointed at.
Refs: #1373
Clang emits a reference to __cxa_pure_virtual in the vtable of every class
that still has an unoverridden pure virtual member. Nothing defined that
symbol on the SPIR-V platform, so the consumer rejected the whole module
error : unresolved external symbol symbol #__cxa_pure_virtual
at offset 56 in data segment #GLOBAL_VARIABLES
and every kernel in the program silently failed to build. Launches kept
reporting hipSuccess while results stayed at their initial values.
Define it next to __assert_fail with the same semantics ROCm's device libs
use: reaching a pure virtual slot is a hard error, so report and abort.
The check has to run, not just compile: the symbol resolves at the device
module build inside the driver, so a compile-only test passes either way.
TestDeviceVirtualDispatch.hip already carries the indirect-call capability
gate, so the abstract-base case goes there rather than duplicating it.
Fixes #1381
Collaborator
Author
|
This branch as pushed regresses Trilinos STK from 8/14 to 7/14 on Arc B570: Folding in the two commits that fix it, a constant GEP repair for retyped vtables and dropping unreferenced vtable globals (#1382), plus the device |
Collaborator
Author
|
Necessary but not sufficient for RDC device virtual dispatch: with this applied, a |
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.
Adds an LLVM pass that moves C++ vtable function pointer slots into the generic address space, the only address space SPIR-V permits function pointers in, and repairs the slot loads and indirect call sites that read them.
Also folds in the three follow-ups that make it usable on real C++: constant GEPs are rebuilt when a vtable is retyped (classes with virtual bases), unreferenced vtable globals are dropped before translation, and
__cxa_pure_virtualis defined on the device.Closes #1373
Fixes #1381
Fixes #1382