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
98 changes: 98 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: Documentation

on:
push:
branches: ['main']
pull_request:
branches: ['main']

# Only one Pages deployment at a time; a push while one is still running
# waits for it rather than racing it, but never cancels an in-progress
# deployment (a half-published site would be worse than a delayed one).
concurrency:
group: 'pages'
cancel-in-progress: false

permissions:
contents: read

jobs:
docs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Install Doxygen
run: sudo apt-get update && sudo apt-get install -y doxygen

- name: Cache pip packages
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('doc/requirements.txt') }}
restore-keys: pip-${{ runner.os }}-

- name: Install Conan2 and Sphinx/Breathe
run: |
pip install conan
pip install -r doc/requirements.txt

- name: Cache Conan dependencies
id: conan-cache
uses: actions/cache@v3
with:
path: ~/.conan2
key: conan2-${{ runner.os }}-docs-${{ hashFiles('**/conanfile.*') }}
restore-keys: conan2-${{ runner.os }}-docs-

- name: Detect default Conan profile
if: steps.conan-cache.outputs.cache-hit != 'true'
run: conan profile detect

- name: Install Conan Dependencies
run: conan install --build=missing -of ${{ github.workspace }}/build ${{ github.workspace }}

- name: Configure CMake
run: >
cmake -S ${{ github.workspace }}
-B ${{ github.workspace }}/build
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/build/build/Release/generators/conan_toolchain.cmake
-DWEAVER_BUILD_DOCS=ON

# Sphinx runs with -W (warnings-as-errors, set in the `docs` CMake
# target): a literalinclude tag that no longer exists, or a
# weaver-asm symbol that got inlined away/renamed, fails the build
# here instead of silently shipping a blank/stale code block. objdump
# (for the weaver-asm assembly listings) and Python3 both ship with
# ubuntu-latest already, no separate install needed.
- name: Build documentation
run: cmake --build ${{ github.workspace }}/build --target docs

# Runs on every push and PR (see above), but the artifact is only ever
# deployed by the `deploy` job below, which is gated to pushes on
# `main` -- so a PR still gets full build validation without
# publishing whatever's in the PR branch.
- name: Upload documentation artifact
uses: actions/upload-pages-artifact@v3
with:
path: ${{ github.workspace }}/build/sphinx

deploy:
needs: docs
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest

permissions:
pages: write
id-token: write

environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}

steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
151 changes: 151 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,40 @@ if("^${CMAKE_SOURCE_DIR}$" STREQUAL "^${PROJECT_SOURCE_DIR}$")
)
weaver_target_sources(weaver_tests "src" ${WEAVER_TEST_SRC})

# Documentation snippets double as tests: every //! [tag] block in
# doc/snippets is referenced verbatim from the Sphinx/Doxygen docs, so
# compiling and running it here is what keeps an example from silently
# drifting out of sync with the API it documents.
set(WEAVER_DOC_SNIPPET_SRC
doc/snippets/float8_snippets.cpp
doc/snippets/float8_asm.cpp
doc/snippets/select_snippets.cpp
doc/snippets/select_asm.cpp
doc/snippets/sqrt_snippets.cpp
doc/snippets/sqrt_asm.cpp
doc/snippets/approx_snippets.cpp
doc/snippets/approx_asm.cpp
doc/snippets/sgn_snippets.cpp
doc/snippets/sgn_asm.cpp
doc/snippets/tolerance_snippets.cpp
doc/snippets/tolerance_asm.cpp
doc/snippets/reduce_snippets.cpp
doc/snippets/reduce_asm.cpp
doc/snippets/dot_snippets.cpp
doc/snippets/dot_asm.cpp
doc/snippets/eigen_add_snippets.cpp
doc/snippets/eigen_add_asm.cpp
doc/snippets/eigen_norm_snippets.cpp
doc/snippets/eigen_norm_asm.cpp
doc/snippets/eigen_cross_snippets.cpp
doc/snippets/eigen_cross_asm.cpp
doc/snippets/eigen_select_snippets.cpp
doc/snippets/eigen_select_asm.cpp
doc/snippets/eigen_alignedbox_snippets.cpp
doc/snippets/eigen_alignedbox_asm.cpp
)
weaver_target_sources(weaver_tests "doc" ${WEAVER_DOC_SNIPPET_SRC})

