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#

expand_srem(text)

Rewrite srem instructions as a - (a / b) * b.

expand_fcmp_ordering(text)

Rewrite fcmp uno and fcmp ord as tests on the bit pattern.

expand_copysign(text)

Rewrite calls to llvm.copysign as operations on the bit pattern.

expand_fmuladd(text)

Rewrite llvm.fmuladd as a multiplication and an addition.

emulate_fma(text[, types])

Rewrite llvm.fma as a fused multiply-add in software.

emulate_rounding64(text)

Compute float64 truncation and rounding to even from floor.

expand_ctlz(text)

Rewrite llvm.ctlz as a binary search for the highest set bit.

expand_funnel_shift(text)

Rewrite llvm.fshl and llvm.fshr as shifts and an or.

expand_mul_hi(text[, narrow])

Rewrite libclc's 32-bit mul_hi helper as a 64-bit multiplication.

avoid_faceforward(text)

Rewrite strict comparisons with zero as negated complements.

rename_minimumnum(text)

Replace llvm.minimumnum/maximumnum by minnum/maxnum.

expand_byte_table_loads(text)

Rewrite integer loads from byte tables as loads of single bytes.

expand_saturating(text)

Rewrite saturating additions and subtractions as plain arithmetic.

legalize(text[, narrow_ints])

Apply all rewrites.

Module Contents#

numba_vulkan.legalize.expand_srem(text)#

Rewrite srem instructions as a - (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.

Parameters:

text (str) – Textual LLVM IR.

Returns:

The rewritten LLVM IR.

Return type:

str

numba_vulkan.legalize.expand_fcmp_ordering(text)#

Rewrite fcmp uno and fcmp ord as 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.

Parameters:

text (str) – Textual LLVM IR.

Returns:

The rewritten LLVM IR.

Return type:

str

numba_vulkan.legalize.expand_copysign(text)#

Rewrite calls to llvm.copysign as 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.

Parameters:

text (str) – Textual LLVM IR.

Returns:

The rewritten LLVM IR.

Return type:

str

numba_vulkan.legalize.expand_fmuladd(text)#

Rewrite llvm.fmuladd as 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 Fma for it, which some drivers fuse and others do not, and which would bypass the software fma of devices that need one (see emulate_fma). Separate operations give the same results on every device.

Parameters:

text (str) – Textual LLVM IR.

Returns:

The rewritten LLVM IR.

Return type:

str

numba_vulkan.legalize.emulate_fma(text, types=('float', 'double'))#

Rewrite llvm.fma as a fused multiply-add in software.

Vulkan allows Fma to round the product before adding, and llvmpipe does so in its vectorised code, in both precisions. libclc’s argument reductions, sin and cos among 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 + c is used.

Parameters:
  • text (str) – Textual LLVM IR.

  • types (tuple of str, optional) – The LLVM float types whose fma is rewritten.

Returns:

The rewritten LLVM IR.

Return type:

str

Notes

Correct only while every operation is rounded on its own, which the NoContraction decoration of all float arithmetic guarantees outside fastmath kernels.

numba_vulkan.legalize.emulate_rounding64(text)#

Compute float64 truncation and rounding to even from floor.

llvmpipe (Mesa 26.1) gets both wrong in its vectorised code, that is for values that differ between invocations: Trunc returns values of at least 2**24 unchanged, fraction included, and RoundEven rounds halves towards zero and loses the sign of negative zeros. Floor is correct. This matters beyond math.trunc and round: libclc’s sin, cos and tan truncate the quadrant number, and go wrong from about 2**24 * pi / 2 on.

With a = |x| and f = floor(a), trunc(x) is copysign(f, x) and roundeven(x) is copysign(f + 1, x) where a - f exceeds a half or equals it with f odd, else copysign(f, x). Every step is exact, and infinities and NaN come out unchanged.

Parameters:

text (str) – Textual LLVM IR.

Returns:

The rewritten LLVM IR, with the intrinsics it uses declared.

Return type:

str

numba_vulkan.legalize.expand_ctlz(text)#

Rewrite llvm.ctlz as 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.

Parameters:

text (str) – Textual LLVM IR.

Returns:

The rewritten LLVM IR.

Return type:

str

numba_vulkan.legalize.expand_funnel_shift(text)#

Rewrite llvm.fshl and llvm.fshr as shifts and an or.

Parameters:

text (str) – Textual LLVM IR.

Returns:

The rewritten LLVM IR.

Return type:

str

numba_vulkan.legalize.expand_mul_hi(text, narrow=False)#

Rewrite libclc’s 32-bit mul_hi helper as a 64-bit multiplication.

__clc_mul_hi returns the upper half of a product. libclc’s clspv build leaves it undefined, because the clspv compiler supplies it.

Parameters:
  • text (str) – Textual LLVM IR.

  • narrow (bool) – Use 32-bit arithmetic only, for devices without 64-bit integers.

Returns:

The rewritten LLVM IR.

Return type:

str

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 GLSL faceforward function. For scalars it crashes or trips an internal assertion, and the pattern is common: x = a * b followed by -y if x < 0 else y, which is also how copysign and rounding are usually written. The combine only matches the four strict predicates, so x < 0 is emitted as not (x >= 0 or unordered), which is equivalent.

Parameters:

text (str) – Textual LLVM IR.

Returns:

The rewritten LLVM IR.

Return type:

str

numba_vulkan.legalize.rename_minimumnum(text)#

Replace llvm.minimumnum/maximumnum by minnum/maxnum.

The backend cannot legalise the newer intrinsics. They differ from the older ones only in how signalling NaNs and signed zeros are ordered.

Parameters:

text (str) – Textual LLVM IR.

Returns:

The rewritten LLVM IR.

Return type:

str

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.

Parameters:

text (str) – Textual LLVM IR.

Returns:

The rewritten LLVM IR.

Return type:

str

numba_vulkan.legalize.expand_saturating(text)#

Rewrite saturating additions and subtractions as plain arithmetic.

LLVM’s instcombine forms llvm.usub.sat and its relatives from clamped arithmetic, which the SPIR-V backend cannot select.

Parameters:

text (str) – Textual LLVM IR.

Returns:

The rewritten LLVM IR.

Return type:

str

numba_vulkan.legalize.legalize(text, narrow_ints=False)#

Apply all rewrites.

Parameters:
  • text (str) – Textual LLVM IR after optimisation.

  • narrow_ints (bool) – Whether the kernel must not use 64-bit integers.

Returns:

IR that the SPIR-V backend can translate.

Return type:

str