Skip to content

Inline FP64 emulation when a kernel requires SIMD32 on fused-EU platforms - #428

Open
pvelesko wants to merge 2 commits into
intel:masterfrom
pvelesko:fix/fp64-emu-simd32-inline
Open

Inline FP64 emulation when a kernel requires SIMD32 on fused-EU platforms#428
pvelesko wants to merge 2 commits into
intel:masterfrom
pvelesko:fix/fp64-emu-simd32-inline

Conversation

@pvelesko

@pvelesko pvelesko commented Aug 5, 2026

Copy link
Copy Markdown

On platforms without native FP64, PreCompiledFuncImport rewrites double arithmetic into __igcbuiltin_dp_* calls and keeps the slow DP emulation builtins in call form. On platforms needing the fused-EU call workaround that is unsafe at SIMD32, and the guards that would otherwise prevent it do not apply to a kernel that requires SIMD32:

  • OpenCLKernelCodeGen.cpp:2669 (hasSubroutine) and :2849 (forceLowestSIMDForStackCalls) both sit inside if (requiredSimdSize == 0).
  • :2687, which forces SIMD16 when optimizations are disabled, carries the same condition, so -cl-opt-disable does not avoid it either.
  • :2829 does handle a required SIMD32 by disabling EU fusion and warning, but only when hasNestedCall || hasIndirectCall || isIndirectGroup. Plain emulation subroutines match none of those.

So a kernel with intel_reqd_sub_group_size(32) (or SPIR-V OpExecutionMode SubgroupSize 32) reaches SIMD32 codegen with the emulation still in call form, and the generated code is wrong with no diagnostic: the double returned by an emulated call is replaced by an unrelated live value of the caller, so rsqrt() yields another local. Substituting that value into a host model reproduces all 32 GPU lanes bit exactly.

Keep the emulation inlined for those kernels instead, unless call form was explicitly requested through EmulationFunctionControl.

Measured on the test kernel, same source and same ocloc, only libigc differing:

build __igcbuiltin_dp_ calls in the asm dump "Stack call has been detected" kernel emitted
master 20 yes yes
this change 0 no yes

Parts with native FP64 (Battlemage, PVC) generate no emulation calls at all and are unaffected, which is why the same reproducer passes there.

An alternative would be to widen the :2829 case to cover plain stack calls, disabling EU fusion for the whole kernel and warning. I chose inlining because it is local to the pass that creates the calls and does not change a global hardware mode as a side effect, but I am happy to take the other approach if preferred.

Note that IGC/ocloc_tests only builds with -DIGC_OPTION__ENABLE_OCLOC_LIT_TESTS=ON.

Fixes #397

…es SIMD32

On platforms without native FP64, PreCompiledFuncImport rewrites double
arithmetic into __igcbuiltin_dp_* calls and keeps the slow DP emulation builtins
as subroutines/stack calls rather than inlining them.

IGC otherwise keeps calls out of SIMD32 on platforms that need the fused-EU call
workaround, through ForceLowestSIMDForStackCalls and the requireCallWA() SIMD32
bail-out in COpenCLKernel::checkSIMDCompileConds(). Both guards sit inside
"if (requiredSimdSize == 0)", so a kernel that requires SIMD32 through
intel_reqd_sub_group_size(32) or SPIR-V OpExecutionMode SubgroupSize 32 bypasses
them and reaches SIMD32 codegen with the emulation still in call form.

The kernel added here does exactly that: intel_reqd_sub_group_size(32) with
rsqrt() on doubles under -cl-fp64-gen-emu on dg2. The test dumps the generated
assembly and requires that the kernel really is generated and that neither a
stack call nor a surviving __igcbuiltin_dp_ call appears anywhere in the dump.
Those two absence patterns are given to FileCheck as --implicit-check-not rather
than written as leading CHECK-NOT directives: a CHECK-NOT placed before the first
positive CHECK only guards the input ahead of that match, which is not where the
emulation calls are printed.

Without the following commit the emulation stays in call form and the generated
code is silently wrong: the double returned by an emulated call is replaced by
an unrelated live value of the caller.

Signed-off-by: Paulius Velesko <pvelesko@pglc.io>
…orms

On platforms without native FP64, PreCompiledFuncImport rewrites double
arithmetic into __igcbuiltin_dp_* calls and keeps the slow DP emulation
builtins as subroutines/stack calls rather than inlining them.

IGC otherwise keeps calls out of SIMD32 on platforms that need the fused-EU
call workaround, through ForceLowestSIMDForStackCalls and the requireCallWA()
SIMD32 bail-out in COpenCLKernel::checkSIMDCompileConds(). Both guards sit
inside "if (requiredSimdSize == 0)", so a kernel that requires SIMD32 through
intel_reqd_sub_group_size(32) or SPIR-V OpExecutionMode SubgroupSize 32
bypasses them and reaches SIMD32 codegen with the emulation still in call
form. The generated code is then silently wrong: the double returned by an
emulated call is replaced by an unrelated live value of the caller, so for
instance rsqrt() yields the value of another local. Measured on Gen12LP,
every lane of the result is wrong, and forcing the emulation inline via
EmulationFunctionControl makes the same kernel bit-exact.

Keep the emulation inlined for such kernels instead. SIMD8 and SIMD16, and
any explicit EmulationFunctionControl request, are unaffected.

Fixes intel#397

Signed-off-by: Paulius Velesko <pvelesko@pglc.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpAtomicIAdd (int64) produces 4x expected value with SubgroupSize 32 and double-precision shuffle/rsqrt loop

1 participant