set_target_properties(weaver_tests PROPERTIES
CXX_STANDARD ${WEAVER_CXX_STANDARD}
CXX_STANDARD_REQUIRED YES
Expand All @@ -161,3 +195,120 @@ if("^${CMAKE_SOURCE_DIR}$" STREQUAL "^${PROJECT_SOURCE_DIR}$")
catch_discover_tests(weaver_tests)
endif()
endif()

# Doxygen -> Breathe -> Sphinx documentation, off by default so a plain
# `cmake --build` never needs Doxygen/Python/Sphinx installed.
if(WEAVER_BUILD_DOCS)
find_package(Doxygen REQUIRED)
find_package(Python3 COMPONENTS Interpreter REQUIRED)
find_program(SPHINX_EXECUTABLE sphinx-build REQUIRED)
# objdump ships with binutils, i.e. whatever gcc/clang toolchain is
# already on hand on Linux/macOS -- WEAVER_BUILD_DOCS is scoped to that,
# not to every CI backend/compiler this project builds against. The
# extracted instructions naturally differ by compiler/OS; that's fine,
# it's showing whatever the configured toolchain actually generated.
find_program(WEAVER_OBJDUMP objdump REQUIRED)

set(WEAVER_DOXYGEN_XML_DIR "${CMAKE_CURRENT_BINARY_DIR}/doxygen/xml")
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/doc/Doxyfile.in"
"${CMAKE_CURRENT_BINARY_DIR}/Doxyfile"
@ONLY
)

