[libc] Add integration tests.

Summary:
This patch aims to add integration tests to check the following:
1) Header files are generated as expected.
2) Libc functions have the correct public name.
3) Libc functions have the correct return type and parameter types.
4) Symbols are exposed in the public lib.a files.

Reviewers: sivachandra, abrachet

Reviewed By: sivachandra

Subscribers: aheejin, ecnelises, dxf, mgorny, jfb, tschuett, libc-commits

Tags: #libc-project

Differential Revision: https://reviews.llvm.org/D79192

GitOrigin-RevId: b836ae24a9f4202a73a227bc3dac9b1a40979a7d
diff --git a/cmake/modules/LLVMLibCObjectRules.cmake b/cmake/modules/LLVMLibCObjectRules.cmake
index 898831c..d1d098d 100644
--- a/cmake/modules/LLVMLibCObjectRules.cmake
+++ b/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/config/linux/x86_64/entrypoints.txt b/config/linux/x86_64/entrypoints.txt
new file mode 100644
index 0000000..2f79d7f
--- /dev/null
+++ b/config/linux/x86_64/entrypoints.txt
@@ -0,0 +1,49 @@
+set(LIBC_ENTRYPOINTS
+    # 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.memcpy
+    libc.src.string.strcpy
+    libc.src.string.strcat
+    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
+)
+
+set(LIBM_ENTRYPOINTS
+    # math.h entrypoints
+    libc.src.math.cosf
+    libc.src.math.round
+    libc.src.math.sincosf
+    libc.src.math.sinf
+)
\ No newline at end of file
diff --git a/config/linux/x86_64/headers.txt b/config/linux/x86_64/headers.txt
new file mode 100644
index 0000000..8c7d71e
--- /dev/null
+++ b/config/linux/x86_64/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/lib/CMakeLists.txt b/lib/CMakeLists.txt
index e0921d9..deae56e 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -1,69 +1,16 @@
+include("${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/${LIBC_TARGET_MACHINE}/entrypoints.txt")
+include("${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/${LIBC_TARGET_MACHINE}/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.call_once
-    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.ceil
-    libc.src.math.ceilf
-    libc.src.math.cosf
-    libc.src.math.fabs
-    libc.src.math.fabsf
-    libc.src.math.floor
-    libc.src.math.floorf
-    libc.src.math.expf
-    libc.src.math.exp2f
-    libc.src.math.round
-    libc.src.math.sincosf
-    libc.src.math.sinf
-    libc.src.math.trunc
-    libc.src.math.truncf
+  ${LIBM_ENTRYPOINTS}
 )
 
 add_redirector_library(
diff --git a/test/src/CMakeLists.txt b/test/src/CMakeLists.txt
index d333108..561a129 100644
--- a/test/src/CMakeLists.txt
+++ b/test/src/CMakeLists.txt
@@ -8,3 +8,91 @@
 add_subdirectory(sys)
 add_subdirectory(threads)
 add_subdirectory(unistd)
+
+include("${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/${LIBC_TARGET_MACHINE}/entrypoints.txt")
+include("${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/${LIBC_TARGET_MACHINE}/headers.txt")
+
+set(public_test ${CMAKE_CURRENT_BINARY_DIR}/public_integration_test.cpp)
+
+set(entrypoints_name_list "")
+foreach(entry IN LISTS LIBC_ENTRYPOINTS LIBM_ENTRYPOINTS)
+  get_target_property(entry_name ${entry} "ENTRYPOINT_NAME")
+  list(APPEND entrypoints_name_list ${entry_name})
+endforeach()
+
+# TODO: Remove these when they are added to the TableGen.
+list(REMOVE_ITEM entrypoints_name_list "__assert_fail" "__errno_location")
+list(TRANSFORM entrypoints_name_list PREPEND "-e=")
+
+# Generate integration test souce code.
+add_custom_command(
+  OUTPUT ${public_test}
+  COMMAND $<TARGET_FILE:libc-prototype-testgen> -o ${public_test}
+          ${entrypoints_name_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 ${PUBLIC_HEADERS}
+          llvmlibc llvmlibm
+)
+
+add_executable(
+  public_integration_test
+  EXCLUDE_FROM_ALL
+  ${public_test}
+)
+# Blank out default include directories to prevent accidentally including
+# system headers or our own internal headers.
+set_target_properties(
+  public_integration_test
+  PROPERTIES
+  INCLUDE_DIRECTORIES ""
+)
+# Only include we need is the include for cpp::IsSame and our generated
+# public headers.
+target_include_directories(
+  public_integration_test BEFORE
+  PRIVATE
+    "${LIBC_SOURCE_DIR}/utils/CPP"
+    "${LIBC_BUILD_DIR}/include"
+)
+target_compile_options(
+  public_integration_test
+  PRIVATE
+  -ffreestanding
+)
+target_link_options(
+  public_integration_test
+  PRIVATE "-nostdlib"
+)
+set(library_files)
+foreach(library_name IN LISTS "llvmlibc;llvmlibm")
+  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(
+    public_integration_test-tidy
+    VERBATIM
+    COMMAND $<TARGET_FILE:clang-tidy> --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 public_integration_test-tidy)
+endif()
+
+target_link_libraries(public_integration_test
+  PRIVATE
+  ${library_files}
+)
+add_dependencies(check-libc public_integration_test)
diff --git a/utils/HdrGen/CMakeLists.txt b/utils/HdrGen/CMakeLists.txt
index 65eaa8b..65c2d65 100644
--- a/utils/HdrGen/CMakeLists.txt
+++ b/utils/HdrGen/CMakeLists.txt
@@ -11,3 +11,5 @@
   PublicAPICommand.cpp
   PublicAPICommand.h
 )
+
+add_subdirectory(PrototypeTestGen)
diff --git a/utils/HdrGen/PrototypeTestGen/.clang-tidy b/utils/HdrGen/PrototypeTestGen/.clang-tidy
new file mode 100644
index 0000000..d404f83
--- /dev/null
+++ b/utils/HdrGen/PrototypeTestGen/.clang-tidy
@@ -0,0 +1,3 @@
+CheckOptions:
+  - key:             readability-identifier-naming.VariableCase
+    value:           camelBack
diff --git a/utils/HdrGen/PrototypeTestGen/CMakeLists.txt b/utils/HdrGen/PrototypeTestGen/CMakeLists.txt
new file mode 100644
index 0000000..e4ad5c2
--- /dev/null
+++ b/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/utils/HdrGen/PrototypeTestGen/PrototypeTestGen.cpp b/utils/HdrGen/PrototypeTestGen/PrototypeTestGen.cpp
new file mode 100644
index 0000000..aad451a
--- /dev/null
+++ b/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<std::string>
+    EntrypointNamesOption("e", llvm::cl::desc("<list of entrypoints>"),
+                          llvm::cl::OneOrMore);
+
+} // anonymous namespace
+
+bool TestGeneratorMain(llvm::raw_ostream &OS, llvm::RecordKeeper &records) {
+  OS << "#include \"TypeTraits.h\"\n";
+  llvm_libc::APIIndexer G(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");
+    std::string 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 (llvm::StringRef(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);
+}