numba_vulkan.legalize#
Rewrites of LLVM IR that the SPIR-V backend cannot translate for shaders.
LLVM’s SPIR-V backend aborts on, or miscompiles, a number of constructs
that ordinary optimised code contains. Each function here replaces one of
them by an equivalent the backend handles. They work on textual IR and run
after LLVM’s passes, because the optimiser would otherwise recreate the
original forms (it recognises a hand-written copysign or NaN test and
emits the intrinsic or comparison again).
Most of these constructs come from libclc, whose clspv build expects
the clspv compiler to legalise them.
Functions#
|
Rewrite |
|
Rewrite |
|
Rewrite calls to |
|
Rewrite |
|
Rewrite |
|
Compute |
|
Rewrite |
|
Rewrite |
|
Rewrite libclc's 32-bit |
|
Rewrite strict comparisons with zero as negated complements. |
|
Replace |
|
Rewrite integer loads from byte tables as loads of single bytes. |
|
Rewrite saturating additions and subtractions as plain arithmetic. |
|
Apply all rewrites. |
Module Contents#
- numba_vulkan.legalize.expand_srem(text)#
Rewrite
sreminstructions asa - (a / b) * b.OpSRem returns wrong results for negative 64-bit operands on NVIDIA’s driver, so signed remainders are derived from the quotient instead.
- numba_vulkan.legalize.expand_fcmp_ordering(text)#
Rewrite
fcmp unoandfcmp ordas tests on the bit pattern.The SPIR-V backend emits OpUnordered and OpOrdered for them, which only OpenCL-flavoured SPIR-V may use. Numba’s own number and ufunc implementations produce these comparisons when they check for NaN.
- numba_vulkan.legalize.expand_copysign(text)#
Rewrite calls to
llvm.copysignas operations on the bit pattern.The SPIR-V backend cannot select the intrinsic, and instcombine introduces it even where the source spelled out the bit operations.
- numba_vulkan.legalize.expand_fmuladd(text)#
Rewrite
llvm.fmuladdas a multiplication and an addition.The intrinsic means “multiply and add, fused or not”; libclc uses it throughout. The SPIR-V backend of LLVM 22 emits GLSL’s
Fmafor it, which some drivers fuse and others do not, and which would bypass the softwarefmaof devices that need one (see emulate_fma). Separate operations give the same results on every device.
- numba_vulkan.legalize.emulate_fma(text, types=('float', 'double'))#
Rewrite
llvm.fmaas a fused multiply-add in software.Vulkan allows
Fmato round the product before adding, and llvmpipe does so in its vectorised code, in both precisions. libclc’s argument reductions,sinandcosamong them, rely on fusion and lose all accuracy without it. The product is computed exactly as the sum of two values (Dekker’s method, with Veltkamp’s splitting), added to the third operand with Knuth’s two-sum, and rounded once more. That differs from a true fused operation only in rare double-rounding cases. The splitting overflows for operands near the largest values; there,a * b + cis used.- Parameters:
- Returns:
The rewritten LLVM IR.
- Return type:
Notes
Correct only while every operation is rounded on its own, which the
NoContractiondecoration of all float arithmetic guarantees outsidefastmathkernels.
- numba_vulkan.legalize.emulate_rounding64(text)#
Compute
float64truncation and rounding to even fromfloor.llvmpipe (Mesa 26.1) gets both wrong in its vectorised code, that is for values that differ between invocations:
Truncreturns values of at least 2**24 unchanged, fraction included, andRoundEvenrounds halves towards zero and loses the sign of negative zeros.Flooris correct. This matters beyondmath.truncandround: libclc’ssin,cosandtantruncate the quadrant number, and go wrong from about 2**24 * pi / 2 on.With
a = |x|andf = floor(a),trunc(x)iscopysign(f, x)androundeven(x)iscopysign(f + 1, x)wherea - fexceeds a half or equals it withfodd, elsecopysign(f, x). Every step is exact, and infinities and NaN come out unchanged.
- numba_vulkan.legalize.expand_ctlz(text)#
Rewrite
llvm.ctlzas a binary search for the highest set bit.The backend cannot legalise the intrinsic. The result for zero is the bit width, as LLVM defines it.
- numba_vulkan.legalize.expand_funnel_shift(text)#
Rewrite
llvm.fshlandllvm.fshras shifts and anor.
- numba_vulkan.legalize.expand_mul_hi(text, narrow=False)#
Rewrite libclc’s 32-bit
mul_hihelper as a 64-bit multiplication.__clc_mul_hireturns the upper half of a product. libclc’s clspv build leaves it undefined, because the clspv compiler supplies it.
- numba_vulkan.legalize.avoid_faceforward(text)#
Rewrite strict comparisons with zero as negated complements.
The SPIR-V backend has a combine that turns
select(fcmp(a * b, 0), n, -n)into the GLSLfaceforwardfunction. For scalars it crashes or trips an internal assertion, and the pattern is common:x = a * bfollowed by-y if x < 0 else y, which is also howcopysignand rounding are usually written. The combine only matches the four strict predicates, sox < 0is emitted asnot (x >= 0 or unordered), which is equivalent.
- numba_vulkan.legalize.rename_minimumnum(text)#
Replace
llvm.minimumnum/maximumnumbyminnum/maxnum.The backend cannot legalise the newer intrinsics. They differ from the older ones only in how signalling NaNs and signed zeros are ordered.
- numba_vulkan.legalize.expand_byte_table_loads(text)#
Rewrite integer loads from byte tables as loads of single bytes.
libclc stores some constants as byte arrays and reads 32- or 64-bit integers from arbitrary byte offsets. Shaders address memory by element, not by byte, so such a load is assembled from the individual bytes (little-endian), each reached through a structured
getelementptr.
- numba_vulkan.legalize.expand_saturating(text)#
Rewrite saturating additions and subtractions as plain arithmetic.
LLVM’s instcombine forms
llvm.usub.satand its relatives from clamped arithmetic, which the SPIR-V backend cannot select.