Skip to content
Open
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
60 changes: 60 additions & 0 deletions cmake/dispatch-levels.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright 2026 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

##
## This is a cmake helper to split a spec provided as `a|b|c`
## into three fields `[a, b, c]`
##

include_guard(GLOBAL)

# Scoped this file via PUSH/POP so it does not affect the includer
cmake_policy(PUSH)
cmake_policy(SET CMP0007 NEW)

# Splits `spec` on '|' into `n` fields
function(svs_split_fields spec n)
string(REPLACE "|" ";" fields "${spec}")
list(LENGTH fields n_fields)
if(NOT n_fields EQUAL n)
message(FATAL_ERROR "Malformed spec '${spec}': expected ${n} '|'-separated fields, got ${n_fields}.")
endif()
math(EXPR last_index "${n} - 1")
foreach(i RANGE 0 ${last_index})
list(GET fields ${i} value)
list(GET ARGN ${i} out_var)
set(${out_var} "${value}" PARENT_SCOPE)
endforeach()
endfunction()

# e.g. svs_parse_isa_level("AVX2|haswell|avx2" level arch infix) sets
# level=AVX2, arch=haswell, infix=avx2
function(svs_parse_isa_level spec out_level out_arch out_infix)
svs_split_fields("${spec}" 3 level arch infix)
set(${out_level} "${level}" PARENT_SCOPE)
set(${out_arch} "${arch}" PARENT_SCOPE)
set(${out_infix} "${infix}" PARENT_SCOPE)
endfunction()

# e.g. svs_parse_tu_spec("avx2.cpp|AVX2|haswell|avx2" src level arch infix) sets
# src=avx2.cpp, level=AVX2, arch=haswell, infix=avx2
function(svs_parse_tu_spec spec out_src out_level out_arch out_infix)
svs_split_fields("${spec}" 4 src level arch infix)
set(${out_src} "${src}" PARENT_SCOPE)
set(${out_level} "${level}" PARENT_SCOPE)
set(${out_arch} "${arch}" PARENT_SCOPE)
set(${out_infix} "${infix}" PARENT_SCOPE)
endfunction()

cmake_policy(POP)
82 changes: 82 additions & 0 deletions cmake/dispatch-surface.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright 2026 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#####
##### The dispatch surface for the x86 distance kernels.
#####
##### This file is the single place the extent list and the ISA levels are
##### written down. Everything derived from them is generated:
#####
##### - svs/core/distance/dispatch_surface.h, which drives every `extern
##### template` and explicit instantiation, and `supported_dim_list`
##### - the object library each ISA level's translation unit is compiled into,
##### and the instruction budget it is compiled at
#####
##### Edit this file. The header is not in the source tree at all: it is written
##### into the build directory on every configure and installed from there.
#####
##### A build may point somewhere else with -DSVS_DISPATCH_SURFACE_FILE=<file>,
##### in which case only that build tree describes the overridden surface.
#####
##### Bookkeeping
#####
##### To add or remove a fixed extent:
##### Edit `SVS_SUPPORTED_DIMS`. The generated header, every `extern
##### template`, and `supported_dim_list` follow automatically.
#####
##### To add an ISA level:
##### 1. Add a row to `SVS_ISA_LEVELS`.
##### 2. Add a `SVS_TYPE_PAIRS_<enumerator>` list in
##### include/svs/multi-arch/x86/preprocessor.h, listing the element-type
##### pairs that level has kernels for.
##### 3. Add the level's translation unit at
##### include/svs/multi-arch/x86/<infix>.cpp.
##### The object library and its compile flags follow from the row here. A
##### level without a type-pair list is a compile error, not an empty
##### instantiation set.
#####
##### To add or remove an element-type pair for a level:
##### Edit that level's `SVS_TYPE_PAIRS_<enumerator>` list in
##### include/svs/multi-arch/x86/preprocessor.h. Not configured here: a type
##### pair exists because an implementation exists for it.
#####
##### To change a level's instruction budget:
##### Edit the middle field of its row in `SVS_ISA_LEVELS`, observing the
##### constraint recorded there.
#####


##
## List of supported dimensions; kernels will be available for these dimensions
## If used with dimensions not in this list, compute will be dispatched to
## svs::Dynamic, which is automatically added to the list of supported dims.
##

set(SVS_SUPPORTED_DIMS 64 96 100 128 160 200 512 768)

##
## ISA mapping
##
## All compute kernels will be compiled for the architectures below
## NONE is not added; it only serves as a fallback and it will compile with
## the flags set by the consumer.
## Format is "ENUM|arch|infix", where
## - ENUM is the AVX_AVAILABILITY value from distance_core.h
## - arch is the -march flag passed to the compiler
## - infix is a **unique** string used for object and library names

