diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt --- a/libc/CMakeLists.txt +++ b/libc/CMakeLists.txt @@ -78,8 +78,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/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,106 @@ "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;PUBLIC_HDRS" # 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_INTTEST_PUBLIC_HDRS} + ) + + 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" + "${LIBC_BUILD_DIR}/include" + ) + target_compile_options( + ${target_name} + PRIVATE + -ffreestanding + ) + 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() + + if(COMPILER_RESOURCE_DIR AND LLVM_LIBC_ENABLE_LINTING) + add_custom_target( + ${target_name}-tidy + VERBATIM + COMMAND $ --system-headers + --checks=-*,llvmlibc-restrict-system-libc-headers + "--extra-arg=-resource-dir=${COMPILER_RESOURCE_DIR}" + --header-filter=.* + --warnings-as-errors=llvmlibc-* + "-config={CheckOptions: [{key: llvmlibc-restrict-system-libc-headers.Includes, value: '-*, linux/*, asm/*.h, asm-generic/*.h'}]}" + --quiet + -p ${PROJECT_BINARY_DIR} + ${public_test} + DEPENDS + clang-tidy ${public_test} + ) + add_dependencies(check-libc ${target_name}-tidy) + endif() + + target_link_libraries(${target_name} + PRIVATE + ${library_files} + ) + add_dependencies(check-libc ${target_name}) +endfunction(add_libc_public_integration_test) 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}" @@ -255,7 +256,7 @@ # crossplatform touch. COMMAND "${CMAKE_COMMAND}" -E touch ${lint_timestamp} COMMENT "Linting... ${target_name}" - DEPENDS ${clang-tidy} ${objects_target_name} ${ADD_ENTRYPOINT_OBJ_SRCS} + DEPENDS clang-tidy ${objects_target_name} ${ADD_ENTRYPOINT_OBJ_SRCS} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/libc/lib/CMakeLists.txt b/libc/config/linux/entrypoints.txt copy from libc/lib/CMakeLists.txt copy to libc/config/linux/entrypoints.txt --- a/libc/lib/CMakeLists.txt +++ b/libc/config/linux/entrypoints.txt @@ -1,7 +1,4 @@ - -add_entrypoint_library( - llvmlibc - DEPENDS +set(LIBC_ENTRYPOINTS # assert.h entrypoints libc.src.assert.__assert_fail @@ -23,11 +20,9 @@ libc.src.stdlib.abort # string.h entrypoints - libc.src.string.bzero libc.src.string.memcpy - libc.src.string.memset - libc.src.string.strcat libc.src.string.strcpy + libc.src.string.strcat libc.src.string.strlen # sys/mman.h entrypoints @@ -45,22 +40,10 @@ libc.src.unistd.write ) -add_entrypoint_library( - llvmlibm - DEPENDS +set(LIBM_ENTRYPOINTS # math.h entrypoints libc.src.math.cosf - libc.src.math.fabs - libc.src.math.fabsf - libc.src.math.expf - libc.src.math.exp2f libc.src.math.round libc.src.math.sincosf libc.src.math.sinf -) - -add_redirector_library( - llvmlibc_redirectors - DEPENDS - round_redirector -) +) \ No newline at end of file diff --git a/libc/config/linux/headers.txt b/libc/config/linux/headers.txt new file mode 100644 --- /dev/null +++ b/libc/config/linux/headers.txt @@ -0,0 +1,12 @@ +set(PUBLIC_HEADERS + libc.include.assert_h + libc.include.errno + libc.include.math + libc.include.signal + libc.include.stdio + libc.include.stdlib + libc.include.sys_mman + libc.include.sys_syscall + libc.include.threads + libc.include.unistd +) diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -1,4 +1,3 @@ - add_header( llvm_libc_common_h HDR diff --git a/libc/lib/CMakeLists.txt b/libc/lib/CMakeLists.txt --- a/libc/lib/CMakeLists.txt +++ b/libc/lib/CMakeLists.txt @@ -1,62 +1,16 @@ +include("${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/entrypoints.txt") +include("${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/headers.txt") add_entrypoint_library( llvmlibc DEPENDS - # assert.h entrypoints - libc.src.assert.__assert_fail - - # errno.h entrypoints - libc.src.errno.__errno_location - - # signal.h entrypoints - libc.src.signal.raise - libc.src.signal.sigaction - libc.src.signal.sigdelset - libc.src.signal.sigaddset - libc.src.signal.sigemptyset - libc.src.signal.sigprocmask - libc.src.signal.sigfillset - libc.src.signal.signal - - # stdlib.h entrypoints - libc.src.stdlib._Exit - libc.src.stdlib.abort - - # string.h entrypoints - libc.src.string.bzero - libc.src.string.memcpy - libc.src.string.memset - libc.src.string.strcat - libc.src.string.strcpy - libc.src.string.strlen - - # sys/mman.h entrypoints - libc.src.sys.mman.mmap - libc.src.sys.mman.munmap - - # threads.h entrypoints - libc.src.threads.mtx_init - libc.src.threads.mtx_lock - libc.src.threads.mtx_unlock - libc.src.threads.thrd_create - libc.src.threads.thrd_join - - # unistd.h entrypoints - libc.src.unistd.write + ${LIBC_ENTRYPOINTS} ) add_entrypoint_library( llvmlibm DEPENDS - # math.h entrypoints - libc.src.math.cosf - libc.src.math.fabs - libc.src.math.fabsf - libc.src.math.expf - libc.src.math.exp2f - libc.src.math.round - libc.src.math.sincosf - libc.src.math.sinf + ${LIBM_ENTRYPOINTS} ) add_redirector_library( @@ -64,3 +18,12 @@ DEPENDS round_redirector ) + +add_libc_public_integration_test( + public_integration_test + LIBS + llvmlibc + llvmlibm + PUBLIC_HDRS + ${PUBLIC_HEADERS} +) 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,71 @@ +//===-- 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 + +bool TestGeneratorMain(llvm::raw_ostream &OS, llvm::RecordKeeper &records) { + OS << "#include \"TypeTraits.h\"\n"; + auto G = llvm_libc::makeAPIReader(records); + for (const auto &header : G->PublicHeaders) + OS << "#include <" << header << ">\n"; + OS << '\n'; + OS << "int main() {\n"; + for (const auto &entrypoint : EntrypointNamesOption) { + auto match = G->FunctionSpecMap.find(entrypoint); + if (match == G->FunctionSpecMap.end()) { + llvm::errs() << "ERROR: entrypoint '" << entrypoint + << "' could not be found in spec in any public header\n"; + return true; + } + llvm::Record *functionSpec = match->second; + 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, size = args.size(); i < size; ++i) { + llvm::Record *argType = args[i]->getValueAsDef("ArgType"); + OS << G->getTypeAsString(argType); + if (i < 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; +} + +int main(int argc, char *argv[]) { + llvm::cl::ParseCommandLineOptions(argc, argv); + return TableGenMain(argv[0], 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.emplace(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: + explicit 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