Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,14 @@ Run from this repository:
| hipSPARSE | `ROCM_LIBRARIES=<rocm-libraries> ./gen.sh --overwrite hipsparse` | `bindings/fortran/hipsparse/hipsparse.F90` |
| rocFFT | `ROCM_LIBRARIES=<rocm-libraries> ./gen.sh --overwrite rocfft` | `bindings/fortran/rocfft/rocfft.F90` |
| hipFFT | `ROCM_LIBRARIES=<rocm-libraries> ./gen.sh --overwrite hipfft` | `bindings/fortran/hipfft/hipfft.F90` |
| hipFFT Xt and Mp | `ROCM_LIBRARIES=<rocm-libraries> ./gen.sh --overwrite hipfftxt` | `bindings/fortran/hipfft/hipfftxt.F90` |
| hipFFTW | `ROCM_LIBRARIES=<rocm-libraries> ./gen.sh --overwrite hipfftw` | `bindings/fortran/hipfft/hipfftw.F90` |
| rocRAND | `ROCM_LIBRARIES=<rocm-libraries> ./gen.sh --overwrite rocrand` | `bindings/fortran/rocrand/rocrand.F90` |
| hipRAND | `ROCM_LIBRARIES=<rocm-libraries> ./gen.sh --overwrite hiprand` | `bindings/fortran/hiprand/hiprand.F90` |

`<rocm-systems>` and `<rocm-libraries>` are the paths of your checkouts, and the
last column is relative to them. Several libraries can be named in one run
(`./gen.sh --overwrite hipfft hipfftw`). A math library reads its own headers from
(`./gen.sh --overwrite hipfft hipfftxt hipfftw`). A math library reads its own headers from
the checkout and HIP's from the installed ROCm (`ROCM_INCLUDE`, default
`/opt/rocm/include`); set `ROCM_SYSTEMS` as well to take HIP from a rocm-systems
checkout instead.
Expand Down Expand Up @@ -158,7 +159,7 @@ the checkout, and compile it with `bash check_mods.sh`.

