From e947aa8a4ba7463f295e2efbbdd28c6d0f19d7e1 Mon Sep 17 00:00:00 2001 From: johroj <56767565+johroj@users.noreply.github.com> Date: Mon, 10 Nov 2025 21:01:15 +0100 Subject: [PATCH 1/3] Enable juliac build --- src/FFTW.jl | 8 ++++---- src/fft.jl | 28 ++++++++++++++++++++-------- src/providers.jl | 16 ++++++++++++++-- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/FFTW.jl b/src/FFTW.jl index 8884704..c6ea114 100644 --- a/src/FFTW.jl +++ b/src/FFTW.jl @@ -31,19 +31,19 @@ end if VERSION >= v"1.11.0" # This can be be deleted once FFTW_jll is upgraded to the real lazy jll code, to get the real benefits of this mess -mutable struct FakeLazyLibrary +mutable struct FakeLazyLibrary{T} reallibrary::Symbol - on_load_callback + on_load_callback::T @atomic h::Ptr{Cvoid} end import Libdl: LazyLibrary, dlopen -function dlopen(lib::FakeLazyLibrary) +function dlopen(lib::FakeLazyLibrary{T}) where T h = @atomic :monotonic lib.h h != C_NULL && return h @lock fftwlock begin h = @atomic :monotonic lib.h h != C_NULL && return h - h = dlopen(getglobal(FFTW, lib.reallibrary)) + h = dlopen(getglobal(FFTW, lib.reallibrary)::String) lib.on_load_callback() @atomic :release lib.h = h end diff --git a/src/fft.jl b/src/fft.jl index eb62f61..1cced8d 100644 --- a/src/fft.jl +++ b/src/fft.jl @@ -320,14 +320,26 @@ unsafe_convert(::Type{PlanPtr}, p::FFTWPlan) = p.plan # pushing the plan to be destroyed to the deferred_destroy_plans (which itself is protected by a lock). # This is accomplished by the maybe_destroy_plan function, which is used as the plan finalizer. +struct FFTWPlanDestructor + ptr::PlanPtr + use_32bit_lib::Bool +end + +FFTWPlanDestructor(plan::FFTWPlan{<:fftwSingle}) = FFTWPlanDestructor(plan.plan, true) +FFTWPlanDestructor(plan::FFTWPlan{<:fftwDouble}) = FFTWPlanDestructor(plan.plan, false) + # these functions should only be called while the fftwlock is held -unsafe_destroy_plan(@nospecialize(plan::FFTWPlan{<:fftwDouble})) = - ccall((:fftw_destroy_plan,libfftw3), Cvoid, (PlanPtr,), plan) -unsafe_destroy_plan(@nospecialize(plan::FFTWPlan{<:fftwSingle})) = - ccall((:fftwf_destroy_plan,libfftw3f), Cvoid, (PlanPtr,), plan) +unsafe_destroy_plan(plan::FFTWPlan) = unsafe_destroy_plan(FFTWPlanDestructor(plan)) +function unsafe_destroy_plan(destructor::FFTWPlanDestructor) + if destructor.use_32bit_lib + ccall((:fftwf_destroy_plan,libfftw3f), Cvoid, (PlanPtr,), destructor.ptr) + else + ccall((:fftw_destroy_plan,libfftw3), Cvoid, (PlanPtr,), destructor.ptr) + end +end const deferred_destroy_lock = ReentrantLock() # lock protecting the deferred_destroy_plans list -const deferred_destroy_plans = FFTWPlan[] +const deferred_destroy_plans = FFTWPlanDestructor[] function destroy_deferred() lock(deferred_destroy_lock) @@ -375,7 +387,7 @@ function maybe_destroy_plan(plan::FFTWPlan) unlock(fftwlock) end else - push!(deferred_destroy_plans, plan) + push!(deferred_destroy_plans, FFTWPlanDestructor(plan)) end finally unlock(deferred_destroy_lock) @@ -495,11 +507,11 @@ function assert_applicable(p::FFTWPlan{T,K,inplace}, X::StridedArray{T}, Y::Stri elseif alignment_of(Y) != p.oalign && p.flags & UNALIGNED == 0 throw(ArgumentError("FFTW plan applied to output with wrong memory alignment")) elseif inplace != (pointer(X) == pointer(Y)) - throw(ArgumentError(string("FFTW ", + throw(ArgumentError(join(["FFTW ", inplace ? "in-place" : "out-of-place", " plan applied to ", inplace ? "out-of-place" : "in-place", - " data"))) + " data"]))) end end diff --git a/src/providers.jl b/src/providers.jl index 6764fd2..0ccc0e2 100644 --- a/src/providers.jl +++ b/src/providers.jl @@ -45,8 +45,20 @@ end # callback function that FFTW uses to launch `num` parallel # tasks (FFTW/fftw3#175): function spawnloop(f::Ptr{Cvoid}, fdata::Ptr{Cvoid}, elsize::Csize_t, num::Cint, callback_data::Ptr{Cvoid}) - @sync for i = 0:num-1 - Threads.@spawn ccall(f, Ptr{Cvoid}, (Ptr{Cvoid},), fdata + elsize*i) + # Wrap the for-loop in a simplified @sync, achieving type stability by not depending on a Channel{Any}. + # This is necessary for JuliaC to compile. The result runs as long as fftw only uses on thread. + # If FFTW.set_num_threads is used to activate more threads, there will still be a runtime error due to @spawn, + # which is limited by https://github.com/JuliaLang/julia/issues/58818 anyway. + tasks = Channel{Task}(Inf) + try + for i = 0:num-1 + put!(tasks, Threads.@spawn ccall(f, Ptr{Cvoid}, (Ptr{Cvoid},), fdata + elsize*i)) + end + while isready(tasks) + wait(take!(tasks)) + end + finally + close(tasks) end end From 29a960c557aa674c9cfd74db920c551a1de6e428 Mon Sep 17 00:00:00 2001 From: johroj <56767565+johroj@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:22:31 +0100 Subject: [PATCH 2/3] Use fptr in destructor, add __init__ --- src/FFTW.jl | 10 ++++++++-- src/fft.jl | 14 ++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/FFTW.jl b/src/FFTW.jl index c6ea114..6b842a1 100644 --- a/src/FFTW.jl +++ b/src/FFTW.jl @@ -36,7 +36,7 @@ mutable struct FakeLazyLibrary{T} on_load_callback::T @atomic h::Ptr{Cvoid} end -import Libdl: LazyLibrary, dlopen +import Libdl: LazyLibrary, dlopen, dlsym function dlopen(lib::FakeLazyLibrary{T}) where T h = @atomic :monotonic lib.h h != C_NULL && return h @@ -60,7 +60,13 @@ end const libfftw3 = FakeLazyLibrary(:libfftw3_no_init, fftw_init_check, C_NULL) const libfftw3f = FakeLazyLibrary(:libfftw3f_no_init, fftw_init_check, C_NULL) -else +if VERSION >= v"1.12.0" +function __init__() + dlopen(libfftw3) # Ensure that dlopen(::FakeLazyLibrary) is built by JuliaC +end +end + +else # !(VERSION >= v"1.11.0") @static if fftw_provider == "fftw" import FFTW_jll: libfftw3_path as libfftw3_no_init, libfftw3f_path as libfftw3f_no_init, diff --git a/src/fft.jl b/src/fft.jl index 1cced8d..1dfd1c3 100644 --- a/src/fft.jl +++ b/src/fft.jl @@ -322,20 +322,18 @@ unsafe_convert(::Type{PlanPtr}, p::FFTWPlan) = p.plan struct FFTWPlanDestructor ptr::PlanPtr - use_32bit_lib::Bool + fptr::Ptr{Cvoid} end -FFTWPlanDestructor(plan::FFTWPlan{<:fftwSingle}) = FFTWPlanDestructor(plan.plan, true) -FFTWPlanDestructor(plan::FFTWPlan{<:fftwDouble}) = FFTWPlanDestructor(plan.plan, false) +FFTWPlanDestructor(plan::FFTWPlan{<:fftwSingle}) = + FFTWPlanDestructor(plan.plan, dlsym(dlopen(libfftw3f), :fftwf_destroy_plan)) +FFTWPlanDestructor(plan::FFTWPlan{<:fftwDouble}) = + FFTWPlanDestructor(plan.plan, dlsym(dlopen(libfftw3), :fftw_destroy_plan)) # these functions should only be called while the fftwlock is held unsafe_destroy_plan(plan::FFTWPlan) = unsafe_destroy_plan(FFTWPlanDestructor(plan)) function unsafe_destroy_plan(destructor::FFTWPlanDestructor) - if destructor.use_32bit_lib - ccall((:fftwf_destroy_plan,libfftw3f), Cvoid, (PlanPtr,), destructor.ptr) - else - ccall((:fftw_destroy_plan,libfftw3), Cvoid, (PlanPtr,), destructor.ptr) - end + ccall(destructor.fptr, Cvoid, (PlanPtr, ), destructor.ptr) end const deferred_destroy_lock = ReentrantLock() # lock protecting the deferred_destroy_plans list From b06d563f738d3d85bc9ac54051cc84d2c2cb8eca Mon Sep 17 00:00:00 2001 From: johroj <56767565+johroj@users.noreply.github.com> Date: Sat, 15 Nov 2025 13:36:23 +0100 Subject: [PATCH 3/3] Fix 1.10 --- src/FFTW.jl | 2 +- src/fft.jl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/FFTW.jl b/src/FFTW.jl index 6b842a1..9adca47 100644 --- a/src/FFTW.jl +++ b/src/FFTW.jl @@ -36,7 +36,7 @@ mutable struct FakeLazyLibrary{T} on_load_callback::T @atomic h::Ptr{Cvoid} end -import Libdl: LazyLibrary, dlopen, dlsym +import Libdl: LazyLibrary, dlopen function dlopen(lib::FakeLazyLibrary{T}) where T h = @atomic :monotonic lib.h h != C_NULL && return h diff --git a/src/fft.jl b/src/fft.jl index 1dfd1c3..cae9529 100644 --- a/src/fft.jl +++ b/src/fft.jl @@ -2,6 +2,7 @@ import Base: show, *, convert, unsafe_convert, size, strides, ndims, pointer import LinearAlgebra: mul! +import Libdl: dlopen, dlsym """ r2r(A, kind [, dims])