# doc/snippets/*_asm.cpp functions are compiled here in isolation (one
# shared OBJECT library, no LTO, no other call sites in the program) so
# doc/tools/extract_asm.py can disassemble the actual vector instructions
# they generate for the how-to guides, rather than just asserting
# FloatWeaver is vectorized. Adding another example is just adding its
# *_asm.cpp file below and its function name to WEAVER_ASM_SYMBOLS --
# extract_asm.py searches every object in the shared library for
# whichever one actually defines each symbol.
add_library(weaver_doc_asm OBJECT
doc/snippets/float8_asm.cpp
doc/snippets/select_asm.cpp
doc/snippets/sqrt_asm.cpp
doc/snippets/approx_asm.cpp
doc/snippets/sgn_asm.cpp
doc/snippets/tolerance_asm.cpp
doc/snippets/reduce_asm.cpp
doc/snippets/dot_asm.cpp
doc/snippets/eigen_add_asm.cpp
doc/snippets/eigen_norm_asm.cpp
doc/snippets/eigen_cross_asm.cpp
doc/snippets/eigen_select_asm.cpp
doc/snippets/eigen_alignedbox_asm.cpp
)
weaver_configure_target(weaver_doc_asm)
target_link_libraries(weaver_doc_asm PRIVATE Eigen3::Eigen Catch2::Catch2WithMain)
target_include_directories(weaver_doc_asm PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")

set(WEAVER_ASM_DIR "${CMAKE_CURRENT_BINARY_DIR}/doc/asm")
set(WEAVER_ASM_SYMBOLS
add_float8 select_max
sqrt_float8 rcp_float8 rsqrt_float8 sgn_float8
equal_tol_float8 any_greater_float8 dot_float8
add_vec3_float8 squared_norm_vec3_float8 cross_vec3_float8
select_vec3_float8 merge_box2_float8
)

# Where each symbol's arguments arrive, System V AMD64 (WEAVER_BUILD_DOCS
# only ever runs gcc/clang on Linux/macOS, so this is the only calling
# convention in play). Not derived automatically -- see extract_asm.py's
# --params for why -- so double-check this against the real disassembly
# whenever a signature here changes or a new example is added below.
# weaver::floatN/boolN arguments up to 32 bytes are trivial-for-calls and
# arrive directly in the next xmm/ymm register; Eigen::Matrix/AlignedBox
# arguments are not, so they arrive as a pointer in the next integer
# register instead, and when the *return* type is likewise non-trivial
# the caller additionally passes a hidden return-value pointer in RDI
# ahead of the real parameters.
set(WEAVER_ASM_PARAMS_add_float8 "ymm0 = a, ymm1 = b")
set(WEAVER_ASM_PARAMS_select_max "ymm0 = a, ymm1 = b")
set(WEAVER_ASM_PARAMS_sqrt_float8 "ymm0 = a")
set(WEAVER_ASM_PARAMS_rcp_float8 "ymm0 = a")
set(WEAVER_ASM_PARAMS_rsqrt_float8 "ymm0 = a")
set(WEAVER_ASM_PARAMS_sgn_float8 "ymm0 = a")
set(WEAVER_ASM_PARAMS_equal_tol_float8 "ymm0 = a, ymm1 = b, ymm2 = tol")
set(WEAVER_ASM_PARAMS_any_greater_float8 "ymm0 = a, ymm1 = b")
set(WEAVER_ASM_PARAMS_dot_float8 "ymm0 = a, ymm1 = b")
set(WEAVER_ASM_PARAMS_add_vec3_float8 "rdi = return-value pointer, rsi = &a, rdx = &b")
set(WEAVER_ASM_PARAMS_squared_norm_vec3_float8 "rdi = &v")
set(WEAVER_ASM_PARAMS_cross_vec3_float8 "rdi = return-value pointer, rsi = &a, rdx = &b")
set(WEAVER_ASM_PARAMS_select_vec3_float8 "ymm0 = mask, rdi = return-value pointer, rsi = &a, rdx = &b")
set(WEAVER_ASM_PARAMS_merge_box2_float8 "rdi = return-value pointer, rsi = &a, rdx = &b")

set(WEAVER_ASM_OUTPUTS "")
foreach(sym IN LISTS WEAVER_ASM_SYMBOLS)
set(out "${WEAVER_ASM_DIR}/${sym}.txt")
add_custom_command(
OUTPUT "${out}"
COMMAND "${CMAKE_COMMAND}" -E make_directory "${WEAVER_ASM_DIR}"
COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/doc/tools/extract_asm.py"
--objdump "${WEAVER_OBJDUMP}"
--object "$<TARGET_OBJECTS:weaver_doc_asm>"
--symbol "${sym}"
--params "${WEAVER_ASM_PARAMS_${sym}}"
--output "${out}"
DEPENDS weaver_doc_asm "$<TARGET_OBJECTS:weaver_doc_asm>"
COMMENT "Extracting assembly for ${sym}"
VERBATIM
)
list(APPEND WEAVER_ASM_OUTPUTS "${out}")
endforeach()
add_custom_target(weaver_doc_asm_extract DEPENDS ${WEAVER_ASM_OUTPUTS})

add_custom_target(docs
COMMAND "${DOXYGEN_EXECUTABLE}" "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile"
COMMAND "${CMAKE_COMMAND}" -E env
"WEAVER_DOXYGEN_XML_DIR=${WEAVER_DOXYGEN_XML_DIR}"
"WEAVER_ASM_DIR=${WEAVER_ASM_DIR}"
"${SPHINX_EXECUTABLE}" -b html -W
"${CMAKE_CURRENT_SOURCE_DIR}/doc/sphinx"
"${CMAKE_CURRENT_BINARY_DIR}/sphinx"
DEPENDS weaver_doc_asm_extract
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Building Doxygen XML and Sphinx HTML documentation"
VERBATIM
)
endif()
2 changes: 2 additions & 0 deletions cmake/config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ set(WEAVER_VECTORIZE "SSE 4.2" CACHE STRING "Vectorization instruction set")
set_property(CACHE WEAVER_VECTORIZE PROPERTY STRINGS "Generic" "SSE 4.2" "AVX" "AVX 2" "AVX 512" "NEON")
message(STATUS "Compiling for ${WEAVER_VECTORIZE}")

option(WEAVER_BUILD_DOCS "Build the Doxygen/Sphinx documentation ('docs' target)" OFF)

# Configure the compiler options a target
function(weaver_configure_target tgt)
message(STATUS "Configure target: ${tgt}")
Expand Down
29 changes: 29 additions & 0 deletions doc/Doxyfile.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Minimal Doxygen config for FloatWeaver.
#
# This only lists the settings that differ from Doxygen's defaults -- run
# `doxygen -g -` to see the (very long) full set if you need to tweak
# something not covered here.
#
# Configured by CMake (see the WEAVER_BUILD_DOCS option in
# cmake/config.cmake) and consumed by Sphinx/Breathe, not viewed directly:
# GENERATE_HTML is off because Sphinx renders the final site, this step only
# produces the XML Breathe reads.

DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = FloatWeaver
PROJECT_NUMBER = @PROJECT_VERSION@
PROJECT_BRIEF = "CUDA-style SIMD vector types for the CPU"

OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@/doxygen
INPUT = @PROJECT_SOURCE_DIR@/include/weaver
STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@/include

RECURSIVE = NO
EXTRACT_ALL = NO
WARN_IF_UNDOCUMENTED = NO
QUIET = YES

GENERATE_HTML = NO
GENERATE_LATEX = NO
GENERATE_XML = YES
XML_OUTPUT = xml
2 changes: 2 additions & 0 deletions doc/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
sphinx==7.4.7
breathe==4.35.0
52 changes: 52 additions & 0 deletions doc/snippets/approx_asm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2023 Basil Fierz
//
// Use of this source code is governed by an MIT - style
// license that can be found in the LICENSE file or at
// https ://opensource.org/licenses/MIT.

// C++ standard library
#include <cmath>

// Catch2
#include <catch2/catch_all.hpp>

// FloatWeaver
#include <weaver/vector_types.h>

// See doc/snippets/float8_asm.cpp for why these functions take their
// operands as parameters rather than literals.

//! [Approximate reciprocal float8 asm]
weaver::float8 rcp_float8(weaver::float8 a)
{
return weaver::rcp(a);
}
//! [Approximate reciprocal float8 asm]

//! [Approximate reciprocal square root float8 asm]
weaver::float8 rsqrt_float8(weaver::float8 a)
{
return weaver::rsqrt(a);
}
//! [Approximate reciprocal square root float8 asm]

TEST_CASE("Approximate reciprocal float8 (asm demo)", "[doc]")
{
weaver::float8 a{ 2.0f, 4.0f, 8.0f, 5.0f, 1.0f, 10.0f, 16.0f, 20.0f };

weaver::float8 r = rcp_float8(a);
for (int i = 0; i < 8; ++i)
REQUIRE(std::fabs(r[i] - 1.0f / a[i]) < 1e-2f * (1.0f / a[i]));
}

TEST_CASE("Approximate reciprocal square root float8 (asm demo)", "[doc]")
{
weaver::float8 a{ 4.0f, 9.0f, 16.0f, 25.0f, 1.0f, 100.0f, 64.0f, 36.0f };

weaver::float8 r = rsqrt_float8(a);
for (int i = 0; i < 8; ++i)
{
float exact = 1.0f / std::sqrt(a[i]);
REQUIRE(std::fabs(r[i] - exact) < 1e-2f * exact);
}
}
Loading
Loading