Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #926 +/- ##
==========================================
+ Coverage 85.74% 87.25% +1.51%
==========================================
Files 29 39 +10
Lines 5619 6506 +887
==========================================
+ Hits 4818 5677 +859
- Misses 801 829 +28 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
maleadt
added a commit
that referenced
this pull request
Sep 9, 2026
#904 demoted Julia's `unordered` heap-reference accesses, but codegen also stores the type tag of every heap-allocated object with `release` ordering, through the generic pointer the device allocator returns. Apple's back-end cannot legalize that either: on macOS 26 the pipeline compile aborts with XPC_ERROR_CONNECTION_INTERRUPTED, and metal-tt reports "unable to legalize instruction: store release (p0)". It surfaced with Float64 emulation (#926), where DomainError(::Float64, ...) in log1p's throw path is not inlined and its allocation survives to the back-end. The same applies to SPIR-V: OpAtomicLoad/OpAtomicStore only take scalars, so a release-ordered pointer store is just as invalid there as an unordered one. Since device-side atomics go through target intrinsics rather than these instructions, every LLVM atomic load or store that reaches these back-ends is Julia GC bookkeeping, and `demote_atomics!` now strips all of them.
maleadt
force-pushed
the
tb/softfloat
branch
from
September 9, 2026 12:26
b90581d to
b620fd3
Compare
maleadt
force-pushed
the
tb/softfloat
branch
from
September 9, 2026 13:39
b620fd3 to
b68f8e9
Compare
Member
Author
|
Depends on llvm/llvm-project#208026, which I'll add in JuliaPackaging/Yggdrasil#14727 |
maleadt
force-pushed
the
tb/softfloat
branch
2 times, most recently
from
September 9, 2026 19:02
34ca1fe to
9b4ff44
Compare
The runtime library machinery compiles Julia methods into a cached, relocatable bitcode library that is linked into every kernel. Generalize it so that back-ends can link additional such libraries into individual compilation jobs, without registering anything globally. A provider (a subtype of `AbstractDeviceLibraryProvider`) describes its methods as `DeviceLibraryMethod`s and is selected per job through `device_library_providers(job)`. Its library is linked after `finish_linked_module!`, preceded by an optional `prepare_device_library!` hook that can rewrite the module first. This allows providers to legalize a representation (such as Float64 on Metal) before the definitions that implement it are linked in. Libraries are only loaded when the kernel actually references one of their exports, and are cached per provider and runtime configuration, sharing the per-function bitcode cache and validity tracking of the built-in runtime.
Implement SoftFloat64 with integer arithmetic and shared rounding, and link it through a job-scoped device-library provider. Preserve subnormals when widening under flush-to-zero and remap floating-point attributes with the integer ABI. Test arithmetic against native and high-precision references, execute legalized code through the native JIT, and validate Metal-compatible LLVM and SPIR-V output.
maleadt
force-pushed
the
tb/softfloat
branch
from
September 11, 2026 11:32
9b4ff44 to
3452534
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.
This PR makes ordinary
Float64code compile for GPU targets that have no double-precisionsupport, such as Metal and some oneAPI devices, by emulating the arithmetic in software.
It also generalizes the runtime-library machinery that this builds on, so that back-ends
can link additional Julia-implemented libraries into individual compilation jobs.
Heavily LLM assisted (Fable 5.1, Astra 6), so this will need a bunch of testing and review before it's viable.
How it works
The emulation operates on the final LLVM module, after all Julia code has been compiled and
linked, and is therefore transparent to Julia code: any
Float64code that works on the CPUworks on the device, including Base's pure-Julia math functions (
exp,log,sin,^,cbrt, ...),ComplexF64, tuples and structs containingFloat64, and so on.It happens in two steps, right before optimization (
src/softfloat/legalize.jl):doublevalues (fadd,fmul,fcmp,fptosi,llvm.sqrt.f64, ...) is replaced by a call to a placeholder function thatstill has the original
doublesignature, e.g.fadd double %a, %bbecomescall double @gpu_softfloat_add64(double %a, double %b).doubletype replaced byi64, infunction signatures, aggregates, vectors, globals, constants and typed attributes
(
byvaletc.). Constants keep their exact bit patterns. This turns the placeholders intodeclarations of the actual emulation routines, which are then linked in.
The routines themselves (
src/softfloat/binary64/) are plain Julia functions operating onUInt64bit patterns, ported from metal-softfloat(which derives from Berkeley SoftFloat). They cover addition, multiplication, division,
square root, FMA, comparisons, min/max, integral rounding, and conversions to and from
integers,
Float32andFloat16. Subtraction is addition of a negated operand, andnegation,
absandcopysignare single bit operations that inline into user code. Theexpensive routines are
@noinline, so every kernel contains at most one copy of each.For example,
Metal.code_llvm(+, (Float64, Float64))gives:while
-(x::Float64)compiles toxor i64 %x, -9223372036854775808, andx * 2.0 + 1.0totwo calls with the constants' bit patterns as immediate operands.
Semantics: round-to-nearest-even, gradual underflow, signed zeros, infinities, quiet
comparisons, and a canonical quiet NaN wherever an operation produces NaN. Not supported:
floating-point exception flags, other rounding modes,
Float64atomics andfrem(Julia'sremdoes not use it).One piece of Base does not compile as-is: the Payne-Hanek argument reduction used by
sin/cos/tanfor arguments above2^20·π/2usesUInt128arithmetic, which most GPUback-ends cannot lower (this is unrelated to Float64 emulation).
SoftFloat.paynehanekisan equivalent implementation using pairs of
UInt64, bit-for-bit identical to Base's, forback-ends to override
Base.Math.paynehanekwith.Device-library providers
The runtime library is compiled from Julia methods into per-function relocatable bitcode,
cached with their
CodeInstances, and linked into every kernel. This PR generalizes thatmachinery into providers: a back-end returns providers from
GPUCompiler.device_library_providers(job), each listing its methods asDeviceLibraryMethods. A provider's library is linked afterfinish_linked_module!, rightafter its
prepare_device_library!hook has had a chance to rewrite the module (which iswhere the legalization above runs). Libraries are loaded only when a kernel references one
of their exports, so enabling a provider for every job is cheap, and they are cached per
provider and runtime configuration, sharing the runtime's bitcode cache and invalidation.
Nothing is registered globally: loading a package that defines a provider has no effect on
jobs that do not select it. A back-end enables emulation with one method:
plus a method-table override for
Base.Math.paynehanek. Since the selection is a functionof the compiler configuration, cached compilation results are keyed correctly.
Validation
test/softfloat.jl: over a million bit-exact comparisons of every routine against nativeCPU arithmetic, including edge cases (subnormals, signed zeros, infinities, NaNs, every
Float16and both sides of everyFloat16rounding midpoint), andpaynehanekagainstBase's.
test/softfloat/legalize.jl: LLVM-level tests of the legalization (aggregates, vectors,globals, nested constant arrays, typed attributes, metadata, opaque-pointer storage), and
that unknown external ABIs are rejected rather than silently changed.
test/softfloat/native.jl,test/device_library.jl: execution through the native JIT,provider selection, and library invalidation on method redefinition.
test/spirv/softfloat.jl: a kernel compiled withsupports_fp64=falsethrough LLVM'sSPIR-V back-end validates and has neither
OpTypeFloat 64nor theFloat64capability.(The Khronos translator rejects the odd integer widths the optimizer introduces, e.g.
i11; actual oneAPI execution has not been tested.)Float64andComplexF64,on Julia 1.10, 1.11 and 1.12 (LLVM 15 through 18, typed and opaque pointers).
Performance
See JuliaGPU/Metal.jl#955 for measurements. In short, emulated operations cost roughly 2-10x a
native
Float32broadcast on an M1, and dependent chains of additions run at about atenth of the native
Float32rate. Code size stays bounded thanks to the out-of-lineroutines: a kernel using
exp,sqrtandsingrows by a few hundred lines of AIR.Provenance
src/softfloat/binary64/is a Julia port of metal-softfloat (MIT), whose tables andalgorithms derive from Berkeley SoftFloat 3e (BSD-3-Clause);
paynehanek.jlis adapted fromJulia's
base/special/rem_pio2.jl(MIT). The notices are inLICENSES/.