Fold or erase the intrinsics that no SPIR-V producer accepts - #1477
Draft
pvelesko wants to merge 2 commits into
Draft
Fold or erase the intrinsics that no SPIR-V producer accepts#1477pvelesko wants to merge 2 commits into
pvelesko wants to merge 2 commits into
Conversation
Nine intrinsics that clang emits for ordinary __builtin_* calls reach the device link and fail there. Seven of the nine break both SPIR-V producers chipStar supports. Through the llvm-spirv translator every one of them aborts translation: InvalidFunctionCall: Unexpected llvm intrinsic: llvm.prefetch.p4 Through the in-tree SPIR-V backend seven of them abort code generation, for example: LLVM ERROR: unable to legalize instruction: G_PREFETCH %22:pid(p4), 0, 3, 1 chipStar puts all device code of a binary in one module, so any one of these takes down every kernel in the program, and the diagnostic carries no source location. The tests are compile-only, driven by hipcc -c through add_hipcc_test, at -O0 and -O3 since the intrinsics do not all survive to the device link at the same optimization level. llvm.objectsize is checked at -O0 only because LLVM's own LowerConstantIntrinsics pass folds it away from -O1 up.
pvelesko
force-pushed
the
2026-08-25-lower-hint-intrinsics
branch
from
September 1, 2026 06:37
65ed5c3 to
7eaf31b
Compare
Nine intrinsics that clang emits for ordinary __builtin_* calls reach SPIR-V
emission and fail there. The llvm-spirv translator rejects all nine with
"InvalidFunctionCall: Unexpected llvm intrinsic", and the in-tree SPIR-V
backend fails to legalize seven of them. chipStar puts all device code of a
binary in one module, so one __builtin_prefetch anywhere in it makes every
kernel in the program fail to build, with a diagnostic that carries no source
location.
New HipLowerHintIntrinsicsPass handles eight of them, each with a rewrite the
LLVM Language Reference explicitly authorises, so no defined behavior changes:
llvm.prefetch erased ("otherwise, it is a noop")
llvm.readcyclecounter 0 ("On backends without support, this is
llvm.readsteadycounter lowered to a constant 0.")
llvm.get.rounding 1, round to nearest ties to even, the only mode an
OpenCL device offers, and guarded by the absence of
llvm.set.rounding, which clang rejects on spirv64
llvm.allow.runtime.check false ("the program must be valid and correct both
if it returns true and if it returns false")
llvm.returnaddress null ("or zero if it cannot be identified")
llvm.frameaddress null (same sentence)
llvm.objectsize lowerObjectSizeCall, the same entry point LLVM's
own LowerConstantIntrinsics pass uses
The ninth, llvm.memcpy.inline, has a real effect and is expanded to a loop
instead. HipLowerMemset becomes HipLowerMemIntrinsics and picks it up next to
the memset expansion it already did. The memset form needed no separate case
because MemSetInst::classof covers Intrinsic::memset_inline, whereas
MemCpyInst::classof covers plain llvm.memcpy too, which keeps its direct
OpCopyMemorySized translation and is filtered out.
The pass runs before the pipeline's DCE so the address computations feeding an
erased llvm.prefetch are removed with it.
pvelesko
force-pushed
the
2026-08-25-lower-hint-intrinsics
branch
from
September 1, 2026 06:44
7eaf31b to
7f6792b
Compare
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.
Nine intrinsics that clang emits for ordinary
__builtin_*calls survivehip-post-link-passesand then fail at SPIR-V emission.hipcc -con a kernel that calls__builtin_prefetchfails today at-O0and at-O3, on LLVM 21, 22 and 23, through both producers chipStar supports.Through the
llvm-spirvtranslator:Through the in-tree SPIR-V backend:
chipStar puts all device code of a binary into a single module, so one
__builtin_prefetchanywhere in the program makes every kernel in it fail to build, and neither diagnostic carries a source location.__builtin_prefetchis common in the CPU code people port to HIP.Measured per intrinsic on minimal
spirv64modules withllvm-spirvandllcfrom each installed toolchain. The translator rejects all nine on all three LLVM versions withInvalidFunctionCall: Unexpected llvm intrinsic. The backend:The fix is a new
HipLowerHintIntrinsicsPassinllvm_passes/, registered in the post link pipeline next to the other lowering passes and standalone aship-lower-hint-intrinsicsso it is directly testable withopt. It handles eight of the nine, and each rewrite is one the LLVM Language Reference explicitly authorises, so no defined behaviour changes. Quotations below are fromllvm/docs/LangRef.mdatllvmorg-23.1.0-rc2.llvm.prefetchis erased. Overview: "The 'llvm.prefetch' intrinsic is a hint to the code generator to insert a prefetch instruction if supported; otherwise, it is a noop." Semantics: "This intrinsic does not modify the behavior of the program."llvm.readcyclecounterandllvm.readsteadycounterbecomei64 0. Both Semantics sections say "On backends without support, this is lowered to a constant 0."llvm.get.roundingbecomesi32 1. LangRef fixes the encoding of the result, with 1 meaning "to nearest, ties to even". That is the only mode an OpenCL device offers, per the OpenCL C specification, Rounding Modes: "The only default floating-point rounding mode supported is round to nearest even i.e the default rounding mode will be_rtefor floating-point types." The fold is additionally guarded on the module containing nollvm.set.rounding, so a folded getter can never contradict a setter. That guard is not expected to fire, since clang rejects the setter outright on this target withbuiltin is not supported on this target.llvm.allow.runtime.checkbecomesi1 false. Semantics: "For each evaluation of a call to this intrinsic, the program must be valid and correct both if it returnstrueand if it returnsfalse."falseelides the guarded check, which is the better choice on a GPU.llvm.returnaddressandllvm.frameaddressbecome a null pointer. Both Semantics sections say the intrinsic "either returns a pointer indicating the [return|frame] address of the specified call frame, or zero if it cannot be identified", and a SPIR-V kernel has no addressable call frame.llvm.objectsizeis resolved withllvm::lowerObjectSizeCall, the same entry point LLVM's ownLowerConstantIntrinsicspass uses. Semantics: "Thellvm.objectsizeintrinsic is lowered to a value representing the size of the object concerned. If the size cannot be determined,llvm.objectsizereturnsi32/i64 -1 or 0(depending on theminargument)." That is why the backend already copes and only the translator lane needs this.The ninth,
llvm.memcpy.inline, has a real effect and is expanded to a loop instead.HipLowerMemsetbecomesHipLowerMemIntrinsicsand picks it up next to thellvm.memsetexpansion it already did. The memset form needed no separate case becauseMemSetInst::classofalready coversIntrinsic::memset_inline, whereasMemCpyInst::classofcovers plainllvm.memcpytoo, which keeps its directOpCopyMemorySizedtranslation and is filtered out. A loop preserves what LangRef guarantees: "The behavior of 'llvm.memcpy.inline.' is equivalent to the behavior of 'llvm.memcpy.', but the generated code is guaranteed not to call any external functions."The new pass runs before the pipeline's DCE so the address computations that fed an erased
llvm.prefetchare removed with it.Tests are
tests/compilersources driven byhipcc -cthroughadd_hipcc_test, one per intrinsic, at-O0and-O3, since the intrinsics do not all survive to the device link at the same optimization level.llvm.objectsizeis checked at-O0only, because from-O1up LLVM'sLowerConstantIntrinsicsfolds it away before the device link and the test would pass regardless. All 17 fail on the tests only commit on the translator lane, and 8 of the 17 fail there on the LLVM 23 in-tree backend lane, which already accepts allow.runtime.check, objectsize, memcpy.inline, returnaddress and frameaddress. All 17 pass on the fix commit on both lanes.Verified on two builds of this branch, one against LLVM 22.1.0 and one against LLVM 23.1.0-rc2 with the in-tree SPIR-V backend, both of which build the whole tree and pass all 17 new tests. In
tests/compilerthe only failures are the shell tests that execute the binary they build, which cannot run on the GPU used here; the exact count is environment specific rather than a property of the branch.Caveats. No device was available on the machine this was developed on, so the six executing tests fail at
hipErrorNotInitialized (OpenCL failed to initialize any devices)and every result above is compile level only. CI needs to confirm the runtime behaviour, in particular that__builtin_memcpy_inlinestill copies the right bytes and that__builtin_flt_roundsreturning 1 matches the device. LLVM 21 coverage here is a syntax check of the two pass sources against the LLVM 21.1.7 headers plus the standalonellvm-spirvandllcmatrix, not a full chipStar build.