diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -1181,23 +1181,28 @@ // If we are compiling for a GPU target we want to override the system headers // with ones created by the 'libc' project if present. - // FIXME: We need to find a way to make these headers compatible with the - // host environment so they can be included from offloading languages. For now - // these are only active when targeting the GPU with cross-compilation. if (!Args.hasArg(options::OPT_nostdinc) && !Args.hasArg(options::OPT_nogpuinc) && !Args.hasArg(options::OPT_nobuiltininc) && - C.getActiveOffloadKinds() == Action::OFK_None && (getToolChain().getTriple().isNVPTX() || getToolChain().getTriple().isAMDGCN())) { - // Add include/gpu-none-libc/* to our system include path. This lets us use - // GPU-specific system headers first. + // Without an offloading language we will include these headers directly. + // Offloading languages will instead only use the declarations stored in + // the resource directory at clang/lib/Headers/llvm_libc_wrappers. + if (C.getActiveOffloadKinds() == Action::OFK_None) { SmallString<128> P(llvm::sys::path::parent_path(D.InstalledDir)); llvm::sys::path::append(P, "include"); llvm::sys::path::append(P, "gpu-none-llvm"); CmdArgs.push_back("-c-isystem"); CmdArgs.push_back(Args.MakeArgString(P)); + } else { + SmallString<128> P(D.ResourceDir); + llvm::sys::path::append(P, "include"); + llvm::sys::path::append(P, "llvm_libc_wrappers"); + CmdArgs.push_back("-internal-isystem"); + CmdArgs.push_back(Args.MakeArgString(P)); + } } // If we are offloading to a target via OpenMP we need to include the diff --git a/clang/lib/Headers/CMakeLists.txt b/clang/lib/Headers/CMakeLists.txt --- a/clang/lib/Headers/CMakeLists.txt +++ b/clang/lib/Headers/CMakeLists.txt @@ -298,6 +298,13 @@ openmp_wrappers/new ) +set(llvm_libc_wrapper_files + llvm_libc_wrappers/stdio.h + llvm_libc_wrappers/stdlib.h + llvm_libc_wrappers/string.h + llvm_libc_wrappers/ctype.h +) + include(GetClangResourceDir) get_clang_resource_dir(output_dir PREFIX ${LLVM_LIBRARY_OUTPUT_INTDIR}/.. SUBDIR include) set(out_files) @@ -333,7 +340,8 @@ # Copy header files from the source directory to the build directory foreach( f ${files} ${cuda_wrapper_files} ${cuda_wrapper_bits_files} - ${ppc_wrapper_files} ${openmp_wrapper_files} ${hlsl_files}) + ${ppc_wrapper_files} ${openmp_wrapper_files} ${hlsl_files} + ${llvm_libc_wrapper_files}) copy_header_to_output_dir(${CMAKE_CURRENT_SOURCE_DIR} ${f}) endforeach( f ) @@ -427,6 +435,7 @@ "x86-resource-headers" "opencl-resource-headers" "openmp-resource-headers" + "llvm-libc-resource-headers" "windows-resource-headers" "utility-resource-headers") @@ -453,6 +462,7 @@ # Other header groupings add_header_target("hlsl-resource-headers" ${hlsl_files}) add_header_target("opencl-resource-headers" ${opencl_files}) +add_header_target("llvm-libc-resource-headers" ${llvm_libc_wrapper_files}) add_header_target("openmp-resource-headers" ${openmp_wrapper_files}) add_header_target("windows-resource-headers" ${windows_only_files}) add_header_target("utility-resource-headers" ${utility_files}) @@ -481,6 +491,11 @@ DESTINATION ${header_install_dir}/ppc_wrappers COMPONENT clang-resource-headers) +install( + FILES ${llvm_libc_wrapper_files} + DESTINATION ${header_install_dir}/llvm_libc_wrappers + COMPONENT clang-resource-headers) + install( FILES ${openmp_wrapper_files} DESTINATION ${header_install_dir}/openmp_wrappers @@ -636,6 +651,12 @@ EXCLUDE_FROM_ALL COMPONENT openmp-resource-headers) +install( + FILES ${openmp_wrapper_files} + DESTINATION ${header_install_dir}/openmp_wrappers + EXCLUDE_FROM_ALL + COMPONENT openmp-resource-headers) + install( FILES ${utility_files} DESTINATION ${header_install_dir} diff --git a/clang/lib/Headers/llvm_libc_wrappers/ctype.h b/clang/lib/Headers/llvm_libc_wrappers/ctype.h new file mode 100644 --- /dev/null +++ b/clang/lib/Headers/llvm_libc_wrappers/ctype.h @@ -0,0 +1,85 @@ +//===-- Wrapper for C standard ctype.h declarations on the GPU ------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef __CLANG_LLVM_LIBC_WRAPPERS_CTYPE_H__ +#define __CLANG_LLVM_LIBC_WRAPPERS_CTYPE_H__ + +#if !defined(_OPENMP) && !defined(__HIP__) && !defined(__CUDA__) +#error "This file is for GPU offloading compilation only" +#endif + +#include_next + +#if __has_include() + +#if defined(__HIP__) || defined(__CUDA__) +#define __LIBC_ATTRS __attribute__((device)) +#endif + +// The GNU headers like to provide these as macros, we need to undefine them so +// they do not conflict with the following definitions for the GPU. + +#pragma push_macro("isalnum") +#pragma push_macro("isalpha") +#pragma push_macro("isblank") +#pragma push_macro("iscntrl") +#pragma push_macro("isdigit") +#pragma push_macro("isgraph") +#pragma push_macro("islower") +#pragma push_macro("isprint") +#pragma push_macro("ispunct") +#pragma push_macro("isspace") +#pragma push_macro("isupper") +#pragma push_macro("isxdigit") +#pragma push_macro("tolower") +#pragma push_macro("toupper") + +#undef isalnum +#undef isalpha +#undef iscntrl +#undef isdigit +#undef islower +#undef isgraph +#undef isprint +#undef ispunct +#undef isspace +#undef isupper +#undef isblank +#undef isxdigit +#undef tolower +#undef toupper + +#pragma omp begin declare target + +#include + +#pragma omp end declare target + +// Restore the original macros when compiling on the host. +#if !defined(__NVPTX__) && !defined(__AMDGPU__) +#pragma pop_macro("isalnum") +#pragma pop_macro("isalpha") +#pragma pop_macro("isblank") +#pragma pop_macro("iscntrl") +#pragma pop_macro("isdigit") +#pragma pop_macro("isgraph") +#pragma pop_macro("islower") +#pragma pop_macro("isprint") +#pragma pop_macro("ispunct") +#pragma pop_macro("isspace") +#pragma pop_macro("isupper") +#pragma pop_macro("isxdigit") +#pragma pop_macro("tolower") +#pragma pop_macro("toupper") +#endif + +#undef __LIBC_ATTRS + +#endif + +#endif // __CLANG_LLVM_LIBC_WRAPPERS_CTYPE_H__ diff --git a/clang/lib/Headers/llvm_libc_wrappers/llvm-libc-decls/README.txt b/clang/lib/Headers/llvm_libc_wrappers/llvm-libc-decls/README.txt new file mode 100644 --- /dev/null +++ b/clang/lib/Headers/llvm_libc_wrappers/llvm-libc-decls/README.txt @@ -0,0 +1,6 @@ +LLVM libc declarations +====================== + +This directory will be filled by the `libc` project with declarations that are +availible on the device. Each declaration will use the `__LIBC_ATTRS` attribute +to control emission on the device side. diff --git a/clang/lib/Headers/llvm_libc_wrappers/stdio.h b/clang/lib/Headers/llvm_libc_wrappers/stdio.h new file mode 100644 --- /dev/null +++ b/clang/lib/Headers/llvm_libc_wrappers/stdio.h @@ -0,0 +1,34 @@ +//===-- Wrapper for C standard stdio.h declarations on the GPU ------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef __CLANG_LLVM_LIBC_WRAPPERS_STDIO_H__ +#define __CLANG_LLVM_LIBC_WRAPPERS_STDIO_H__ + +#if !defined(_OPENMP) && !defined(__HIP__) && !defined(__CUDA__) +#error "This file is for GPU offloading compilation only" +#endif + +#include_next + +#if __has_include() + +#if defined(__HIP__) || defined(__CUDA__) +#define __LIBC_ATTRS __attribute__((device)) +#endif + +#pragma omp begin declare target + +#include + +#pragma omp end declare target + +#undef __LIBC_ATTRS + +#endif + +#endif // __CLANG_LLVM_LIBC_WRAPPERS_STDIO_H__ diff --git a/clang/lib/Headers/llvm_libc_wrappers/stdlib.h b/clang/lib/Headers/llvm_libc_wrappers/stdlib.h new file mode 100644 --- /dev/null +++ b/clang/lib/Headers/llvm_libc_wrappers/stdlib.h @@ -0,0 +1,42 @@ +//===-- Wrapper for C standard stdlib.h declarations on the GPU------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef __CLANG_LLVM_LIBC_WRAPPERS_STDLIB_H__ +#define __CLANG_LLVM_LIBC_WRAPPERS_STDLIB_H__ + +#if !defined(_OPENMP) && !defined(__HIP__) && !defined(__CUDA__) +#error "This file is for GPU offloading compilation only" +#endif + +#include_next + +#if __has_include() + +#if defined(__HIP__) || defined(__CUDA__) +#define __LIBC_ATTRS __attribute__((device)) +#endif + +#pragma omp begin declare target + +// The LLVM C library uses this type so we forward declare it. +typedef void (*__atexithandler_t)(void); + +// Enforce ABI compatibility with the structs used by the LLVM C library. +_Static_assert(__builtin_offsetof(div_t, quot) == 0, "ABI mismatch!"); +_Static_assert(__builtin_offsetof(ldiv_t, quot) == 0, "ABI mismatch!"); +_Static_assert(__builtin_offsetof(lldiv_t, quot) == 0, "ABI mismatch!"); + +#include + +#pragma omp end declare target + +#undef __LIBC_ATTRS + +#endif + +#endif // __CLANG_LLVM_LIBC_WRAPPERS_STDLIB_H__ diff --git a/clang/lib/Headers/llvm_libc_wrappers/string.h b/clang/lib/Headers/llvm_libc_wrappers/string.h new file mode 100644 --- /dev/null +++ b/clang/lib/Headers/llvm_libc_wrappers/string.h @@ -0,0 +1,48 @@ +//===-- Wrapper for C standard string.h declarations on the GPU------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef __CLANG_LLVM_LIBC_WRAPPERS_STRING_H__ +#define __CLANG_LLVM_LIBC_WRAPPERS_STRING_H__ + +#if !defined(_OPENMP) && !defined(__HIP__) && !defined(__CUDA__) +#error "This file is for GPU offloading compilation only" +#endif + +// The GNU headers provide non C-standard headers when in C++ mode. Manually +// undefine it here so that the definitions agree with the C standard for our +// purposes. +#ifdef __cplusplus +extern "C" { +#pragma push_macro("__cplusplus") +#undef __cplusplus +#endif + +#include_next + +#pragma pop_macro("__cplusplus") +#ifdef __cplusplus +} +#endif + +#if __has_include() + +#if defined(__HIP__) || defined(__CUDA__) +#define __LIBC_ATTRS __attribute__((device)) +#endif + +#pragma omp begin declare target + +#include + +#pragma omp end declare target + +#undef __LIBC_ATTRS + +#endif + +#endif // __CLANG_LLVM_LIBC_WRAPPERS_STRING_H__ diff --git a/clang/test/Driver/gpu-libc-headers.c b/clang/test/Driver/gpu-libc-headers.c --- a/clang/test/Driver/gpu-libc-headers.c +++ b/clang/test/Driver/gpu-libc-headers.c @@ -1,6 +1,14 @@ // REQUIRES: nvptx-registered-target // REQUIRES: amdgpu-registered-target +// RUN: %clang -### --target=x86_64-unknown-linux-gnu -fopenmp=libomp --sysroot=./ \ +// RUN: -fopenmp-targets=amdgcn-amd-amdhsa -Xopenmp-target=amdgcn-amd-amdhsa --offload-arch=gfx908 \ +// RUN: -nogpulib %s 2>&1 | FileCheck %s --check-prefix=CHECK-HEADERS +// RUN: %clang -### --target=x86_64-unknown-linux-gnu -fopenmp=libomp --sysroot=./ \ +// RUN: -fopenmp-targets=nvptx64-nvidia-cuda -Xopenmp-target=nvptx64-nvidia-cuda --offload-arch=sm_70 \ +// RUN: -nogpulib %s 2>&1 | FileCheck %s --check-prefix=CHECK-HEADERS +// CHECK-HEADERS: "-cc1"{{.*}}"-internal-isystem" "{{.*}}include{{.*}}llvm_libc_wrappers"{{.*}}"-isysroot" "./" + // RUN: %clang -### --target=amdgcn-amd-amdhsa -mcpu=gfx1030 -nogpulib \ // RUN: -nogpuinc %s 2>&1 | FileCheck %s --check-prefix=CHECK-HEADERS-DISABLED // RUN: %clang -### --target=amdgcn-amd-amdhsa -mcpu=gfx1030 -nogpulib \ diff --git a/libc/cmake/modules/LLVMLibCHeaderRules.cmake b/libc/cmake/modules/LLVMLibCHeaderRules.cmake --- a/libc/cmake/modules/LLVMLibCHeaderRules.cmake +++ b/libc/cmake/modules/LLVMLibCHeaderRules.cmake @@ -131,6 +131,23 @@ ${hdrgen_deps} ) + if(LIBC_TARGET_ARCHITECTURE_IS_GPU) + file(MAKE_DIRECTORY ${LIBC_INCLUDE_DIR}/llvm-libc-decls) + set(decl_out_file ${LIBC_INCLUDE_DIR}/llvm-libc-decls/${relative_path}) + add_custom_command( + OUTPUT ${decl_out_file} + COMMAND ${hdrgen_exe} -o ${decl_out_file} + --header ${ADD_GEN_HDR_GEN_HDR} --def ${in_file} --export-decls + ${replacement_params} -I ${LIBC_SOURCE_DIR} ${ENTRYPOINT_NAME_LIST_ARG} + ${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/api.td + + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${in_file} ${fq_data_files} ${td_includes} + ${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/api.td + ${hdrgen_deps} + ) + endif() + if(ADD_GEN_HDR_DEPENDS) get_fq_deps_list(fq_deps_list ${ADD_GEN_HDR_DEPENDS}) # Dependencies of a add_header target can only be another add_gen_header target @@ -144,13 +161,14 @@ endif() add_custom_target( ${fq_target_name} - DEPENDS ${out_file} ${fq_deps_list} + DEPENDS ${out_file} ${fq_deps_list} ${decl_out_file} ) set_target_properties( ${fq_target_name} PROPERTIES HEADER_FILE_PATH ${out_file} + DECLS_FILE_PATH ${decl_out_file} DEPS "${fq_deps_list}" ) endfunction(add_gen_header) diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -3,6 +3,11 @@ include(LLVMLibCHeaderRules) +# The GPU build wants to install files in the compiler's resource directory. +if(LIBC_TARGET_ARCHITECTURE_IS_GPU) + include(GetClangResourceDir) +endif() + add_subdirectory(llvm-libc-macros) add_subdirectory(llvm-libc-types) @@ -539,4 +544,21 @@ install(FILES ${header_file} DESTINATION ${LIBC_INSTALL_INCLUDE_DIR}/${nested_dir} COMPONENT libc-headers) + # The GPU optionally provides the supported declarations externally so + # offloading languages like CUDA and OpenMP know what is supported by libc. We + # install these in the compiler's resource directory at a preset location. + if(LIBC_TARGET_ARCHITECTURE_IS_GPU) + get_target_property(decls_file ${target} DECLS_FILE_PATH) + if(NOT decls_file) + continue() + endif() + get_clang_resource_dir(resource_dir SUBDIR include) + file(RELATIVE_PATH relative_path ${LIBC_INCLUDE_BINARY_DIR} ${decls_file}) + get_filename_component(nested_dir ${relative_path} DIRECTORY) + set(install_dir + ${CMAKE_INSTALL_PREFIX}/${resource_dir}/llvm_libc_wrappers/${nested_dir}) + install(FILES ${decls_file} + DESTINATION ${install_dir} + COMPONENT libc-headers) + endif() endforeach() diff --git a/libc/utils/HdrGen/Generator.h b/libc/utils/HdrGen/Generator.h --- a/libc/utils/HdrGen/Generator.h +++ b/libc/utils/HdrGen/Generator.h @@ -52,6 +52,7 @@ ArgMap(Map) {} void generate(llvm::raw_ostream &OS, llvm::RecordKeeper &Records); + void generateDecls(llvm::raw_ostream &OS, llvm::RecordKeeper &Records); }; } // namespace llvm_libc diff --git a/libc/utils/HdrGen/Generator.cpp b/libc/utils/HdrGen/Generator.cpp --- a/libc/utils/HdrGen/Generator.cpp +++ b/libc/utils/HdrGen/Generator.cpp @@ -10,6 +10,7 @@ #include "IncludeFileCommand.h" #include "PublicAPICommand.h" +#include "utils/LibcTableGenUtil/APIIndexer.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/MemoryBuffer.h" @@ -116,4 +117,78 @@ } } +void Generator::generateDecls(llvm::raw_ostream &OS, + llvm::RecordKeeper &Records) { + + OS << "//===-- C standard declarations for " << StdHeader << " " + << std::string(80 - (42 + StdHeader.size()), '-') << "===//\n" + << "//\n" + << "// Part of the LLVM Project, under the Apache License v2.0 with LLVM " + "Exceptions.\n" + << "// See https://llvm.org/LICENSE.txt for license information.\n" + << "// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception\n" + << "//\n" + << "//" + "===-------------------------------------------------------------------" + "---===//\n\n"; + + std::string HeaderGuard(StdHeader.size(), '\0'); + llvm::transform(StdHeader, HeaderGuard.begin(), [](const char C) -> char { + return !isalnum(C) ? '_' : llvm::toUpper(C); + }); + OS << "#ifndef __LLVM_LIBC_DECLARATIONS_" << HeaderGuard << "\n" + << "#define __LLVM_LIBC_DECLARATIONS_" << HeaderGuard << "\n\n"; + + OS << "#ifndef __LIBC_ATTRS\n" + << "#define __LIBC_ATTRS\n" + << "#endif\n\n"; + + OS << "#ifdef __cplusplus\n" + << "extern \"C\" {\n" + << "#endif\n\n"; + + APIIndexer G(StdHeader, Records); + for (auto &Name : EntrypointNameList) { + // Filter out functions not exported by this header. + if (G.FunctionSpecMap.find(Name) == G.FunctionSpecMap.end()) + continue; + + llvm::Record *FunctionSpec = G.FunctionSpecMap[Name]; + llvm::Record *RetValSpec = FunctionSpec->getValueAsDef("Return"); + llvm::Record *ReturnType = RetValSpec->getValueAsDef("ReturnType"); + + OS << G.getTypeAsString(ReturnType) << " " << Name << "("; + + auto ArgsList = FunctionSpec->getValueAsListOfDefs("Args"); + for (size_t i = 0; i < ArgsList.size(); ++i) { + llvm::Record *ArgType = ArgsList[i]->getValueAsDef("ArgType"); + OS << G.getTypeAsString(ArgType); + if (i < ArgsList.size() - 1) + OS << ", "; + } + + OS << ") __LIBC_ATTRS;\n\n"; + } + + // Make another pass over entrypoints to emit object declarations. + for (const auto &Name : EntrypointNameList) { + if (G.ObjectSpecMap.find(Name) == G.ObjectSpecMap.end()) + continue; + llvm::Record *ObjectSpec = G.ObjectSpecMap[Name]; + auto Type = ObjectSpec->getValueAsString("Type"); + OS << "extern " << Type << " " << Name << " __LIBC_ATTRS;\n"; + } + + // Emit a final newline if we emitted any object declarations. + if (llvm::any_of(EntrypointNameList, [&](const std::string &Name) { + return G.ObjectSpecMap.find(Name) != G.ObjectSpecMap.end(); + })) + OS << "\n"; + + OS << "#ifdef __cplusplus\n" + << "}\n" + << "#endif\n\n"; + OS << "#endif\n"; +} + } // namespace llvm_libc diff --git a/libc/utils/HdrGen/Main.cpp b/libc/utils/HdrGen/Main.cpp --- a/libc/utils/HdrGen/Main.cpp +++ b/libc/utils/HdrGen/Main.cpp @@ -32,6 +32,9 @@ llvm::cl::list ReplacementValues( "args", llvm::cl::desc("Command separated = pairs."), llvm::cl::value_desc("[,name=value]")); +llvm::cl::opt ExportDecls( + "export-decls", + llvm::cl::desc("Output a new header containing only the entrypoints.")); void ParseArgValuePairs(std::unordered_map &Map) { for (std::string &R : ReplacementValues) { @@ -48,7 +51,10 @@ std::unordered_map ArgMap; ParseArgValuePairs(ArgMap); Generator G(HeaderDefFile, EntrypointNamesOption, StandardHeader, ArgMap); - G.generate(OS, Records); + if (ExportDecls) + G.generateDecls(OS, Records); + else + G.generate(OS, Records); return false; }