set(SVS_ISA_LEVELS
"AVX2|haswell|avx2"
"AVX512|cascadelake|avx512"
)
226 changes: 226 additions & 0 deletions cmake/generate-dispatch-surface.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
# Copyright 2026 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#####
##### Derives the dispatch surface declared in cmake/dispatch-surface.cmake.
#####
##### Defines svs_generate_dispatch_surface(), which produces:
##### <build>/generated/include/svs/core/distance/dispatch_surface.h (build tree only)
##### SVS_DISPATCH_TU_SPECS -- "<src>|<level>|<arch>|<infix>", one per ISA level
#####
##### cmake/multi-arch.cmake calls the function and receives both.
#####

include_guard(GLOBAL)

include("${CMAKE_CURRENT_LIST_DIR}/dispatch-levels.cmake")

set(SVS_DISPATCH_GEN_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}")

set(SVS_DEFAULT_DISPATCH_SURFACE_FILE "${CMAKE_CURRENT_LIST_DIR}/dispatch-surface.cmake")
set(SVS_DISPATCH_SURFACE_FILE "${SVS_DEFAULT_DISPATCH_SURFACE_FILE}"
CACHE FILEPATH
"Declaration of the ahead-of-time distance-kernel dispatch surface"
)

#####
##### Read the declaration
#####

function(svs_dispatch_read_declaration surface_file x86_src_dir
out_supported_dims out_dim_list out_dim_count out_isa_levels)
set(SVS_DISPATCH_SURFACE_FILE "${surface_file}")
set(SVS_X86_SRC_DIR "${x86_src_dir}")
include("${SVS_DISPATCH_GEN_CMAKE_DIR}/validate-dispatch-surface.cmake")
set(${out_supported_dims} "${SVS_SUPPORTED_DIMS}" PARENT_SCOPE)
set(${out_dim_list} "${SVS_DIM_LIST}" PARENT_SCOPE)
set(${out_dim_count} "${SVS_DIM_COUNT}" PARENT_SCOPE)
set(${out_isa_levels} "${SVS_ISA_LEVELS}" PARENT_SCOPE)
endfunction()

#####
##### Build the macro bodies
#####

function(svs_dispatch_build_expansions dim_list isa_levels
out_dim_loop out_target_loop out_level_loop out_level_defines)
set(dim_loop "\\\n")
foreach(dim IN LISTS dim_list)
string(APPEND dim_loop " M(${dim}) \\\n")
endforeach()
string(APPEND dim_loop " /* end */")

set(target_loop "\\\n")
set(level_loop "\\\n")
set(level_defines "")
foreach(level_spec IN LISTS isa_levels)
svs_parse_isa_level("${level_spec}" level _ _)
string(APPEND level_loop " M(${level}) \\\n")
string(APPEND level_defines "#define SVS_ISA_LEVEL_${level} 1\n")
foreach(dim IN LISTS dim_list)
string(APPEND target_loop " M(${dim}, ${level}) \\\n")
endforeach()
endforeach()
string(APPEND target_loop " /* end */")
string(APPEND level_loop " /* end */")
string(STRIP "${level_defines}" level_defines)

set(${out_dim_loop} "${dim_loop}" PARENT_SCOPE)
set(${out_target_loop} "${target_loop}" PARENT_SCOPE)
set(${out_level_loop} "${level_loop}" PARENT_SCOPE)
set(${out_level_defines} "${level_defines}" PARENT_SCOPE)
endfunction()

#####
##### Derive the translation-unit specs
#####

function(svs_dispatch_derive_tu_specs isa_levels x86_src_dir
out_tu_specs out_levels out_level_report)
set(tu_specs)
set(levels)
set(level_report)
foreach(level_spec IN LISTS isa_levels)
svs_parse_isa_level("${level_spec}" level arch infix)
list(APPEND tu_specs "${x86_src_dir}/${infix}.cpp|${level}|${arch}|${infix}")
list(APPEND levels "${level}")
list(APPEND level_report "AVX_AVAILABILITY::${level} -march=${arch} ${infix}.cpp")
endforeach()
set(${out_tu_specs} "${tu_specs}" PARENT_SCOPE)
set(${out_levels} "${levels}" PARENT_SCOPE)
set(${out_level_report} "${level_report}" PARENT_SCOPE)
endfunction()

#####
##### Emit the header
#####

