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 @@ -78,6 +78,10 @@ ALL DEPENDS ${library_file} ) + set_target_properties( + ${target_name} + PROPERTIES LIBRARY_FILE ${library_file} + ) endfunction(add_entrypoint_library) # Rule to build a shared library of redirector objects. diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake --- a/libc/cmake/modules/LLVMLibCTestRules.cmake +++ b/libc/cmake/modules/LLVMLibCTestRules.cmake @@ -175,3 +175,265 @@ ) add_dependencies(libc-fuzzer ${fq_target_name}) endfunction(add_libc_fuzzer) + +# Rule to add a libc integration test. +# Usage +# add_libc_integration_test( +# +# SUITE +# ENTRYPOINT +# DEPENDS +# INTERNAL_HDR +# PUBLIC_HDR +# LIB +# ) +function(add_libc_integration_tests target_name) + if(NOT LLVM_INCLUDE_TESTS) + return() + endif() + + cmake_parse_arguments( + "LIBC_INTTEST" + "" # No optional arguments + "SUITE;ENTRYPOINT;INTERNAL_HDR;PUBLIC_HDR" # Single value arguments + "DEPENDS;LIB" # No Multi value arguments + ${ARGN} + ) + if(NOT LIBC_INTTEST_ENTRYPOINT) + message(FATAL_ERROR "'add_libc_integration_test' target requires a ENTRYPOINT " + "the name of the entrypoint.") + endif() + if(NOT LIBC_INTTEST_INTERNAL_HDR) + message(FATAL_ERROR "'add_libc_integration_test' target requires a INTERNAL_HDR " + "a internal .h file.") + endif() + if(NOT LIBC_INTTEST_PUBLIC_HDR) + message(FATAL_ERROR "'add_libc_integration_test' target requires a PUBLIC_HDR " + "a public .h file.") + endif() + if(NOT LIBC_INTTEST_LIB) + message(FATAL_ERROR "'add_libc_integration_test' target requires a LIB " + "public library to link against.") + endif() + + get_fq_target_name(${target_name} fq_target_name) + + add_libc_public_integration_test( + ${fq_target_name} + ENTRYPOINT + ${LIBC_INTTEST_ENTRYPOINT} + PUBLIC_HDR + ${LIBC_INTTEST_PUBLIC_HDR} + LIB + ${LIBC_INTTEST_LIB} + ) + + add_libc_internal_integration_test( + ${fq_target_name} + ENTRYPOINT + ${LIBC_INTTEST_ENTRYPOINT} + PUBLIC_HDR + ${LIBC_INTTEST_PUBLIC_HDR} + INTERNAL_HDR + ${LIBC_INTTEST_INTERNAL_HDR} + DEPENDS + ${LIBC_INTTEST_DEPENDS} + ) + + if(LIBC_INTTEST_SUITE) + add_dependencies( + ${LIBC_INTTEST_SUITE} + ${fq_target_name}-internal + ${fq_target_name}-public + ) +endif() +endfunction(add_libc_integration_tests) + +# Rule to add a libc public integration test. +# Usage +# add_libc_public_integration_test( +# +# ENTRYPOINT +# PUBLIC_HDR +# LIB +# ) +function(add_libc_public_integration_test target_name) +if(NOT LLVM_INCLUDE_TESTS) + return() + endif() + + cmake_parse_arguments( + "LIBC_INTTEST" + "" # No option arguments + "ENTRYPOINT;PUBLIC_HDR" # Single value arguments + "LIB" # Multi value arguments + ${ARGN} + ) + + set(public_test ${CMAKE_CURRENT_BINARY_DIR}/${target_name}-public-test.cpp) + + # Generate integration test souce code + add_custom_command( + OUTPUT ${public_test} + COMMAND $ -o ${public_test} --public + --header ${LIBC_INTTEST_PUBLIC_HDR} + --entrypoint ${LIBC_INTTEST_ENTRYPOINT} + --public-header ${LIBC_INTTEST_PUBLIC_HDR} + -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 + ) + + add_executable( + ${target_name}-public + EXCLUDE_FROM_ALL + ${public_test} + ) + # Put these into a different folder to not pollute the lib folder + set_target_properties(${target_name}-public PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${LIBC_BUILD_DIR}/integration_tests/" + ) + # Blank out default include directories to prevent accidentally including + # system headers or our own internal headers. + set_target_properties( + ${target_name}-public + PROPERTIES + INCLUDE_DIRECTORIES "" + ) + # Only includes we need are is the include for cpp::function + # and our generated public headers. + target_include_directories( + ${target_name}-public BEFORE + PRIVATE + "${LIBC_SOURCE_DIR}/utils/CPP" + ) + target_include_directories( + ${target_name}-public SYSTEM BEFORE + PRIVATE + "${LIBC_BUILD_DIR}/include" + ) + get_compiler_include_dir() + target_compile_options( + ${target_name}-public + PRIVATE "-nostdinc" + # There is currently a bug that prevents us from adding the compilers + # 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}-public + PRIVATE "-nostdlib" + ) + set(library_files) + foreach(library_name IN LISTS LIBC_INTTEST_LIB) + get_target_property(library_file ${library_name} "LIBRARY_FILE") + list(APPEND library_files ${library_file}) + endforeach() + + target_link_libraries(${target_name}-public + PRIVATE + ${library_files} + ) +endfunction(add_libc_public_integration_test) + +# Rule to add a libc internal integration test. +# Usage +# add_libc_internal_integration_test( +# +# ENTRYPOINT +# PUBLIC_HDR +# INTERNAL_HDR +# DEPENDS +# ) +function(add_libc_internal_integration_test target_name) + if(NOT LLVM_INCLUDE_TESTS) + return() + endif() + + cmake_parse_arguments( + "LIBC_INTTEST" + "" # No option arguments + "ENTRYPOINT;INTERNAL_HDR;PUBLIC_HDR" # Single value arguments + "DEPENDS" # Multi value arguments + ${ARGN} + ) + + set(internal_test ${CMAKE_CURRENT_BINARY_DIR}/${target_name}-internal-test.cpp) + + # Generate integration test souce code + add_custom_command( + OUTPUT ${internal_test} + COMMAND $ -o ${internal_test} + --header ${LIBC_INTTEST_INTERNAL_HDR} + --entrypoint ${LIBC_INTTEST_ENTRYPOINT} + --public-header ${LIBC_INTTEST_PUBLIC_HDR} + -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 + ) + + add_executable( + ${target_name}-internal + EXCLUDE_FROM_ALL + ${internal_test} + ) + # Put these into a different folder to not pollute the lib folder + set_target_properties(${target_name}-internal PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${LIBC_BUILD_DIR}/tests/integration_tests" + ) + target_include_directories( + ${target_name}-internal BEFORE + PRIVATE + "${LIBC_SOURCE_DIR}/utils/CPP" + "${LIBC_BUILD_DIR}/include" + "${LIBC_SOURCE_DIR}" + "${LIBC_BUILD_DIR}" + ) + get_fq_deps_list(fq_deps_list ${LIBC_INTTEST_DEPENDS}) + get_object_files_for_test(link_object_files ${fq_deps_list}) + target_link_libraries(${target_name}-internal PRIVATE ${link_object_files}) +endfunction(add_libc_internal_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/test/src/assert/CMakeLists.txt b/libc/test/src/assert/CMakeLists.txt --- a/libc/test/src/assert/CMakeLists.txt +++ b/libc/test/src/assert/CMakeLists.txt @@ -1,9 +1,9 @@ -add_libc_testsuite(libc_assert_unittests) +add_libc_testsuite(libc_assert_tests) add_libc_unittest( assert_test SUITE - libc_assert_unittests + libc_assert_tests SRCS assert_test.cpp DEPENDS diff --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt --- a/libc/test/src/math/CMakeLists.txt +++ b/libc/test/src/math/CMakeLists.txt @@ -1,4 +1,4 @@ -add_libc_testsuite(libc_math_unittests) +add_libc_testsuite(libc_math_tests) function(add_math_unittest name) cmake_parse_arguments( @@ -33,7 +33,7 @@ cosf_test NEED_MPFR SUITE - libc_math_unittests + libc_math_tests SRCS cosf_test.cpp HDRS @@ -43,12 +43,28 @@ libc.src.math.cosf libc.utils.CPP.standalone_cpp ) +add_libc_integration_tests( + cosf_integration_test + SUITE + libc_math_tests + ENTRYPOINT + cosf + DEPENDS + libc.src.math.cosf + PUBLIC_HDR + math.h + INTERNAL_HDR + src/math/cosf.h + LIB + llvmlibm + llvmlibc +) add_math_unittest( sinf_test NEED_MPFR SUITE - libc_math_unittests + libc_math_tests SRCS sinf_test.cpp HDRS @@ -58,12 +74,28 @@ libc.src.math.sinf libc.utils.CPP.standalone_cpp ) +add_libc_integration_tests( + sinf_integration_test + SUITE + libc_math_tests + ENTRYPOINT + sinf + DEPENDS + libc.src.math.sinf + PUBLIC_HDR + math.h + INTERNAL_HDR + src/math/sinf.h + LIB + llvmlibm + llvmlibc +) add_math_unittest( sincosf_test NEED_MPFR SUITE - libc_math_unittests + libc_math_tests SRCS sincosf_test.cpp HDRS @@ -73,3 +105,19 @@ libc.src.math.sincosf libc.utils.CPP.standalone_cpp ) +add_libc_integration_tests( + sincosf_integration_test + SUITE + libc_math_tests + ENTRYPOINT + sincosf + DEPENDS + libc.src.math.sincosf + PUBLIC_HDR + math.h + INTERNAL_HDR + src/math/sincosf.h + LIB + llvmlibm + llvmlibc +) diff --git a/libc/test/src/string/CMakeLists.txt b/libc/test/src/string/CMakeLists.txt --- a/libc/test/src/string/CMakeLists.txt +++ b/libc/test/src/string/CMakeLists.txt @@ -1,36 +1,81 @@ -add_libc_testsuite(libc_string_unittests) +add_libc_testsuite(libc_string_tests) add_subdirectory(memory_utils) add_libc_unittest( strcat_test SUITE - libc_string_unittests + libc_string_tests SRCS strcat_test.cpp DEPENDS libc.src.string.strcat ) +add_libc_integration_tests( + strcat_integration_test + SUITE + libc_string_tests + ENTRYPOINT + strcat + DEPENDS + libc.src.string.strcat + PUBLIC_HDR + string.h + INTERNAL_HDR + src/string/strcat.h + LIB + llvmlibc +) add_libc_unittest( strcpy_test SUITE - libc_string_unittests + libc_string_tests SRCS strcpy_test.cpp DEPENDS libc.src.string.strcpy ) +add_libc_integration_tests( + strcpy_integration_test + SUITE + libc_string_tests + ENTRYPOINT + strcpy + DEPENDS + libc.src.string.strcpy + PUBLIC_HDR + string.h + INTERNAL_HDR + src/string/strcpy.h + LIB + llvmlibc +) add_libc_unittest( strlen_test SUITE - libc_string_unittests + libc_string_tests SRCS strlen_test.cpp DEPENDS libc.src.string.strlen ) +add_libc_integration_tests( + strlen_integration_test + SUITE + libc_string_tests + ENTRYPOINT + strlen + DEPENDS + libc.src.string.strlen + PUBLIC_HDR + string.h + INTERNAL_HDR + src/string/strlen.h + LIB + llvmlibc +) # Tests all implementations of memcpy that can run on the host. get_property(memcpy_implementations GLOBAL PROPERTY memcpy_implementations) @@ -41,7 +86,7 @@ add_libc_unittest( ${memcpy_config_name}_test SUITE - libc_string_unittests + libc_string_tests SRCS memcpy_test.cpp DEPENDS @@ -52,3 +97,19 @@ endif() endforeach() +add_libc_integration_tests( + memcpy_integration_test + SUITE + libc_string_tests + ENTRYPOINT + memcpy + DEPENDS + libc.src.string.memcpy + PUBLIC_HDR + string.h + INTERNAL_HDR + src/string/memcpy.h + LIB + llvmlibc +) + diff --git a/libc/test/src/string/memory_utils/CMakeLists.txt b/libc/test/src/string/memory_utils/CMakeLists.txt --- a/libc/test/src/string/memory_utils/CMakeLists.txt +++ b/libc/test/src/string/memory_utils/CMakeLists.txt @@ -1,7 +1,7 @@ add_libc_unittest( utils_test SUITE - libc_string_unittests + libc_string_tests SRCS utils_test.cpp memcpy_utils_test.cpp 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,87 @@ +//===-- 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::opt EntrypointNameOption( + "entrypoint", + llvm::cl::desc("The entrypoint we want to generate a test for."), + llvm::cl::Required); +llvm::cl::opt HeaderFileOption( + "header", + llvm::cl::desc("The header file to include in the generated test."), + llvm::cl::Required); +llvm::cl::opt PublicHeaderFileOption( + "public-header", + llvm::cl::desc("The public header file that contains this entrypoint."), + llvm::cl::Required); +llvm::cl::opt + PublicOption("public", + llvm::cl::desc("Generate test using public symbol name.")); + +} // anonymous namespace + +namespace llvm_libc { + +bool TestGeneratorMain(llvm::raw_ostream &OS, llvm::RecordKeeper &records) { + OS << "#include \"Functional.h\"\n"; + OS << "#include "; + if (PublicOption) + OS << '<' << PublicHeaderFileOption << '>'; + else + OS << '"' << HeaderFileOption << '"'; + OS << "\n\n"; + + OS << "int main() {\n"; + OS << " __llvm_libc::cpp::function<"; + auto G = makeAPIReader(PublicHeaderFileOption, records); + if (G->FunctionSpecMap.count(EntrypointNameOption) == 0) { + llvm::errs() << "ERROR: entrypoint '" << EntrypointNameOption + << "' could not be found in spec for public header '" + << PublicHeaderFileOption << "'\n"; + return true; + } + + llvm::Record *functionSpec = G->FunctionSpecMap[EntrypointNameOption]; + llvm::Record *retValSpec = functionSpec->getValueAsDef("Return"); + llvm::Record *returnType = retValSpec->getValueAsDef("ReturnType"); + OS << G->getTypeAsString(returnType); + OS << '('; + 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 << ")> f("; + if (!PublicOption) { + OS << "__llvm_libc::"; + } + OS << EntrypointNameOption << ");\n"; + + OS << " (void) f;\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; + + virtual std::string getTypeAsString(llvm::Record *TypeRecord) = 0; + + virtual ~APIReader() {} +}; + +std::unique_ptr makeAPIReader(llvm::StringRef StdHeader, + 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,7 +57,7 @@ } } -class APIGenerator { +class APIGenerator : public llvm_libc::APIReader { llvm::StringRef StdHeader; // TableGen classes in spec.td. @@ -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)) { @@ -286,4 +271,9 @@ G.write(OS); } +std::unique_ptr makeAPIReader(llvm::StringRef StdHeader, + llvm::RecordKeeper &Records) { + return std::make_unique(StdHeader, Records); +} + } // namespace llvm_libc