diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt --- a/libc/CMakeLists.txt +++ b/libc/CMakeLists.txt @@ -57,8 +57,8 @@ # The lib and test directories are added at the very end as tests # and libraries potentially draw from the components present in all # of the other directories. -add_subdirectory(lib) if(LLVM_INCLUDE_TESTS) add_subdirectory(test) add_subdirectory(fuzzing) endif() +add_subdirectory(lib) 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 @@ -91,7 +91,7 @@ ${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/api.td WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - DEPENDS ${in_file} ${fq_data_files} ${td_includes} + DEPENDS ${in_file} ${fq_data_files} ${td_includes} ${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/api.td libc-hdrgen ) @@ -103,4 +103,7 @@ ${fq_target_name} DEPENDS ${out_file} ${fq_deps_list} ) + add_dependencies( + libc-generate-headers ${fq_target_name} + ) endfunction(add_gen_header) diff --git a/libc/cmake/modules/LLVMLibCLibraryRules.cmake b/libc/cmake/modules/LLVMLibCLibraryRules.cmake --- a/libc/cmake/modules/LLVMLibCLibraryRules.cmake +++ b/libc/cmake/modules/LLVMLibCLibraryRules.cmake @@ -90,6 +90,7 @@ get_fq_deps_list(fq_deps_list ${ENTRYPOINT_LIBRARY_DEPENDS}) get_object_files_for_entrypoint_library(obj_list ${fq_deps_list}) + set(entrypoint_list "") foreach(dep IN LISTS fq_deps_list) get_target_property(dep_type ${dep} "TARGET_TYPE") if(NOT (${dep_type} STREQUAL ${ENTRYPOINT_OBJ_TARGET_TYPE})) @@ -97,7 +98,9 @@ "not an 'add_entrypoint_object' target.") endif() get_entrypoint_object_file(${dep} objfile) + get_target_property(entrypoint ${dep} "ENTRYPOINT_NAME") list(APPEND obj_list ${objfile}) + list(APPEND entrypoint_list ${entrypoint}) endforeach(dep) list(REMOVE_DUPLICATES obj_list) @@ -112,6 +115,12 @@ ALL DEPENDS ${library_file} ) + set_target_properties( + ${target_name} + PROPERTIES + LIBRARY_FILE ${library_file} + ENTRYPOINTS_LIST "${entrypoint_list}" + ) endfunction(add_entrypoint_library) # Rule to build a shared library of redirector objects. @@ -200,3 +209,135 @@ "DEPS" "${fq_deps_list}" ) endfunction(add_header_library) + +# Rule to add a libc public integration test. +# Usage +# add_libc_public_integration_test( +# +# LIBS +# ) +function(add_libc_public_integration_test target_name) + if(NOT LLVM_INCLUDE_TESTS) + return() + endif() + + cmake_parse_arguments( + "LIBC_INTTEST" + "" # No option arguments + "" # No Single value arguments + "LIBS" # Multi value arguments + ${ARGN} + ) + + set(public_test ${CMAKE_CURRENT_BINARY_DIR}/${target_name}.cpp) + + set(entrypoints_list "") + foreach(lib IN LISTS LIBC_INTTEST_LIBS) + get_target_property(entrypoints ${lib} "ENTRYPOINTS_LIST") + list(APPEND entrypoints_list ${entrypoints}) + endforeach() + # TODO: Remove these when they are added to the TableGen. + list(REMOVE_ITEM entrypoints_list "__assert_fail" "__errno_location") + list(TRANSFORM entrypoints_list PREPEND "-e=") + + # Generate integration test souce code. + add_custom_command( + OUTPUT ${public_test} + COMMAND $ -o ${public_test} + ${entrypoints_list} + -I ${LIBC_SOURCE_DIR} + ${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/api.td + + DEPENDS ${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/api.td + libc-prototype-testgen libc-generate-headers + ) + + add_executable( + ${target_name} + EXCLUDE_FROM_ALL + ${public_test} + ) + # Blank out default include directories to prevent accidentally including + # system headers or our own internal headers. + set_target_properties( + ${target_name} + PROPERTIES + INCLUDE_DIRECTORIES "" + ) + # Only include we need is the include for cpp::IsSame and our generated + # public headers. + target_include_directories( + ${target_name} BEFORE + PRIVATE + "${LIBC_SOURCE_DIR}/utils/CPP" + ) + target_include_directories( + ${target_name} SYSTEM BEFORE + PRIVATE + "${LIBC_BUILD_DIR}/include" + ) + get_compiler_include_dir() + target_compile_options( + ${target_name} + # TODO: Uncomment -nostdinc when we no longer depend on linux headers. + PRIVATE #"-nostdinc" + # There is currently a bug that prevents us from adding the compiler's + # include directory using target_include_directories so we must add it in + # the compiler flags ourselves. + # See: https://gitlab.kitware.com/cmake/cmake/issues/19227 + "-isystem" ${COMPILER_INCLUDE_PATH} + ) + target_link_options( + ${target_name} + PRIVATE "-nostdlib" + ) + set(library_files) + foreach(library_name IN LISTS LIBC_INTTEST_LIBS) + get_target_property(library_file ${library_name} "LIBRARY_FILE") + list(APPEND library_files ${library_file}) + endforeach() + + target_link_libraries(${target_name} + PRIVATE + ${library_files} + ) + add_dependencies(check-libc ${target_name}) +endfunction(add_libc_public_integration_test) + +# This function is needed because we want to compile with -nostdinc but still +# have access to free-standing heaers provided by the compiler such as +# stddef.h, stdatomic.h etc. +# +# We can't use -ibuiltininc because it is only available on clang 11. +function(get_compiler_include_dir) + if(COMPILER_INCLUDE_PATH) + return() + endif() + + # Use --print-resource-dir to find the compiler include path if this flag + # is supported by the compiler. + execute_process( + OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND ${CMAKE_CXX_COMPILER} --print-resource-dir + RESULT_VARIABLE COMMAND_RETURN_CODE + OUTPUT_VARIABLE COMPILER_RESOURCE_DIR + ) + if(COMMAND_RETURN_CODE EQUAL 0) + set(COMPILER_INCLUDE_PATH + "${COMPILER_RESOURCE_DIR}/include" CACHE PATH "path to compiler include dir" + ) + message(STATUS "Set COMPILER_INCLUDE_PATH to + ${COMPILER_INCLUDE_PATH} using --print-resource-dir") + return() + endif() + + # Fallback if --print-resource-dir is not supported by the compiler. + # TODO, see if there is a better way to do this. + find_path(stddef_file_loc stddef.h) + set(COMPILER_INCLUDE_PATH + "${stddef_file_loc}" CACHE PATH "path to compiler include dir" + ) + message(STATUS "Set COMPILER_INCLUDE_PATH to + ${COMPILER_INCLUDE_PATH} using location of stddef.h") + +endfunction(get_compiler_include_dir) diff --git a/libc/cmake/modules/LLVMLibCObjectRules.cmake b/libc/cmake/modules/LLVMLibCObjectRules.cmake --- a/libc/cmake/modules/LLVMLibCObjectRules.cmake +++ b/libc/cmake/modules/LLVMLibCObjectRules.cmake @@ -81,6 +81,10 @@ ) get_fq_target_name(${target_name} fq_target_name) + set(entrypoint_name ${target_name}) + if(ADD_ENTRYPOINT_OBJ_NAME) + set(entrypoint_name ${ADD_ENTRYPOINT_OBJ_NAME}) + endif() if(ADD_ENTRYPOINT_OBJ_ALIAS) # Alias targets help one add aliases to other entrypoint object targets. @@ -109,6 +113,7 @@ set_target_properties( ${fq_target_name} PROPERTIES + "ENTRYPOINT_NAME" ${entrypoint_name} "TARGET_TYPE" ${ENTRYPOINT_OBJ_TARGET_TYPE} "IS_ALIAS" "YES" "OBJECT_FILE" "" @@ -125,11 +130,6 @@ message(FATAL_ERROR "`add_entrypoint_object` rule requires HDRS to be specified.") endif() - set(entrypoint_name ${target_name}) - if(ADD_ENTRYPOINT_OBJ_NAME) - set(entrypoint_name ${ADD_ENTRYPOINT_OBJ_NAME}) - endif() - set(objects_target_name "${fq_target_name}_objects") add_library( @@ -199,6 +199,7 @@ set_target_properties( ${fq_target_name} PROPERTIES + "ENTRYPOINT_NAME" ${entrypoint_name} "TARGET_TYPE" ${ENTRYPOINT_OBJ_TARGET_TYPE} "OBJECT_FILE" "${object_file}" "OBJECT_FILE_RAW" "${object_file_raw}" diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -1,3 +1,4 @@ +add_custom_target(libc-generate-headers) add_header( llvm_libc_common_h diff --git a/libc/lib/CMakeLists.txt b/libc/lib/CMakeLists.txt --- a/libc/lib/CMakeLists.txt +++ b/libc/lib/CMakeLists.txt @@ -58,3 +58,10 @@ DEPENDS round_redirector ) + +add_libc_public_integration_test( + public_integration_test + LIBS + llvmlibc + llvmlibm +) diff --git a/libc/utils/HdrGen/CMakeLists.txt b/libc/utils/HdrGen/CMakeLists.txt --- a/libc/utils/HdrGen/CMakeLists.txt +++ b/libc/utils/HdrGen/CMakeLists.txt @@ -11,3 +11,5 @@ PublicAPICommand.cpp PublicAPICommand.h ) + +add_subdirectory(PrototypeTestGen) diff --git a/libc/utils/HdrGen/PrototypeTestGen/CMakeLists.txt b/libc/utils/HdrGen/PrototypeTestGen/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/utils/HdrGen/PrototypeTestGen/CMakeLists.txt @@ -0,0 +1,5 @@ +add_tablegen(libc-prototype-testgen llvm-libc + PrototypeTestGen.cpp + ../PublicAPICommand.cpp + ../Command.cpp +) diff --git a/libc/utils/HdrGen/PrototypeTestGen/PrototypeTestGen.cpp b/libc/utils/HdrGen/PrototypeTestGen/PrototypeTestGen.cpp new file mode 100644 --- /dev/null +++ b/libc/utils/HdrGen/PrototypeTestGen/PrototypeTestGen.cpp @@ -0,0 +1,76 @@ +//===-- PrototypeTestGen.cpp ----------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "../PublicAPICommand.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/TableGen/Main.h" +#include "llvm/TableGen/Record.h" + +namespace { + +llvm::cl::list + EntrypointNamesOption("e", llvm::cl::desc(""), + llvm::cl::OneOrMore); + +} // anonymous namespace + +namespace llvm_libc { + +bool TestGeneratorMain(llvm::raw_ostream &OS, llvm::RecordKeeper &records) { + // Print includes + OS << "#include \"TypeTraits.h\"\n"; + auto G = makeAPIReader(records); + for (const auto &header : G->PublicHeaders) { + OS << "#include <" << header << ">\n"; + } + OS << '\n'; + OS << "int main() {\n"; + for (const auto &entrypoint : EntrypointNamesOption) { + if (G->FunctionSpecMap.count(entrypoint) == 0) { + llvm::errs() << "ERROR: entrypoint '" << entrypoint + << "' could not be found in spec in any public header\n"; + return true; + } + llvm::Record *functionSpec = G->FunctionSpecMap[entrypoint]; + llvm::Record *retValSpec = functionSpec->getValueAsDef("Return"); + llvm::StringRef returnType = + G->getTypeAsString(retValSpec->getValueAsDef("ReturnType")); + // _Noreturn is an indication for the compiler that a function + // doesn't return, and isn't a type understood by c++ templates. + if (returnType.contains("_Noreturn")) + returnType = "void"; + + OS << " static_assert(__llvm_libc::cpp::IsSame<" << returnType << '('; + auto args = functionSpec->getValueAsListOfDefs("Args"); + for (size_t i = 0; i < args.size(); ++i) { + llvm::Record *argType = args[i]->getValueAsDef("ArgType"); + OS << G->getTypeAsString(argType); + if (i < args.size() - 1) + OS << ", "; + } + OS << "), decltype(" << entrypoint << ")>::Value, "; + OS << '"' << entrypoint + << " prototype in TableGen does not match public header" << '"'; + OS << ");\n"; + } + + OS << '\n'; + OS << " return 0;\n"; + OS << "}\n\n"; + + return false; +} + +} // namespace llvm_libc + +int main(int argc, char *argv[]) { + llvm::cl::ParseCommandLineOptions(argc, argv); + return TableGenMain(argv[0], &llvm_libc::TestGeneratorMain); +} diff --git a/libc/utils/HdrGen/PublicAPICommand.h b/libc/utils/HdrGen/PublicAPICommand.h --- a/libc/utils/HdrGen/PublicAPICommand.h +++ b/libc/utils/HdrGen/PublicAPICommand.h @@ -25,6 +25,9 @@ } // namespace llvm +using NameToRecordMapping = std::unordered_map; +using NameSet = std::unordered_set; + namespace llvm_libc { class PublicAPICommand : public Command { @@ -36,6 +39,28 @@ const Command::ErrorReporter &Reporter) const override; }; +class APIReader { +public: + // Mapping from names to records defining them. + NameToRecordMapping MacroSpecMap; + NameToRecordMapping TypeSpecMap; + NameToRecordMapping EnumerationSpecMap; + NameToRecordMapping FunctionSpecMap; + NameToRecordMapping MacroDefsMap; + NameToRecordMapping TypeDeclsMap; + + NameSet Structs; + NameSet Enumerations; + NameSet Functions; + NameSet PublicHeaders; + + virtual std::string getTypeAsString(llvm::Record *TypeRecord) = 0; + + virtual ~APIReader() {} +}; + +std::unique_ptr makeAPIReader(llvm::RecordKeeper &Records); + } // namespace llvm_libc #endif // LLVM_LIBC_UTILS_HDRGEN_PUBLICAPICOMMAND_H diff --git a/libc/utils/HdrGen/PublicAPICommand.cpp b/libc/utils/HdrGen/PublicAPICommand.cpp --- a/libc/utils/HdrGen/PublicAPICommand.cpp +++ b/libc/utils/HdrGen/PublicAPICommand.cpp @@ -57,8 +57,8 @@ } } -class APIGenerator { - llvm::StringRef StdHeader; +class APIGenerator : public llvm_libc::APIReader { + llvm::Optional StdHeader; // TableGen classes in spec.td. llvm::Record *NamedTypeClass; @@ -69,21 +69,6 @@ llvm::Record *StandardSpecClass; llvm::Record *PublicAPIClass; - using NameToRecordMapping = std::unordered_map; - using NameSet = std::unordered_set; - - // Mapping from names to records defining them. - NameToRecordMapping MacroSpecMap; - NameToRecordMapping TypeSpecMap; - NameToRecordMapping EnumerationSpecMap; - NameToRecordMapping FunctionSpecMap; - NameToRecordMapping MacroDefsMap; - NameToRecordMapping TypeDeclsMap; - - NameSet Structs; - NameSet Enumerations; - NameSet Functions; - bool isaNamedType(llvm::Record *Def) { return isa(Def, NamedTypeClass); } bool isaStructType(llvm::Record *Def) { return isa(Def, StructClass); } @@ -102,7 +87,7 @@ bool isaPublicAPI(llvm::Record *Def) { return isa(Def, PublicAPIClass); } - std::string getTypeAsString(llvm::Record *TypeRecord) { + std::string getTypeAsString(llvm::Record *TypeRecord) override { if (isaNamedType(TypeRecord) || isaStructType(TypeRecord)) { return std::string(TypeRecord->getValueAsString("Name")); } else if (isaPtrType(TypeRecord)) { @@ -121,7 +106,9 @@ void indexStandardSpecDef(llvm::Record *StandardSpec) { auto HeaderSpecList = StandardSpec->getValueAsListOfDefs("Headers"); for (llvm::Record *HeaderSpec : HeaderSpecList) { - if (HeaderSpec->getValueAsString("Name") == StdHeader) { + llvm::StringRef Header = HeaderSpec->getValueAsString("Name"); + if (!StdHeader.hasValue() || Header == StdHeader) { + PublicHeaders.insert(std::string(Header)); auto MacroSpecList = HeaderSpec->getValueAsListOfDefs("Macros"); // TODO: Trigger a fatal error on duplicate specs. for (llvm::Record *MacroSpec : MacroSpecList) @@ -189,13 +176,17 @@ if (isaStandardSpec(Def)) indexStandardSpecDef(Def); if (isaPublicAPI(Def)) { - if (Def->getValueAsString("HeaderName") == StdHeader) + if (!StdHeader.hasValue() || + Def->getValueAsString("HeaderName") == StdHeader) indexPublicAPIDef(Def); } } } public: + APIGenerator(llvm::RecordKeeper &Records) : StdHeader(llvm::None) { + index(Records); + } APIGenerator(llvm::StringRef Header, llvm::RecordKeeper &Records) : StdHeader(Header) { index(Records); @@ -286,4 +277,8 @@ G.write(OS); } +std::unique_ptr makeAPIReader(llvm::RecordKeeper &Records) { + return std::make_unique(Records); +} + } // namespace llvm_libc