# Writes the build-tree header from the template and the expansion strings above.
function(svs_dispatch_emit_header template_file dim_count dim_loop target_loop
level_loop level_defines include_dir out_header)
set(SVS_GEN_DIM_COUNT "${dim_count}")
set(SVS_GEN_DIM_LOOP "${dim_loop}")
set(SVS_GEN_TARGET_LOOP "${target_loop}")
set(SVS_GEN_LEVEL_LOOP "${level_loop}")
set(SVS_GEN_LEVEL_DEFINES "${level_defines}")
set(header "${include_dir}/svs/core/distance/dispatch_surface.h")
configure_file("${template_file}" "${header}" @ONLY)
set(${out_header} "${header}" PARENT_SCOPE)
endfunction()

#####
##### Emit the manifest
#####

function(svs_dispatch_emit_manifest supported_dims levels manifest_file)
string(REPLACE ";" " " levels_text "${levels}")
string(REPLACE ";" " " extents_text "${supported_dims}")
file(GENERATE
OUTPUT "${manifest_file}"
CONTENT "set(SVS_MANIFEST_FIXED_EXTENTS ${extents_text})
set(SVS_MANIFEST_LEVELS ${levels_text})
"
)
endfunction()

#####
##### Orchestrate
#####

# Reads the declaration, writes the build-tree header and the ctest manifest, and
# reports the surface. Returns the TU specs and the generated header path, which is
# how cmake/multi-arch.cmake receives them; nothing here escapes via a bare
# file-scope set().
function(svs_generate_dispatch_surface out_tu_specs out_header)
set(x86_src_dir "${PROJECT_SOURCE_DIR}/include/svs/multi-arch/x86")

svs_dispatch_read_declaration(
"${SVS_DISPATCH_SURFACE_FILE}" "${x86_src_dir}"
supported_dims dim_list dim_count isa_levels
)

# Re-run configure when the declaration changes, so the generated header and
# the translation units cannot go stale.
set_property(
DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
"${SVS_DISPATCH_SURFACE_FILE}"
)

svs_dispatch_build_expansions(
"${dim_list}" "${isa_levels}"
dim_loop target_loop level_loop level_defines
)
svs_dispatch_derive_tu_specs(
"${isa_levels}" "${x86_src_dir}"
tu_specs levels level_report
)

# The build always compiles against the build-tree copy, and it is placed
# ahead of the source include directory so that it wins.
set(generated_include_dir "${CMAKE_CURRENT_BINARY_DIR}/generated/include")
svs_dispatch_emit_header(
"${SVS_DISPATCH_GEN_CMAKE_DIR}/templates/dispatch_surface.h.in"
"${dim_count}" "${dim_loop}" "${target_loop}" "${level_loop}" "${level_defines}"
"${generated_include_dir}"
header
)
target_include_directories(
${SVS_LIB} BEFORE INTERFACE $<BUILD_INTERFACE:${generated_include_dir}>
)

svs_dispatch_emit_manifest(
"${supported_dims}" "${levels}"
"${CMAKE_BINARY_DIR}/dispatch_surface.manifest.cmake"
)

#####
##### Report the surface
#####

list(LENGTH isa_levels level_count)
string(REPLACE ";" " " dims_display "${supported_dims}")
message(STATUS "Dispatch surface: ${dim_count} extents x ${level_count} ISA levels")
message(STATUS " extents: ${dims_display} svs::Dynamic")
foreach(entry IN LISTS level_report)
message(STATUS " level: ${entry}")
endforeach()

# Every enumerator without a translation unit is still reachable -- the entry
# points fall back to it -- so its kernels are built by each consumer instead.
set(enum_header "${PROJECT_SOURCE_DIR}/include/svs/core/distance/distance_core.h")
if(EXISTS "${enum_header}")
file(READ "${enum_header}" enum_text)
if(enum_text MATCHES "enum class AVX_AVAILABILITY[ \t\r\n]*{([^}]*)}")
string(REPLACE "," ";" enumerators "${CMAKE_MATCH_1}")
set(undeclared)
foreach(enumerator IN LISTS enumerators)
string(STRIP "${enumerator}" enumerator)
if(enumerator AND NOT enumerator IN_LIST levels)
list(APPEND undeclared "${enumerator}")
endif()
endforeach()
if(undeclared)
string(REPLACE ";" ", " undeclared_display "${undeclared}")
message(STATUS " not in the surface: ${undeclared_display}")
message(STATUS
" dispatched to, but compiled by no translation unit, so "
"every consumer instantiates those kernels itself, at its own -march"
)
endif()
endif()
endif()

set(${out_tu_specs} "${tu_specs}" PARENT_SCOPE)
set(${out_header} "${header}" PARENT_SCOPE)
endfunction()
Loading
Loading