[Offload] Add Offload API Sphinx documentation (#147323)

* Add spec generation to offload-tblgen tool
* This patch adds generation of Sphinx compatible reStructuedText
utilizing the C domain to document the Offload API directly from the
spec definition `.td` files.
* Add Sphinx HTML documentation target
* Introduces the `docs-offload-html` target when CMake is configured
with `LLVM_ENABLE_SPHINX=ON` and `SPHINX_OUTPUT_HTML=ON`. Utilized
`offload-tblgen -gen-spen` to generate Offload API specification docs.
This commit is contained in:
Kenneth Benzie (Benie) 2025-07-10 11:50:51 +01:00 committed by GitHub
parent 56a8655f4a
commit cea33304c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 305 additions and 16 deletions

View File

@ -23,6 +23,8 @@ elseif(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
return()
endif()
set(OFFLOAD_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
if(OPENMP_STANDALONE_BUILD)
set(OFFLOAD_LIBDIR_SUFFIX "" CACHE STRING
"Suffix of lib installation directory, e.g. 64 => lib64")
@ -371,6 +373,7 @@ add_subdirectory(tools/offload-tblgen)
add_subdirectory(plugins-nextgen)
add_subdirectory(DeviceRTL)
add_subdirectory(tools)
add_subdirectory(docs)
# Build target agnostic offloading library.
add_subdirectory(libomptarget)

3
offload/docs/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
_static/
_themes/
offload-api.rst

View File

@ -0,0 +1,37 @@
if(LLVM_ENABLE_SPHINX)
include(AddSphinxTarget)
if(SPHINX_FOUND AND SPHINX_OUTPUT_HTML)
# Generate offload-api.rst from OffloadAPI.td
set(LLVM_TARGET_DEFINITIONS
${OFFLOAD_SOURCE_DIR}/liboffload/API/OffloadAPI.td)
tablegen(OFFLOAD source/offload-api.rst -gen-doc
EXTRA_INCLUDES ${OFFLOAD_SOURCE_DIR}/liboffload/API)
add_public_tablegen_target(OffloadDocsGenerate)
# Due to Sphinx only allowing a single source direcotry and the fact we
# only generate a single file, copy offload-api.rst to the source directory
# to be included in the generated documentation.
# Additionally, copy the llvm-theme into the Sphinx source directory.
# A .gitignore file ensures the copied files will not be added to the
# repository.
add_custom_target(OffloadDocsCopy
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/source/offload-api.rst
${CMAKE_CURRENT_SOURCE_DIR}/offload-api.rst
COMMAND ${CMAKE_COMMAND} -E copy
${OFFLOAD_SOURCE_DIR}/../clang/www/favicon.ico
${CMAKE_CURRENT_SOURCE_DIR}/_static/favicon.ico
COMMAND ${CMAKE_COMMAND} -E copy
${OFFLOAD_SOURCE_DIR}/../llvm/docs/_static/llvm.css
${CMAKE_CURRENT_SOURCE_DIR}/_static/llvm.css
COMMAND ${CMAKE_COMMAND} -E copy_directory
${OFFLOAD_SOURCE_DIR}/../llvm/docs/_themes
${CMAKE_CURRENT_SOURCE_DIR}/_themes
)
add_dependencies(OffloadDocsCopy OffloadDocsGenerate)
# Generate the HTML documentation, the docs-offload-html target.
add_sphinx_target(html offload)
add_dependencies(docs-offload-html OffloadDocsCopy)
endif()
endif()

32
offload/docs/conf.py Normal file
View File

@ -0,0 +1,32 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = "Offload"
copyright = "2025, LLVM project"
author = "LLVM project"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = []
templates_path = ["_templates"]
exclude_patterns = []
# -- C domain configuration --------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#c-config
c_maximum_signature_line_length = 60
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = "llvm-theme"
html_theme_path = ["_themes"]
html_static_path = ["_static"]
html_favicon = "_static/favicon.ico"

21
offload/docs/index.rst Normal file
View File

@ -0,0 +1,21 @@
.. Offload documentation master file, created by
sphinx-quickstart on Fri Jul 4 14:59:13 2025.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Offload's documentation!
===================================
.. toctree::
:maxdepth: 2
:caption: Contents:
offload-api
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -44,21 +44,6 @@ def : Macro {
let alt_value = "";
}
def : Macro {
let name = "OL_DLLEXPORT";
let desc = "Microsoft-specific dllexport storage-class attribute";
let condition = "defined(_WIN32)";
let value = "__declspec(dllexport)";
}
def : Macro {
let name = "OL_DLLEXPORT";
let desc = "GCC-specific dllexport storage-class attribute";
let condition = "__GNUC__ >= 4";
let value = "__attribute__ ((visibility (\"default\")))";
let alt_value = "";
}
def : Handle {
let name = "ol_platform_handle_t";
let desc = "Handle of a platform instance";

View File

@ -48,7 +48,7 @@ static void ProcessHandle(const HandleRec &H, raw_ostream &OS) {
exit(1);
}
auto ImplName = H.getName().substr(0, H.getName().size() - 9) + "_impl_t";
auto ImplName = getHandleImplName(H);
OS << CommentsHeader;
OS << formatv("/// @brief {0}\n", H.getDesc());
OS << formatv("typedef struct {0} *{1};\n", ImplName, H.getName());

View File

@ -12,6 +12,7 @@ set(LLVM_LINK_COMPONENTS Support)
add_tablegen(offload-tblgen OFFLOAD
EXPORT OFFLOAD
APIGen.cpp
DocGen.cpp
EntryPointGen.cpp
MiscGen.cpp
GenCommon.hpp

View File

@ -0,0 +1,195 @@
//===- offload-tblgen/DocGen.cpp - Tablegen backend for Offload header ----===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This is a Tablegen backend that produces the contents of the Offload API
// specification. The generated reStructuredText is Sphinx compatible, see
// https://www.sphinx-doc.org/en/master/usage/domains/c.html for further
// details on the C language domain.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/TableGen/Record.h"
#include "llvm/TableGen/TableGenBackend.h"
#include "GenCommon.hpp"
#include "RecordTypes.hpp"
using namespace llvm;
using namespace offload::tblgen;
namespace {
std::string makeFunctionSignature(StringRef RetTy, StringRef Name,
ArrayRef<ParamRec> Params) {
std::string S;
raw_string_ostream OS{S};
OS << RetTy << " " << Name << "(";
for (const ParamRec &Param : Params) {
OS << Param.getType() << " " << Param.getName();
if (Param != Params.back()) {
OS << ", ";
}
}
OS << ")";
return S;
}
std::string makeDoubleBackticks(StringRef R) {
std::string S;
for (char C : R) {
if (C == '`') {
S.push_back('`');
}
S.push_back(C);
}
return S;
}
void processMacro(const MacroRec &M, raw_ostream &OS) {
OS << formatv(".. c:macro:: {0}\n\n", M.getNameWithArgs());
OS << " " << M.getDesc() << "\n\n";
}
void processTypedef(const TypedefRec &T, raw_ostream &OS) {
OS << formatv(".. c:type:: {0} {1}\n\n", T.getValue(), T.getName());
OS << " " << T.getDesc() << "\n\n";
}
void processHandle(const HandleRec &H, raw_ostream &OS) {
OS << formatv(".. c:type:: struct {0} *{1}\n\n", getHandleImplName(H),
H.getName());
OS << " " << H.getDesc() << "\n\n";
}
void processFptrTypedef(const FptrTypedefRec &F, raw_ostream &OS) {
OS << ".. c:type:: "
<< makeFunctionSignature(F.getReturn(),
StringRef{formatv("(*{0})", F.getName())},
F.getParams())
<< "\n\n";
for (const ParamRec &P : F.getParams()) {
OS << formatv(" :param {0}: {1}\n", P.getName(), P.getDesc());
}
OS << "\n";
}
void processEnum(const EnumRec &E, raw_ostream &OS) {
OS << formatv(".. c:enum:: {0}\n\n", E.getName());
OS << " " << E.getDesc() << "\n\n";
for (const EnumValueRec Etor : E.getValues()) {
OS << formatv(" .. c:enumerator:: {0}_{1}\n\n", E.getEnumValNamePrefix(),
Etor.getName());
OS << " " << Etor.getDesc() << "\n\n";
}
}
void processStruct(const StructRec &S, raw_ostream &OS) {
OS << formatv(".. c:struct:: {0}\n\n", S.getName());
OS << " " << S.getDesc() << "\n\n";
for (const StructMemberRec &M : S.getMembers()) {
OS << formatv(" .. c:member:: {0} {1}\n\n", M.getType(), M.getName());
OS << " " << M.getDesc() << "\n\n";
}
}
void processFunction(const FunctionRec &F, raw_ostream &OS) {
OS << ".. c:function:: "
<< makeFunctionSignature({formatv("{0}_result_t", PrefixLower)},
F.getName(), F.getParams())
<< "\n\n";
OS << " " << F.getDesc() << "\n\n";
for (StringRef D : F.getDetails()) {
OS << " " << D << "\n";
}
if (!F.getDetails().empty()) {
OS << "\n";
}
for (const ParamRec &P : F.getParams()) {
OS << formatv(" :param {0}: {1}\n", P.getName(), P.getDesc());
}
for (const ReturnRec &R : F.getReturns()) {
OS << formatv(" :retval {0}:\n", R.getValue());
for (StringRef C : R.getConditions()) {
OS << " * ";
if (C.starts_with("`") && C.ends_with("`")) {
OS << ":c:expr:" << C;
} else {
OS << makeDoubleBackticks(C);
}
OS << "\n";
}
}
OS << "\n";
}
} // namespace
void EmitOffloadDoc(const RecordKeeper &Records, raw_ostream &OS) {
OS << "Offload API\n";
OS << "===========\n\n";
ArrayRef<const Record *> Macros = Records.getAllDerivedDefinitions("Macro");
if (!Macros.empty()) {
OS << "Macros\n";
OS << "------\n\n";
for (const Record *M : Macros) {
processMacro(MacroRec{M}, OS);
}
}
ArrayRef<const Record *> Handles = Records.getAllDerivedDefinitions("Handle");
ArrayRef<const Record *> Typedefs =
Records.getAllDerivedDefinitions("Typedef");
ArrayRef<const Record *> FptrTypedefs =
Records.getAllDerivedDefinitions("FptrTypedef");
if (!Handles.empty() || !Typedefs.empty() || !FptrTypedefs.empty()) {
OS << "Type Definitions\n";
OS << "----------------\n\n";
for (const Record *H : Handles) {
processHandle(HandleRec{H}, OS);
}
for (const Record *T : Typedefs) {
processTypedef(TypedefRec{T}, OS);
}
for (const Record *F : FptrTypedefs) {
processFptrTypedef(FptrTypedefRec{F}, OS);
}
}
ArrayRef<const Record *> Enums = Records.getAllDerivedDefinitions("Enum");
OS << "Enums\n";
OS << "-----\n\n";
if (!Enums.empty()) {
for (const Record *E : Enums) {
processEnum(EnumRec{E}, OS);
}
}
ArrayRef<const Record *> Structs = Records.getAllDerivedDefinitions("Struct");
if (!Structs.empty()) {
OS << "Structs\n";
OS << "-------\n\n";
for (const Record *S : Structs) {
processStruct(StructRec{S}, OS);
}
}
ArrayRef<const Record *> Functions =
Records.getAllDerivedDefinitions("Function");
if (!Functions.empty()) {
OS << "Functions\n";
OS << "---------\n\n";
for (const Record *F : Functions) {
processFunction(FunctionRec{F}, OS);
}
}
}

View File

@ -65,3 +65,8 @@ MakeParamComment(const llvm::offload::tblgen::ParamRec &Param) {
(Param.isOut() ? "[out]" : ""),
(Param.isOpt() ? "[optional]" : ""), Param.getDesc());
}
inline std::string
getHandleImplName(const llvm::offload::tblgen::HandleRec &H) {
return (H.getName().substr(0, H.getName().size() - 9) + "_impl_t").str();
}

View File

@ -11,6 +11,7 @@
#include "llvm/TableGen/Record.h"
void EmitOffloadAPI(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS);
void EmitOffloadDoc(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS);
void EmitOffloadFuncNames(const llvm::RecordKeeper &Records,
llvm::raw_ostream &OS);
void EmitOffloadImplFuncDecls(const llvm::RecordKeeper &Records,

View File

@ -26,6 +26,7 @@ enum ActionType {
PrintRecords,
DumpJSON,
GenAPI,
GenDoc,
GenFuncNames,
GenImplFuncDecls,
GenEntryPoints,
@ -44,6 +45,8 @@ cl::opt<ActionType> Action(
clEnumValN(DumpJSON, "dump-json",
"Dump all records as machine-readable JSON"),
clEnumValN(GenAPI, "gen-api", "Generate Offload API header contents"),
clEnumValN(GenDoc, "gen-doc",
"Generate Offload API documentation contents"),
clEnumValN(GenFuncNames, "gen-func-names",
"Generate a list of all Offload API function names"),
clEnumValN(
@ -71,6 +74,9 @@ static bool OffloadTableGenMain(raw_ostream &OS, const RecordKeeper &Records) {
case GenAPI:
EmitOffloadAPI(Records, OS);
break;
case GenDoc:
EmitOffloadDoc(Records, OS);
break;
case GenFuncNames:
EmitOffloadFuncNames(Records, OS);
break;