main()
```
The others pass `argshook=` to add include directories or force-included headers (see `src/hipfft/generator.jl`).
The others pass `argshook=` to add include directories or force-included headers (see `src/hipfftxt/generator.jl`).
3. Add `"<lib>"` to `LIBS` in `src/common/header_sources.jl`
(and to `proj_of`, `repo_of` and `lib_include` there if its header directory is not `<lib>/` or it ships in rocm-systems).
4. Run `./gen.sh && bash check_mods.sh`, then add `src/<lib>/metadata/<lib>.toml` as needed (see [Fixing a binding](#fixing-a-binding)).
Expand All @@ -173,6 +174,9 @@ If the generator finds far fewer functions than expected, check the `prefix` spe
| `[library] prefix` | required | only functions whose name starts with it are bound |
| `[library] opaque_handles` | required (may be `[]`) | struct tags never emitted as derived types (handles) |
| `[library] own_headers` | `[name]` | header-path markers of the files whose enums, structs and macros are this library's |
| `[library] uses` | `[]` | modules `use`d whole, so the module re-exports them; their headers' enums, constants and types are not redeclared (hipfftxt: `["hipfft"]`, as CUDA Fortran's cufftXt includes cufft) |
| `[library] function_prefixes` | `[]` | when set, the functions bound are those starting with one of them, whatever header declares them (hipfftxt: `["hipfftXt", "hipfftMp"]`) |
| `[library] skip_function_prefixes` | `[]` | functions starting with one of them are not bound: another library binds them (hipfft: `["hipfftXt", "hipfftMp"]`) |
| `[library] reexport_enums` | `[]` | sibling libraries whose enums are `use`d from the sibling module instead of copied (rocSOLVER: `["rocblas"]`) |
| `[library] keep_structs` | `[]` | HIP vector types to emit anyway, when a function takes one by value (rocRAND: `["uint4"]`) |
| `[library] pointer_mode` | `false` | the handle has a pointer mode: a `const` pointer named `alpha` or `beta` is `scalar-pointer-mode` (BLAS, SPARSE) |
Expand Down Expand Up @@ -498,6 +502,7 @@ The version is in `src/Project.toml` and follows semantic versioning; releases a
| hipSOLVER | `hipsolver` |
| hipSPARSE | `hipsparse` |
| hipFFT | `hipfft` |
| hipFFT Xt and Mp (`hipfftXt*`, `hipfftMp*`) | `hipfftxt` (uses and re-exports `hipfft`) |
| hipFFTW | `hipfftw` |
| hipRAND | `hiprand` |

Expand Down
3 changes: 2 additions & 1 deletion src/common/assemble.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ function gen_files(output_dir::String, cfg::Dict, funcs::Vector{FuncInfo},
aliases::Dict{String,String} = Dict{String,String}(),
sibling_enum_mod::Dict{String,String} = Dict{String,String}(),
sibling_enums::Vector{EnumInfo} = EnumInfo[],
uses::Vector{String} = String[],
extra_generics::Union{Nothing,Function} = nothing,
extra_wrappers::Union{Nothing,Function} = nothing,
extra_procs::Dict{String,Tuple{Vector{String},Vector{String}}} =
Expand Down Expand Up @@ -74,7 +75,7 @@ function gen_files(output_dir::String, cfg::Dict, funcs::Vector{FuncInfo},
# only withholds their _typed form.
devonly = md.devonly,
string_args = get(cfg["library"], "string_args", false),
own_consts, sibling_enum_mod,
own_consts, sibling_enum_mod, uses,
bind_names = copy(BIND_NAMES[]), extra_procs)

# Must precede all emission.
Expand Down
16 changes: 14 additions & 2 deletions src/common/clang_args.jl
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,15 @@ function parse_library(dir::AbstractString, include_dir::AbstractString;
root = Clang.getTranslationUnitCursor(tu)

prefix = cfg["library"]["prefix"]
# The functions go by name, whatever header declares them (hipfft.h declares
# hipfftXtSetWorkArea, hipfftxt's): only those of function_prefixes when set,
# less those of skip_function_prefixes, which another library binds.
fpre = strlist(get(cfg["library"], "function_prefixes", String[]))
skip = strlist(get(cfg["library"], "skip_function_prefixes", String[]))
funcs = GC.@preserve ctx tu collect_funcs(root, prefix)
starts(f, ps) = any(p -> startswith(f.name, p), ps)
isempty(fpre) || filter!(f -> starts(f, fpre), funcs)
filter!(f -> !starts(f, skip), funcs)
@info "Found $(length(funcs)) $(prefix)* functions"
declared = [f.name for f in funcs]
funcfilter === nothing || (funcs = funcfilter(funcs))
Expand All @@ -463,6 +471,7 @@ end
# An empty TOML array parses as Vector{Any}, which the typed keywords reject.
strlist(x) = collect(String, x)
own_headers_of(cfg::Dict) = strlist(get(cfg["library"], "own_headers", [cfg["general"]["name"]]))
uses_of(cfg::Dict) = strlist(get(cfg["library"], "uses", String[]))

"""
library_funcs(dir; include_dir, headers, argshook, funcfilter, gen_kwargs...) -> (cfg, funcs, parsed)
Expand Down Expand Up @@ -527,9 +536,12 @@ function run_library_generator(dir::AbstractString;
@info "Found $(length(macros)) #define constants"

# Each sibling constant, mapped to the module defining it, for the `use` lines.
# uses: modules `use`d whole, and so re-exported (hipfftxt uses hipfft); their
# enums, constants and types are not redeclared.
uses = uses_of(cfg)
sibling_enum_mod = Dict{String,String}()
sibling_enums = EnumInfo[]
for sib in reexport
for sib in vcat(reexport, uses)
sib_enums = GC.@preserve ctx tu collect_enums(root; own=[sib])
sib_macros = GC.@preserve ctx tu collect_macros(root; own=[sib])
append!(sibling_enums, sib_enums)
Expand All @@ -545,7 +557,7 @@ function run_library_generator(dir::AbstractString;
@info "Found $(length(structs)) derived types"

gen_files(output_dir, cfg, funcs, enums, structs; macros=macros, aliases=aliases,
sibling_enum_mod=sibling_enum_mod, sibling_enums=sibling_enums, gen_kwargs...)
sibling_enum_mod=sibling_enum_mod, sibling_enums=sibling_enums, uses=uses, gen_kwargs...)
end

# BLAS Set/Get Vector/Matrix families: drop them from the bind(C) path and emit
Expand Down
10 changes: 6 additions & 4 deletions src/common/config.jl
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,11 @@ neutralize_funcs(funcs::Vector{FuncInfo}, meta::LibMeta) = [
# One module-level `use <sib>, only: ...` per sibling, naming the union of what
# this library borrows. A bare `use rocblas` makes every interface's IMPORT copy
# all of rocBLAS (>5.9 GB on rocsolver); a per-routine `use, only` makes gfortran
# re-read rocblas.mod thousands of times.
# re-read rocblas.mod thousands of times. The `uses` modules come first, bare, so
# that the module re-exports them.
function sibling_module_uses(funcs::Vector{FuncInfo}, libname::String, meta::LibMeta;
extra::Vector{String} = String[]) :: Vector{String}
isempty(meta.sibling_enum_mod) && return String[]
isempty(meta.sibling_enum_mod) && return copy(meta.uses)
bymod = Dict{String,Set{String}}()
for f in funcs, u in sibling_use_onlys(f, libname, meta)
mod, names = split(u, ", only: "; limit = 2)
Expand All @@ -121,8 +122,9 @@ function sibling_module_uses(funcs::Vector{FuncInfo}, libname::String, meta::Lib
(c in meta.own_consts || !haskey(meta.sibling_enum_mod, c)) && continue
push!(get!(bymod, meta.sibling_enum_mod[c], Set{String}()), c)
end
return [ "$(mod), only: " * join(sort(collect(names)), ", ")
for (mod, names) in sort(collect(bymod); by = first) ]
return vcat(meta.uses,
[ "$(mod), only: " * join(sort(collect(names)), ", ")
for (mod, names) in sort(collect(bymod); by = first) if !(mod in meta.uses) ])
end

function sibling_use_onlys(f::FuncInfo, libname::String, meta::LibMeta) :: Vector{String}
Expand Down
11 changes: 6 additions & 5 deletions src/common/header_sources.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@
# modules from its own checkout:
#
# ROCM_SYSTEMS ROCM_LIBRARIES generated headers
# - - all 13 the installed include dir
# - - all 14 the installed include dir
# set - hip, roctx the rocm-systems checkout
# - set the math libraries the rocm-libraries checkout; HIP
# from the installed include dir
# set set all 13 the two checkouts
# set set all 14 the two checkouts
#
# The installed include dir is a positional argument, read only when ROCM_SYSTEMS
# is unset: with it, HIP comes from the checkout.
Expand All @@ -47,6 +47,7 @@ const LIBS = [
"hipsolver",
"rocfft",
"hipfft",
"hipfftxt",
"hipfftw",
"rocrand",
"hiprand",
Expand All @@ -56,8 +57,8 @@ const LIBS = [
const DEFAULT_INCLUDE = "/opt/rocm/include"

# Header subdirectory (installed) / project (checkout) of a library.
proj_of(lib) = lib == "hipfftw" ? "hipfft" :
lib == "roctx" ? "rocprofiler-sdk-roctx" : lib
proj_of(lib) = lib in ("hipfftxt", "hipfftw") ? "hipfft" :
lib == "roctx" ? "rocprofiler-sdk-roctx" : lib

# Upstream repository each module is committed to, in its bindings/fortran/ tree.
repo_of(lib) = lib in ("hip", "roctx") ? "rocm-systems" : "rocm-libraries"
Expand Down Expand Up @@ -197,7 +198,7 @@ end
# A library's metadata file (metadata/<lib>.toml): the checkout's
# <fortran_dir>/metadata/ when it exists, else the generator's own
# src/<lib>/metadata/. The file names carry the library name, so the modules sharing
# a binding directory (hip and roctx, hipfft and hipfftw) share one metadata/.
# a binding directory (hip and roctx; hipfft, hipfftxt and hipfftw) share one metadata/.
function lib_metadata(s::HeaderSources, lib)
if from_checkouts(s)
d = joinpath(fortran_dir(s, lib), "metadata")
Expand Down
2 changes: 2 additions & 0 deletions src/common/metadata.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ Base.@kwdef struct LibMeta
own_consts::Set{String} = Set{String}()
"Foreign enum constant => sibling module that defines it (rocSOLVER uses rocBLAS kinds)."
sibling_enum_mod::Dict{String,String} = Dict{String,String}()
"`[library] uses`: modules `use`d whole, so everything they define is re-exported."
uses::Vector{String} = String[]
"Fortran routine name => C symbol for `bind(C, name=...)`, when they differ."
bind_names::Dict{String,String} = Dict{String,String}()
"Generic => module procedures a library emits itself, (per-rank, assumed-rank)
Expand Down
8 changes: 7 additions & 1 deletion src/explain_metadata.jl
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@ end
function explain(io, name, s::HeaderSources, parse)
# The library binding `name`: the longest prefix that matches (hipfft before hip).
owners = [lib for lib in LIBS if !isempty(lib_prefix(lib)) && startswith(name, lib_prefix(lib))]
owner = isempty(owners) ? "" : owners[argmax(length.(lib_prefix.(owners)))]
isempty(owners) || (owners = filter(l -> length(lib_prefix(l)) == maximum(length ∘ lib_prefix, owners), owners))
# Libraries sharing a prefix (hipfft, hipfftxt): the one whose headers declare it,
# else the one whose metadata names it.
declares(lib) = (p = parse(lib); !(p isa AbstractString) && any(f -> f.name == name, first(p).funcs))
named(lib) = applying_table(load_metadata(joinpath(lib_metadata(s, lib), "$(lib).toml")), name) !== nothing
owner = isempty(owners) ? "" : length(owners) == 1 ? only(owners) :
something(findfirst(declares, owners), findfirst(named, owners), 1) |> i -> owners[i]
found = false
for lib in LIBS
path = joinpath(lib_metadata(s, lib), "$(lib).toml")
Expand Down
15 changes: 4 additions & 11 deletions src/hipfft/generator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
#
###############################################################################

# Fortran bindings for hipFFT.
# Fortran bindings for hipFFT (hipfft/hipfft.h). hipfftXt.h and hipfftMp.h are
# the hipfftxt module's (src/hipfftxt).
#
# Usage (from the repo root):
# julia --project=src src/hipfft/generator.jl [include_dir] [output_dir]
Expand All @@ -34,16 +35,8 @@ include("../common/fortran_gen.jl")
# (explain_metadata.jl).
function main(run = run_library_generator)
run(@__DIR__;
# hipfftMp.h uses hipfftXtSubFormat without including hipfftXt.h, and only one
# translation unit is walked: force-include hipfftXt.h ahead of it.
# hipfftXt.h includes <cstddef>, a C++ header: src/hipfft/cstddef stands in.
argshook = function (args, include_dir, hdrs)
xt = joinpath(dirname(hdrs[1]), "hipfftXt.h")
isfile(xt) || error("Header not found: $xt")
push!(args, "-I$(dirname(hdrs[1]))")
push!(args, "-I$(@__DIR__)")
push!(args, "-include", xt)
end)
# hipfft.h includes <cstddef>, a C++ header: src/hipfft/cstddef stands in.
argshook = (args, include_dir, hdrs) -> push!(args, "-I$(dirname(hdrs[1]))", "-I$(@__DIR__)"))
end

if abspath(PROGRAM_FILE) == @__FILE__
Expand Down
8 changes: 4 additions & 4 deletions src/hipfft/hipfft.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ prefix = "hipfft"
opaque_handles = [
"hipfftHandle",
]
# The hipfftxt module's, as cufftXt's in CUDA Fortran (hipfft.h declares
# hipfftXtSetWorkArea).
skip_function_prefixes = ["hipfftXt", "hipfftMp"]

[headers]
# hipfftMp.h (the multi-process API) includes hipfft.h; hipfftXt.h is force-included
# ahead of it (see generator.jl). hipfftMp.h's routines exist only in a hipFFT built
# with HIPFFT_MPI_ENABLE; a bind(C) interface costs nothing until called.
subdir = "hipfft"
file = "hipfftMp.h"
file = "hipfft.h"

[clang]
defines = [
Expand Down
18 changes: 0 additions & 18 deletions src/hipfft/metadata/hipfft.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,3 @@ workSize = "scalar-host"

[hipfftMakePlanMany64]
workSize = "scalar-host"

[hipfftXtGetSizeMany]
workSize = "scalar-host"

[hipfftXtMakePlanMany]
workSize = "scalar-host"

# hipfftMp.h's routines exist only in a hipFFT built with HIPFFT_MPI_ENABLE, which the
# ROCm packages are not: a wrapper that calls one leaves an undefined symbol in a
# shared libhipfort (hipfort's shared-link test). These arrays stay type(c_ptr), so
# the routine keeps its bind(C) interface alone, which costs nothing until called.
[hipfftXtSetDistribution]
input_lower = "always-c-ptr"
input_upper = "always-c-ptr"
output_lower = "always-c-ptr"
output_upper = "always-c-ptr"
input_stride = "always-c-ptr"
output_stride = "always-c-ptr"
52 changes: 52 additions & 0 deletions src/hipfftxt/generator.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
###############################################################################
# Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved.
#
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################

# Fortran bindings for hipFFT's multi-GPU and multi-process APIs (hipfft/hipfftXt.h,
# hipfft/hipfftMp.h), as CUDA Fortran's cufftXt module. It uses the hipfft module.
#
# Usage (from the repo root):
# julia --project=src src/hipfftxt/generator.jl [include_dir] [output_dir]

include("../common/fortran_gen.jl")

# `run` is run_library_generator, or library_funcs to parse without generating
# (explain_metadata.jl).
function main(run = run_library_generator)
run(@__DIR__;
# hipfftMp.h uses hipfftXtSubFormat without including hipfftXt.h, and only one
# translation unit is walked: force-include hipfftXt.h ahead of it.
# hipfftXt.h includes <cstddef>, a C++ header: src/hipfft/cstddef stands in.
argshook = function (args, include_dir, hdrs)
xt = joinpath(dirname(hdrs[1]), "hipfftXt.h")
isfile(xt) || error("Header not found: $xt")
push!(args, "-I$(dirname(hdrs[1]))")
push!(args, "-I$(joinpath(@__DIR__, "..", "hipfft"))")
push!(args, "-include", xt)
end)
end

if abspath(PROGRAM_FILE) == @__FILE__
main()
end
29 changes: 29 additions & 0 deletions src/hipfftxt/hipfftxt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[general]
name = "hipfftxt"

[library]
prefix = "hipfft"
opaque_handles = [
"hipfftHandle",
]
# The enums, constants and types of hipfftXt.h, hipfftMp.h and hiplibxt.h; hipfft.h
# is the hipfft module's, used (and so re-exported) whole.
own_headers = ["hipfft/hipfftXt", "hipfft/hipfftMp", "hipfft/hiplibxt"]
uses = ["hipfft"]
# Every hipfftXt* and hipfftMp* function, as cufftXt's in CUDA Fortran, whatever
# header declares it (hipfft.h declares hipfftXtSetWorkArea).
function_prefixes = ["hipfftXt", "hipfftMp"]

[headers]
# hipfftMp.h (the multi-process API) includes hipfft.h; hipfftXt.h is force-included
# ahead of it (see generator.jl). hipfftMp.h's routines exist only in a hipFFT built
# with HIPFFT_MPI_ENABLE; a bind(C) interface costs nothing until called.
subdir = "hipfft"
file = "hipfftMp.h"

[clang]
defines = [
"HIPFFT_EXPORT=",
"__HIP_PLATFORM_AMD__=1",
]
extra_include_dirs = []
32 changes: 32 additions & 0 deletions src/hipfftxt/metadata/hipfftxt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved.
# SPDX-License-Identifier: MIT
#
# What the hipfftXt C headers (hipfftXt.h, hipfftMp.h) cannot express, beyond the
# defaults the generator derives from them: one table per C function, and a key
# with braces names several. A _64 or _spt variant without a table takes its
# base's. See "Fixing a binding" in the generator README; `gen.sh --explain
# <function>` shows what applies.

format = 1
# scalar-host:
# workSize stays a scalar, which is right for a plan on one GPU. On a plan spread
# over several GPUs (hipfftXtSetGPUs), hipFFT writes one work size per GPU: such a
# caller passes c_loc of an array to the bind(C) form.

[hipfftXtGetSizeMany]
workSize = "scalar-host"

[hipfftXtMakePlanMany]
workSize = "scalar-host"

# hipfftMp.h's routines exist only in a hipFFT built with HIPFFT_MPI_ENABLE, which the
# ROCm packages are not: a wrapper that calls one leaves an undefined symbol in a
# shared libhipfort (hipfort's shared-link test). These arrays stay type(c_ptr), so
# the routine keeps its bind(C) interface alone, which costs nothing until called.
[hipfftXtSetDistribution]
input_lower = "always-c-ptr"
input_upper = "always-c-ptr"
output_lower = "always-c-ptr"
output_upper = "always-c-ptr"
input_stride = "always-c-ptr"
output_stride = "always-c-ptr"
Loading
Loading