[libc-commits] [libc] [libc] Use `LIBC_COPT_PUBLIC_PACKAGING` for hermetic and integration tests. (PR #79319)

via libc-commits libc-commits at lists.llvm.org
Sat Jan 27 10:13:07 PST 2024


https://github.com/lntue updated https://github.com/llvm/llvm-project/pull/79319

>From e06588295fb945d64a571f95de5b9b8fad4f6aa1 Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue at google.com>
Date: Fri, 26 Jan 2024 09:44:58 -0500
Subject: [PATCH 01/11] [libc] Use LIBC_COPT_PUBLIC_PACKAGING for hermetic and
 integration tests.

---
 libc/cmake/modules/LLVMLibCTestRules.cmake    | 112 +++++++++++++++---
 libc/test/integration/startup/CMakeLists.txt  |   6 +-
 .../math/differential_testing/CMakeLists.txt  |   5 +-
 3 files changed, 107 insertions(+), 16 deletions(-)

diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index 0d0a47b33aaeba0..d07cec047370ee7 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -5,6 +5,7 @@
 # Usage:
 #   get_object_files_for_test(<result var>
 #                             <skipped_entrypoints_var>
+#                             INTERNAL <internal>
 #                             <target0> [<target1> ...])
 #
 #   The list of object files is collected in <result_var>.
@@ -12,11 +13,24 @@
 #   set to a true value.
 #   targetN is either an "add_entrypoint_target" target or an
 #   "add_object_library" target.
+#   If INTERNAL is TRUE, then we collect `target.__internal__` for entry points.
 function(get_object_files_for_test result skipped_entrypoints_list)
+  cmake_parse_arguments(
+    GET_OBJ
+    "" # No optional
+    "INTERNAL" # Single-value
+    "" # No multi-value
+    ${ARGN}
+  )
   set(object_files "")
   set(skipped_list "")
   set(checked_list "")
-  set(unchecked_list "${ARGN}")
+  set(unchecked_list "${GET_OBJ_UNPARSED_ARGUMENTS}")
+
+  set(check_obj_for_tests "CHECK_OBJ_FOR_TESTS_${GET_OBJ_INTERNAL}")
+  set(object_files_for_tests "OBJECT_FILES_FOR_TESTS_${GET_OBJ_INTERNAL}")
+  set(skipped_list_for_tests "SKIPPED_LIST_FOR_TESTS_${GET_OBJ_INTERNAL}")
+
   list(REMOVE_DUPLICATES unchecked_list)
 
   foreach(dep IN LISTS unchecked_list)
@@ -37,19 +51,23 @@ function(get_object_files_for_test result skipped_entrypoints_list)
       continue()
     endif()
 
-    get_target_property(dep_checked ${dep} "CHECK_OBJ_FOR_TESTS")
+    get_target_property(dep_checked ${dep} ${check_obj_for_tests})
 
     if(dep_checked)
       # Target full dependency has already been checked.  Just use the results.
-      get_target_property(dep_obj ${dep} "OBJECT_FILES_FOR_TESTS")
-      get_target_property(dep_skip ${dep} "SKIPPED_LIST_FOR_TESTS")
+      get_target_property(dep_obj ${dep} ${object_files_for_tests})
+      get_target_property(dep_skip ${dep} ${skipped_list_for_tests})
     else()
       # Target full dependency hasn't been checked.  Recursively check its DEPS.
       set(dep_obj "${dep}")
       set(dep_skip "")
 
       get_target_property(indirect_deps ${dep} "DEPS")
-      get_object_files_for_test(dep_obj dep_skip ${indirect_deps})
+      get_object_files_for_test(
+          dep_obj dep_skip
+          INTERNAL ${GET_OBJ_INTERNAL}
+          ${indirect_deps}
+      )
 
       if(${dep_type} STREQUAL ${OBJECT_LIBRARY_TARGET_TYPE})
         get_target_property(dep_object_files ${dep} "OBJECT_FILES")
@@ -62,7 +80,11 @@ function(get_object_files_for_test result skipped_entrypoints_list)
           list(APPEND dep_skip ${dep})
           list(REMOVE_ITEM dep_obj ${dep})
         endif()
-        get_target_property(object_file_raw ${dep} "OBJECT_FILE_RAW")
+        if(${GET_OBJ_INTERNAL})
+          get_target_property(object_file_raw ${dep} "OBJECT_FILE_RAW")
+        else()
+          get_target_property(object_file_raw ${dep} "OBJECT_FILE")
+        endif()
         if(object_file_raw)
           list(APPEND dep_obj ${object_file_raw})
         endif()
@@ -73,9 +95,9 @@ function(get_object_files_for_test result skipped_entrypoints_list)
       endif()
 
       set_target_properties(${dep} PROPERTIES
-        OBJECT_FILES_FOR_TESTS "${dep_obj}"
-        SKIPPED_LIST_FOR_TESTS "${dep_skip}"
-        CHECK_OBJ_FOR_TESTS "YES"
+        "${object_files_for_tests}" "${dep_obj}"
+        "${skipped_list_for_tests}" "${dep_skip}"
+        "${check_obj_for_tests}" "YES"
       )
 
     endif()
@@ -140,7 +162,10 @@ function(create_libc_unittest fq_target_name)
   endif()
 
   get_object_files_for_test(
-      link_object_files skipped_entrypoints_list ${fq_deps_list})
+      link_object_files skipped_entrypoints_list
+      INTERNAL TRUE
+      ${fq_deps_list}
+  )
   if(skipped_entrypoints_list)
     # If a test is OS/target machine independent, it has to be skipped if the
     # OS/target machine combination does not provide any dependent entrypoints.
@@ -167,6 +192,15 @@ function(create_libc_unittest fq_target_name)
     return()
   endif()
 
+  if(SHOW_INTERMEDIATE_OBJECTS)
+    message(STATUS "Adding unit test ${fq_target_name}")
+    if(${SHOW_INTERMEDIATE_OBJECTS} STREQUAL "DEPS")
+      foreach(dep IN LISTS LIBC_UNITTEST_DEPENDS)
+        message(STATUS "  ${fq_target_name} depends on ${dep}")
+      endforeach()
+    endif()
+  endif()
+
   if(LIBC_UNITTEST_NO_RUN_POSTBUILD)
     set(fq_build_target_name ${fq_target_name})
   else()
@@ -389,7 +423,10 @@ function(add_libc_fuzzer target_name)
   get_fq_target_name(${target_name} fq_target_name)
   get_fq_deps_list(fq_deps_list ${LIBC_FUZZER_DEPENDS})
   get_object_files_for_test(
-      link_object_files skipped_entrypoints_list ${fq_deps_list})
+      link_object_files skipped_entrypoints_list
+      INTERNAL TRUE
+      ${fq_deps_list}
+  )
   if(skipped_entrypoints_list)
     if(LIBC_CMAKE_VERBOSE_LOGGING)
       set(msg "Skipping fuzzer target ${fq_target_name} as it has missing deps: "
@@ -493,6 +530,16 @@ function(add_integration_test test_name)
   get_fq_target_name(${test_name}.libc fq_libc_target_name)
 
   get_fq_deps_list(fq_deps_list ${INTEGRATION_TEST_DEPENDS})
+
+  if(SHOW_INTERMEDIATE_OBJECTS)
+    message(STATUS "Adding integration test ${fq_target_name}")
+    if(${SHOW_INTERMEDIATE_OBJECTS} STREQUAL "DEPS")
+      foreach(dep IN LISTS fq_deps_list)
+        message(STATUS "  ${fq_target_name} depends on ${dep}")
+      endforeach()
+    endif()
+  endif()
+
   list(APPEND fq_deps_list
       # All integration tests use the operating system's startup object with the
       # integration test object and need to inherit the same dependencies.
@@ -500,6 +547,7 @@ function(add_integration_test test_name)
       libc.test.IntegrationTest.test
       # We always add the memory functions objects. This is because the
       # compiler's codegen can emit calls to the C memory functions.
+      libc.src.stdlib.atexit
       libc.src.string.bcmp
       libc.src.string.bzero
       libc.src.string.memcmp
@@ -519,7 +567,20 @@ function(add_integration_test test_name)
   # TODO: Instead of gathering internal object files from entrypoints,
   # collect the object files with public names of entrypoints.
   get_object_files_for_test(
-      link_object_files skipped_entrypoints_list ${fq_deps_list})
+      link_object_files skipped_entrypoints_list
+      INTERNAL FALSE
+      ${fq_deps_list}
+  )
+
+  if(SHOW_INTERMEDIATE_OBJECTS)
+    message(STATUS "Get objects for test ${fq_target_name}")
+    if(${SHOW_INTERMEDIATE_OBJECTS} STREQUAL "DEPS")
+      foreach(dep IN LISTS link_object_files)
+        message(STATUS "  ${fq_target_name} need object ${dep}")
+      endforeach()
+    endif()
+  endif()
+  
   if(skipped_entrypoints_list)
     if(LIBC_CMAKE_VERBOSE_LOGGING)
       set(msg "Skipping integration test ${fq_target_name} as it has missing deps: "
@@ -672,6 +733,16 @@ function(add_libc_hermetic_test test_name)
   get_fq_target_name(${test_name}.libc fq_libc_target_name)
 
   get_fq_deps_list(fq_deps_list ${HERMETIC_TEST_DEPENDS})
+
+  if(SHOW_INTERMEDIATE_OBJECTS)
+    message(STATUS "Adding hermetic test ${fq_target_name}")
+    if(${SHOW_INTERMEDIATE_OBJECTS} STREQUAL "DEPS")
+      foreach(dep IN LISTS fq_deps_list)
+        message(STATUS "  ${fq_target_name} depends on ${dep}")
+      endforeach()
+    endif()
+  endif()
+
   list(APPEND fq_deps_list
       # Hermetic tests use the platform's startup object. So, their deps also
       # have to be collected.
@@ -703,8 +774,21 @@ function(add_libc_hermetic_test test_name)
   # TODO: Instead of gathering internal object files from entrypoints,
   # collect the object files with public names of entrypoints.
   get_object_files_for_test(
-      link_object_files skipped_entrypoints_list ${fq_deps_list})
-  if(skipped_entrypoints_list)
+      link_object_files skipped_entrypoints_list
+      INTERNAL FALSE
+      ${fq_deps_list}
+  )
+
+  if(SHOW_INTERMEDIATE_OBJECTS)
+    message(STATUS "Get objects for test ${fq_target_name}")
+    if(${SHOW_INTERMEDIATE_OBJECTS} STREQUAL "DEPS")
+      foreach(dep IN LISTS link_object_files)
+        message(STATUS "  ${fq_target_name} need object ${dep}")
+      endforeach()
+    endif()
+  endif()
+
+    if(skipped_entrypoints_list)
     set(msg "Skipping hermetic test ${fq_target_name} as it has missing deps: "
             "${skipped_entrypoints_list}.")
     message(STATUS ${msg})
diff --git a/libc/test/integration/startup/CMakeLists.txt b/libc/test/integration/startup/CMakeLists.txt
index fb5d6bc787cc261..154a6d8a6aa475d 100644
--- a/libc/test/integration/startup/CMakeLists.txt
+++ b/libc/test/integration/startup/CMakeLists.txt
@@ -38,7 +38,11 @@ function(add_startup_test target_name)
   if(ADD_STARTUP_TEST_DEPENDS)
     get_fq_deps_list(fq_deps_list ${ADD_STARTUP_TEST_DEPENDS})
     add_dependencies(${fq_target_name} ${fq_deps_list})
-    get_object_files_for_test(link_object_files has_skipped_entrypoint_list ${fq_deps_list})
+    get_object_files_for_test(
+        link_object_files has_skipped_entrypoint_list
+        INTERNAL TRUE
+        ${fq_deps_list}
+    )
     target_link_libraries(${fq_target_name} ${link_object_files})
   endif()
 
diff --git a/libc/test/src/math/differential_testing/CMakeLists.txt b/libc/test/src/math/differential_testing/CMakeLists.txt
index 72bc2f8fb16aaa0..466c53287a5b161 100644
--- a/libc/test/src/math/differential_testing/CMakeLists.txt
+++ b/libc/test/src/math/differential_testing/CMakeLists.txt
@@ -27,7 +27,10 @@ function(add_diff_binary target_name)
   get_fq_target_name(${target_name} fq_target_name)
   get_fq_deps_list(fq_deps_list ${DIFF_DEPENDS})
   get_object_files_for_test(
-      link_object_files skipped_entrypoints_list ${fq_deps_list})
+      link_object_files skipped_entrypoints_list
+      INTERNAL TRUE
+      ${fq_deps_list}
+  )
   if(skipped_entrypoints_list)
     if(LIBC_CMAKE_VERBOSE_LOGGING)
       set(msg "Will not build ${fq_target_name} as it has missing deps: "

>From e15ee80bef37c657e90cc979910e309fedf8c19f Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue at google.com>
Date: Fri, 26 Jan 2024 09:47:02 -0500
Subject: [PATCH 02/11] Remove extra C functions in LibcTest.cpp,
 HermeticTestUtils.cpp, and test.cpp.

---
 libc/test/IntegrationTest/test.cpp       | 41 ------------------------
 libc/test/UnitTest/CMakeLists.txt        | 11 ++++++-
 libc/test/UnitTest/HermeticTestUtils.cpp | 37 ---------------------
 libc/test/UnitTest/LibcTest.cpp          | 19 ++++-------
 4 files changed, 16 insertions(+), 92 deletions(-)

diff --git a/libc/test/IntegrationTest/test.cpp b/libc/test/IntegrationTest/test.cpp
index 3bdbe89a3fb62d9..b5cdaa7fdf92479 100644
--- a/libc/test/IntegrationTest/test.cpp
+++ b/libc/test/IntegrationTest/test.cpp
@@ -9,47 +9,6 @@
 #include <stddef.h>
 #include <stdint.h>
 
-// Integration tests rely on the following memory functions. This is because the
-// compiler code generation can emit calls to them. We want to map the external
-// entrypoint to the internal implementation of the function used for testing.
-// This is done manually as not all targets support aliases.
-
-namespace LIBC_NAMESPACE {
-
-int bcmp(const void *lhs, const void *rhs, size_t count);
-void bzero(void *ptr, size_t count);
-int memcmp(const void *lhs, const void *rhs, size_t count);
-void *memcpy(void *__restrict, const void *__restrict, size_t);
-void *memmove(void *dst, const void *src, size_t count);
-void *memset(void *ptr, int value, size_t count);
-int atexit(void (*func)(void));
-
-} // namespace LIBC_NAMESPACE
-
-extern "C" {
-
-int bcmp(const void *lhs, const void *rhs, size_t count) {
-  return LIBC_NAMESPACE::bcmp(lhs, rhs, count);
-}
-void bzero(void *ptr, size_t count) { LIBC_NAMESPACE::bzero(ptr, count); }
-int memcmp(const void *lhs, const void *rhs, size_t count) {
-  return LIBC_NAMESPACE::memcmp(lhs, rhs, count);
-}
-void *memcpy(void *__restrict dst, const void *__restrict src, size_t count) {
-  return LIBC_NAMESPACE::memcpy(dst, src, count);
-}
-void *memmove(void *dst, const void *src, size_t count) {
-  return LIBC_NAMESPACE::memmove(dst, src, count);
-}
-void *memset(void *ptr, int value, size_t count) {
-  return LIBC_NAMESPACE::memset(ptr, value, count);
-}
-
-// This is needed if the test was compiled with '-fno-use-cxa-atexit'.
-int atexit(void (*func)(void)) { return LIBC_NAMESPACE::atexit(func); }
-
-} // extern "C"
-
 // Integration tests cannot use the SCUDO standalone allocator as SCUDO pulls
 // various other parts of the libc. Since SCUDO development does not use
 // LLVM libc build rules, it is very hard to keep track or pull all that SCUDO
diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt
index 9baa41874a83dba..fd0f966495b7f23 100644
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -18,6 +18,13 @@ function(add_unittest_framework_library name)
     set(library_type STATIC)
   endif()
 
+  # When the clock target is available, it will be used inside the test lib
+  # to indicate the performance of the tests.
+  set(clock_target "")
+  if(TARGET libc.src.time.clock)
+    set(clock_target libc.src.time.clock)
+  endif()
+
   foreach(lib IN ITEMS ${name}.unit ${name}.hermetic)
     add_library(
       ${lib}
@@ -28,7 +35,7 @@ function(add_unittest_framework_library name)
     )
     target_include_directories(${lib} PUBLIC ${LIBC_SOURCE_DIR})
     target_compile_options(${lib} PRIVATE -fno-exceptions -fno-rtti)
-    if(TARGET libc.src.time.clock)
+    if(${clock_target})
       target_compile_definitions(${lib} PRIVATE TARGET_SUPPORTS_CLOCK)
     endif()
   endforeach()
@@ -70,6 +77,7 @@ add_unittest_framework_library(
     libc.src.__support.CPP.type_traits
     libc.src.__support.OSUtil.osutil
     libc.src.__support.uint128
+    ${clock_target}
 )
 
 set(libc_death_test_srcs LibcDeathTestExecutors.cpp)
@@ -114,6 +122,7 @@ add_unittest_framework_library(
     libc.src.__support.FPUtil.fpbits_str
     libc.src.__support.FPUtil.fenv_impl
     libc.src.__support.FPUtil.rounding_mode
+    libc.src.errno.errno
 )
 
 add_unittest_framework_library(
diff --git a/libc/test/UnitTest/HermeticTestUtils.cpp b/libc/test/UnitTest/HermeticTestUtils.cpp
index 349c182ff2379f8..145683976268a1b 100644
--- a/libc/test/UnitTest/HermeticTestUtils.cpp
+++ b/libc/test/UnitTest/HermeticTestUtils.cpp
@@ -9,18 +9,6 @@
 #include <stddef.h>
 #include <stdint.h>
 
-namespace LIBC_NAMESPACE {
-
-int bcmp(const void *lhs, const void *rhs, size_t count);
-void bzero(void *ptr, size_t count);
-int memcmp(const void *lhs, const void *rhs, size_t count);
-void *memcpy(void *__restrict, const void *__restrict, size_t);
-void *memmove(void *dst, const void *src, size_t count);
-void *memset(void *ptr, int value, size_t count);
-int atexit(void (*func)(void));
-
-} // namespace LIBC_NAMESPACE
-
 namespace {
 
 // Integration tests cannot use the SCUDO standalone allocator as SCUDO pulls
@@ -37,31 +25,6 @@ static uint8_t *ptr = memory;
 
 extern "C" {
 
-// Hermetic tests rely on the following memory functions. This is because the
-// compiler code generation can emit calls to them. We want to map the external
-// entrypoint to the internal implementation of the function used for testing.
-// This is done manually as not all targets support aliases.
-
-int bcmp(const void *lhs, const void *rhs, size_t count) {
-  return LIBC_NAMESPACE::bcmp(lhs, rhs, count);
-}
-void bzero(void *ptr, size_t count) { LIBC_NAMESPACE::bzero(ptr, count); }
-int memcmp(const void *lhs, const void *rhs, size_t count) {
-  return LIBC_NAMESPACE::memcmp(lhs, rhs, count);
-}
-void *memcpy(void *__restrict dst, const void *__restrict src, size_t count) {
-  return LIBC_NAMESPACE::memcpy(dst, src, count);
-}
-void *memmove(void *dst, const void *src, size_t count) {
-  return LIBC_NAMESPACE::memmove(dst, src, count);
-}
-void *memset(void *ptr, int value, size_t count) {
-  return LIBC_NAMESPACE::memset(ptr, value, count);
-}
-
-// This is needed if the test was compiled with '-fno-use-cxa-atexit'.
-int atexit(void (*func)(void)) { return LIBC_NAMESPACE::atexit(func); }
-
 constexpr uint64_t ALIGNMENT = alignof(uintptr_t);
 
 void *malloc(size_t s) {
diff --git a/libc/test/UnitTest/LibcTest.cpp b/libc/test/UnitTest/LibcTest.cpp
index 201b40a178dca08..83d03f57bf3eaec 100644
--- a/libc/test/UnitTest/LibcTest.cpp
+++ b/libc/test/UnitTest/LibcTest.cpp
@@ -13,17 +13,6 @@
 #include "src/__support/UInt128.h"
 #include "test/UnitTest/TestLogger.h"
 
-#if __STDC_HOSTED__
-#include <time.h>
-#define LIBC_TEST_USE_CLOCK
-#elif defined(TARGET_SUPPORTS_CLOCK)
-#include <time.h>
-
-#include "src/time/clock.h"
-extern "C" clock_t clock() noexcept { return LIBC_NAMESPACE::clock(); }
-#define LIBC_TEST_USE_CLOCK
-#endif
-
 namespace LIBC_NAMESPACE {
 namespace testing {
 
@@ -126,13 +115,17 @@ int Test::runTests(const char *TestFilter) {
       continue;
     }
     tlog << GREEN << "[ RUN      ] " << RESET << TestName << '\n';
-    [[maybe_unused]] const auto start_time = clock();
+#ifdef LIBC_TEST_USE_CLOCK
+    const auto start_time = clock();
+#endif
     RunContext Ctx;
     T->SetUp();
     T->setContext(&Ctx);
     T->Run();
     T->TearDown();
-    [[maybe_unused]] const auto end_time = clock();
+#ifdef LIBC_TEST_USE_CLOCK
+    const auto end_time = clock();
+#endif // LIBC_TEST_USE_CLOCK
     switch (Ctx.status()) {
     case RunContext::RunResult::Fail:
       tlog << RED << "[  FAILED  ] " << RESET << TestName << '\n';

>From 34a5a502170d66298a067f32b3a7c7f6cb99b57a Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue at google.com>
Date: Fri, 26 Jan 2024 09:48:36 -0500
Subject: [PATCH 03/11] Update libc_errno to work correctly for both overlay
 and full build modes.

---
 libc/include/errno.h.def                      |  8 +++
 libc/src/errno/CMakeLists.txt                 | 11 ++++
 libc/src/errno/libc_errno.cpp                 | 56 +++++++++---------
 libc/src/errno/libc_errno.h                   | 57 ++++++++-----------
 libc/test/src/errno/errno_test.cpp            |  2 +-
 libc/test/src/stdlib/StrtolTest.h             | 12 ++--
 libc/test/src/sys/mman/linux/madvise_test.cpp |  2 +-
 libc/test/src/sys/mman/linux/mmap_test.cpp    |  2 +-
 .../test/src/sys/mman/linux/mprotect_test.cpp |  2 +-
 .../src/sys/mman/linux/posix_madvise_test.cpp |  2 +-
 libc/test/src/time/asctime_r_test.cpp         |  6 +-
 libc/test/src/time/asctime_test.cpp           | 12 ++--
 12 files changed, 90 insertions(+), 82 deletions(-)

diff --git a/libc/include/errno.h.def b/libc/include/errno.h.def
index d8f79dd47a0d1cc..6002707deae9b7b 100644
--- a/libc/include/errno.h.def
+++ b/libc/include/errno.h.def
@@ -44,7 +44,15 @@
 #endif
 
 #if !defined(__AMDGPU__) && !defined(__NVPTX__)
+
+#ifdef __cplusplus
+extern "C" {
+  extern thread_local int __llvmlibc_errno;
+}
+#else
 extern _Thread_local int __llvmlibc_errno;
+#endif // __cplusplus
+
 #define errno __llvmlibc_errno
 #endif
 
diff --git a/libc/src/errno/CMakeLists.txt b/libc/src/errno/CMakeLists.txt
index e8868dc48c5e97c..e19285809e245c5 100644
--- a/libc/src/errno/CMakeLists.txt
+++ b/libc/src/errno/CMakeLists.txt
@@ -1,9 +1,20 @@
+# If we are in full build mode, we will provide the errno definition ourselves,
+# and if we are in overlay mode, we will just re-use the system's errno.
+# We are passing LIBC_FULL_BUILD flag in full build mode so that the
+# implementation of libc_errno will know if we are in full build mode or not.
+set(full_build_flag "")
+if(LLVM_LIBC_FULL_BUILD)
+  set(full_build_flag "-DLIBC_FULL_BUILD")
+endif()
+
 add_entrypoint_object(
   errno
   SRCS
     libc_errno.cpp
   HDRS
     libc_errno.h     # Include this
+  COMPILE_OPTIONS
+    ${full_build_flag}
   DEPENDS
     libc.include.errno
     libc.src.__support.common
diff --git a/libc/src/errno/libc_errno.cpp b/libc/src/errno/libc_errno.cpp
index c8f0bffd0962e9b..b501f949e4b73e5 100644
--- a/libc/src/errno/libc_errno.cpp
+++ b/libc/src/errno/libc_errno.cpp
@@ -1,4 +1,4 @@
-//===-- Implementation of errno -------------------------------------------===//
+//===-- Implementation of libc_errno --------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -6,35 +6,39 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "src/__support/macros/attributes.h"
-#include "src/__support/macros/properties/architectures.h"
-
-namespace LIBC_NAMESPACE {
+#include "libc_errno.h"
 
 #ifdef LIBC_TARGET_ARCH_IS_GPU
-struct ErrnoConsumer {
-  void operator=(int) {}
-};
-#endif
+// If we are targeting the GPU we currently don't support 'errno'. We simply
+// consume it.
+void LIBC_NAMESPACE::Errno::operator=(int) {}
+LIBC_NAMESPACE::Errno::operator int() { return 0; }
+
+#elif !defined(LIBC_COPT_PUBLIC_PACKAGING)
+// This mode is for unit testing.  We just use our internal errno.
+LIBC_THREAD_LOCAL int __llvmlibc_internal_errno;
 
+void LIBC_NAMESPACE::Errno::operator=(int a) { __llvmlibc_internal_errno = a; }
+LIBC_NAMESPACE::Errno::operator int() { return __llvmlibc_internal_errno; }
+
+#elif defined(LIBC_FULL_BUILD)
+// This mode is for public libc archive, hermetic, and integration tests.
+// In full build mode, we provide the errno storage ourselves.
 extern "C" {
-#ifdef LIBC_COPT_PUBLIC_PACKAGING
-// TODO: Declare __llvmlibc_errno only under LIBC_COPT_PUBLIC_PACKAGING and
-// __llvmlibc_internal_errno otherwise.
-// In overlay mode, this will be an unused thread local variable as libc_errno
-// will resolve to errno from the system libc's errno.h. In full build mode
-// however, libc_errno will resolve to this thread local variable via the errno
-// macro defined in LLVM libc's public errno.h header file.
-// TODO: Use a macro to distinguish full build and overlay build which can be
-//       used to exclude __llvmlibc_errno under overlay build.
-#ifdef LIBC_TARGET_ARCH_IS_GPU
-ErrnoConsumer __llvmlibc_errno;
-#else
 LIBC_THREAD_LOCAL int __llvmlibc_errno;
-#endif // LIBC_TARGET_ARCH_IS_GPU
+}
+
+void LIBC_NAMESPACE::Errno::operator=(int a) { __llvmlibc_errno = a; }
+LIBC_NAMESPACE::Errno::operator int() { return __llvmlibc_errno; }
+
 #else
-LIBC_THREAD_LOCAL int __llvmlibc_internal_errno;
-#endif
-} // extern "C"
+// In overlay mode, we simply use the system errno.
+#include <errno.h>
+
+void LIBC_NAMESPACE::Errno::operator=(int a) { errno = a; }
+LIBC_NAMESPACE::Errno::operator int() { return errno; }
+
+#endif // LIBC_FULL_BUILD
 
-} // namespace LIBC_NAMESPACE
+// Define the global `libc_errno` instance.
+LIBC_NAMESPACE::Errno libc_errno;
diff --git a/libc/src/errno/libc_errno.h b/libc/src/errno/libc_errno.h
index fbcd1c3395cd1fd..c19aadcb865e304 100644
--- a/libc/src/errno/libc_errno.h
+++ b/libc/src/errno/libc_errno.h
@@ -12,45 +12,34 @@
 #include "src/__support/macros/attributes.h"
 #include "src/__support/macros/properties/architectures.h"
 
+// TODO: Separate just the definition of errno numbers in
+// include/llvm-libc-macros/* and only include that instead of the system
+// <errno.h>.
 #include <errno.h>
 
-// If we are targeting the GPU we currently don't support 'errno'. We simply
-// consume it.
-#ifdef LIBC_TARGET_ARCH_IS_GPU
-namespace LIBC_NAMESPACE {
-struct ErrnoConsumer {
-  void operator=(int) {}
-};
-} // namespace LIBC_NAMESPACE
-#endif
-
-// All of the libc runtime and test code should use the "libc_errno" macro. They
-// should not refer to the "errno" macro directly.
-#ifdef LIBC_COPT_PUBLIC_PACKAGING
-#ifdef LIBC_TARGET_ARCH_IS_GPU
-extern "C" LIBC_NAMESPACE::ErrnoConsumer __llvmlibc_errno;
-#define libc_errno __llvmlibc_errno
-#else
-// This macro will resolve to errno from the errno.h file included above. Under
-// full build, this will be LLVM libc's errno. In overlay build, it will be
-// system libc's errno.
-#define libc_errno errno
-#endif
-#else
-namespace LIBC_NAMESPACE {
+// This header is to be consumed by internal implementations, in which all of
+// them should refer to `libc_errno` instead of using `errno` directly from
+// <errno.h> header.
 
-// TODO: On the GPU build this will be mapped to a single global value. We need
-// to ensure that tests are not run with multiple threads that depend on errno
-// until we have true 'thread_local' support on the GPU.
-extern "C" LIBC_THREAD_LOCAL int __llvmlibc_internal_errno;
+// Unit and hermetic tests should:
+// - #include "src/errno/libc_errno.h"
+// - DO NOT #include <errno.h>
+// - Only use `libc_errno` in the code
+// - Depend on libc.src.errno.errno
 
-// TODO: After all of libc/src and libc/test are switched over to use
-// libc_errno, this header file will be "shipped" via an add_entrypoint_object
-// target. At which point libc_errno, should point to __llvmlibc_internal_errno
-// if LIBC_COPT_PUBLIC_PACKAGING is not defined.
-#define libc_errno LIBC_NAMESPACE::__llvmlibc_internal_errno
+// Integration tests should:
+// - DO NOT #include "src/errno/libc_errno.h"
+// - #include <errno.h>
+// - Use regular `errno` in the code
+// - Still depend on libc.src.errno.errno
 
+namespace LIBC_NAMESPACE {
+struct Errno {
+  void operator=(int);
+  operator int();
+};
 } // namespace LIBC_NAMESPACE
-#endif
+
+extern LIBC_NAMESPACE::Errno libc_errno;
 
 #endif // LLVM_LIBC_SRC_ERRNO_LIBC_ERRNO_H
diff --git a/libc/test/src/errno/errno_test.cpp b/libc/test/src/errno/errno_test.cpp
index 33185c2bcf6f549..876ebfc0ac2696f 100644
--- a/libc/test/src/errno/errno_test.cpp
+++ b/libc/test/src/errno/errno_test.cpp
@@ -12,5 +12,5 @@
 TEST(LlvmLibcErrnoTest, Basic) {
   int test_val = 123;
   libc_errno = test_val;
-  ASSERT_EQ(test_val, libc_errno);
+  ASSERT_ERRNO_EQ(test_val);
 }
diff --git a/libc/test/src/stdlib/StrtolTest.h b/libc/test/src/stdlib/StrtolTest.h
index 8f1723b0386120c..50ed4cca3950e99 100644
--- a/libc/test/src/stdlib/StrtolTest.h
+++ b/libc/test/src/stdlib/StrtolTest.h
@@ -331,8 +331,7 @@ struct StrtoTest : public LIBC_NAMESPACE::testing::Test {
               ((is_signed_v<ReturnT> && sizeof(ReturnT) == 4)
                    ? T_MAX
                    : ReturnT(0xFFFFFFFF)));
-    ASSERT_EQ(libc_errno,
-              is_signed_v<ReturnT> && sizeof(ReturnT) == 4 ? ERANGE : 0);
+    ASSERT_ERRNO_EQ(is_signed_v<ReturnT> && sizeof(ReturnT) == 4 ? ERANGE : 0);
     EXPECT_EQ(str_end - max_32_bit_value, ptrdiff_t(10));
 
     const char *negative_max_32_bit_value = "-0xFFFFFFFF";
@@ -341,8 +340,7 @@ struct StrtoTest : public LIBC_NAMESPACE::testing::Test {
               ((is_signed_v<ReturnT> && sizeof(ReturnT) == 4)
                    ? T_MIN
                    : -ReturnT(0xFFFFFFFF)));
-    ASSERT_EQ(libc_errno,
-              is_signed_v<ReturnT> && sizeof(ReturnT) == 4 ? ERANGE : 0);
+    ASSERT_ERRNO_EQ(is_signed_v<ReturnT> && sizeof(ReturnT) == 4 ? ERANGE : 0);
     EXPECT_EQ(str_end - negative_max_32_bit_value, ptrdiff_t(11));
 
     // Max size for signed 32 bit numbers
@@ -368,8 +366,7 @@ struct StrtoTest : public LIBC_NAMESPACE::testing::Test {
               (is_signed_v<ReturnT> || sizeof(ReturnT) < 8
                    ? T_MAX
                    : ReturnT(0xFFFFFFFFFFFFFFFF)));
-    ASSERT_EQ(libc_errno,
-              (is_signed_v<ReturnT> || sizeof(ReturnT) < 8 ? ERANGE : 0));
+    ASSERT_ERRNO_EQ((is_signed_v<ReturnT> || sizeof(ReturnT) < 8 ? ERANGE : 0));
     EXPECT_EQ(str_end - max_64_bit_value, ptrdiff_t(18));
 
     // See the end of CleanBase10Decode for an explanation of how this large
@@ -381,8 +378,7 @@ struct StrtoTest : public LIBC_NAMESPACE::testing::Test {
         (is_signed_v<ReturnT>
              ? T_MIN
              : (sizeof(ReturnT) < 8 ? T_MAX : -ReturnT(0xFFFFFFFFFFFFFFFF))));
-    ASSERT_EQ(libc_errno,
-              (is_signed_v<ReturnT> || sizeof(ReturnT) < 8 ? ERANGE : 0));
+    ASSERT_ERRNO_EQ((is_signed_v<ReturnT> || sizeof(ReturnT) < 8 ? ERANGE : 0));
     EXPECT_EQ(str_end - negative_max_64_bit_value, ptrdiff_t(19));
 
     // Max size for signed 64 bit numbers
diff --git a/libc/test/src/sys/mman/linux/madvise_test.cpp b/libc/test/src/sys/mman/linux/madvise_test.cpp
index 83c73f5454de1bc..e45cf19f8913ec7 100644
--- a/libc/test/src/sys/mman/linux/madvise_test.cpp
+++ b/libc/test/src/sys/mman/linux/madvise_test.cpp
@@ -23,7 +23,7 @@ TEST(LlvmLibcMadviseTest, NoError) {
   libc_errno = 0;
   void *addr = LIBC_NAMESPACE::mmap(nullptr, alloc_size, PROT_READ,
                                     MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
-  EXPECT_EQ(0, libc_errno);
+  ASSERT_ERRNO_SUCCESS();
   EXPECT_NE(addr, MAP_FAILED);
 
   EXPECT_THAT(LIBC_NAMESPACE::madvise(addr, alloc_size, MADV_RANDOM),
diff --git a/libc/test/src/sys/mman/linux/mmap_test.cpp b/libc/test/src/sys/mman/linux/mmap_test.cpp
index 9b13b8bd8057f23..b996f26db8605f4 100644
--- a/libc/test/src/sys/mman/linux/mmap_test.cpp
+++ b/libc/test/src/sys/mman/linux/mmap_test.cpp
@@ -22,7 +22,7 @@ TEST(LlvmLibcMMapTest, NoError) {
   libc_errno = 0;
   void *addr = LIBC_NAMESPACE::mmap(nullptr, alloc_size, PROT_READ,
                                     MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
-  EXPECT_EQ(0, libc_errno);
+  ASSERT_ERRNO_SUCCESS();
   EXPECT_NE(addr, MAP_FAILED);
 
   int *array = reinterpret_cast<int *>(addr);
diff --git a/libc/test/src/sys/mman/linux/mprotect_test.cpp b/libc/test/src/sys/mman/linux/mprotect_test.cpp
index 7127f77714d6421..96f625984101dc4 100644
--- a/libc/test/src/sys/mman/linux/mprotect_test.cpp
+++ b/libc/test/src/sys/mman/linux/mprotect_test.cpp
@@ -24,7 +24,7 @@ TEST(LlvmLibcMProtectTest, NoError) {
   libc_errno = 0;
   void *addr = LIBC_NAMESPACE::mmap(nullptr, alloc_size, PROT_READ,
                                     MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
-  EXPECT_EQ(0, libc_errno);
+  ASSERT_ERRNO_SUCCESS();
   EXPECT_NE(addr, MAP_FAILED);
 
   int *array = reinterpret_cast<int *>(addr);
diff --git a/libc/test/src/sys/mman/linux/posix_madvise_test.cpp b/libc/test/src/sys/mman/linux/posix_madvise_test.cpp
index 59cf01ac746959d..d20db69042b7adc 100644
--- a/libc/test/src/sys/mman/linux/posix_madvise_test.cpp
+++ b/libc/test/src/sys/mman/linux/posix_madvise_test.cpp
@@ -23,7 +23,7 @@ TEST(LlvmLibcPosixMadviseTest, NoError) {
   libc_errno = 0;
   void *addr = LIBC_NAMESPACE::mmap(nullptr, alloc_size, PROT_READ,
                                     MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
-  EXPECT_EQ(0, libc_errno);
+  ASSERT_ERRNO_SUCCESS();
   EXPECT_NE(addr, MAP_FAILED);
 
   EXPECT_EQ(LIBC_NAMESPACE::posix_madvise(addr, alloc_size, POSIX_MADV_RANDOM),
diff --git a/libc/test/src/time/asctime_r_test.cpp b/libc/test/src/time/asctime_r_test.cpp
index 1abaa135350c139..f3aadbb39de4d0d 100644
--- a/libc/test/src/time/asctime_r_test.cpp
+++ b/libc/test/src/time/asctime_r_test.cpp
@@ -27,17 +27,17 @@ static inline char *call_asctime_r(struct tm *tm_data, int year, int month,
 TEST(LlvmLibcAsctimeR, Nullptr) {
   char *result;
   result = LIBC_NAMESPACE::asctime_r(nullptr, nullptr);
-  ASSERT_EQ(EINVAL, libc_errno);
+  ASSERT_ERRNO_EQ(EINVAL);
   ASSERT_STREQ(nullptr, result);
 
   char buffer[TimeConstants::ASCTIME_BUFFER_SIZE];
   result = LIBC_NAMESPACE::asctime_r(nullptr, buffer);
-  ASSERT_EQ(EINVAL, libc_errno);
+  ASSERT_ERRNO_EQ(EINVAL);
   ASSERT_STREQ(nullptr, result);
 
   struct tm tm_data;
   result = LIBC_NAMESPACE::asctime_r(&tm_data, nullptr);
-  ASSERT_EQ(EINVAL, libc_errno);
+  ASSERT_ERRNO_EQ(EINVAL);
   ASSERT_STREQ(nullptr, result);
 }
 
diff --git a/libc/test/src/time/asctime_test.cpp b/libc/test/src/time/asctime_test.cpp
index 4b5ceb596aa467c..169a7463a3037dc 100644
--- a/libc/test/src/time/asctime_test.cpp
+++ b/libc/test/src/time/asctime_test.cpp
@@ -22,7 +22,7 @@ static inline char *call_asctime(struct tm *tm_data, int year, int month,
 TEST(LlvmLibcAsctime, Nullptr) {
   char *result;
   result = LIBC_NAMESPACE::asctime(nullptr);
-  ASSERT_EQ(EINVAL, libc_errno);
+  ASSERT_ERRNO_EQ(EINVAL);
   ASSERT_STREQ(nullptr, result);
 }
 
@@ -40,7 +40,7 @@ TEST(LlvmLibcAsctime, InvalidWday) {
                0,    // sec
                -1,   // wday
                0);   // yday
-  ASSERT_EQ(EINVAL, libc_errno);
+  ASSERT_ERRNO_EQ(EINVAL);
 
   // Test with wday = 7.
   call_asctime(&tm_data,
@@ -52,7 +52,7 @@ TEST(LlvmLibcAsctime, InvalidWday) {
                0,    // sec
                7,    // wday
                0);   // yday
-  ASSERT_EQ(EINVAL, libc_errno);
+  ASSERT_ERRNO_EQ(EINVAL);
 }
 
 // Months are from January to December. Test passing invalid value in month.
@@ -69,7 +69,7 @@ TEST(LlvmLibcAsctime, InvalidMonth) {
                0,    // sec
                4,    // wday
                0);   // yday
-  ASSERT_EQ(EINVAL, libc_errno);
+  ASSERT_ERRNO_EQ(EINVAL);
 
   // Test with month = 13.
   call_asctime(&tm_data,
@@ -81,7 +81,7 @@ TEST(LlvmLibcAsctime, InvalidMonth) {
                0,    // sec
                4,    // wday
                0);   // yday
-  ASSERT_EQ(EINVAL, libc_errno);
+  ASSERT_ERRNO_EQ(EINVAL);
 }
 
 TEST(LlvmLibcAsctime, ValidWeekdays) {
@@ -209,6 +209,6 @@ TEST(LlvmLibcAsctime, Max64BitYear) {
                         50,         // sec
                         2,          // wday
                         50);        // yday
-  ASSERT_EQ(EOVERFLOW, libc_errno);
+  ASSERT_ERRNO_EQ(EOVERFLOW);
   ASSERT_STREQ(nullptr, result);
 }

>From 5cc39f05f644e6e6b4e688eb9ff65c39abd37ddd Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue at google.com>
Date: Fri, 26 Jan 2024 10:54:18 -0500
Subject: [PATCH 04/11] Switch libc.test.src.unistd.getopt_test to
 UNIT_TEST_ONLY.

---
 libc/test/src/unistd/CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libc/test/src/unistd/CMakeLists.txt b/libc/test/src/unistd/CMakeLists.txt
index 8b9a8db374dd452..37cd9ea419bed21 100644
--- a/libc/test/src/unistd/CMakeLists.txt
+++ b/libc/test/src/unistd/CMakeLists.txt
@@ -432,7 +432,7 @@ add_libc_unittest(
 
 add_libc_test(
   getopt_test
-  HERMETIC_TEST_ONLY # Uses libc's own stderr
+  UNIT_TEST_ONLY # set_getopt_state only exposed for __internal__ target.
   SUITE
     libc_unistd_unittests
   SRCS

>From f1fc24b3031ff61872bcf9b8fac223d6e8c8bc45 Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue at google.com>
Date: Fri, 26 Jan 2024 11:54:54 -0500
Subject: [PATCH 05/11] Still use __internal__ target for hermetic tests on
 GPUs.

---
 libc/cmake/modules/LLVMLibCTestRules.cmake | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index d07cec047370ee7..b169d28194ec578 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -771,11 +771,18 @@ function(add_libc_hermetic_test test_name)
 
   list(REMOVE_DUPLICATES fq_deps_list)
 
+  if(LIBC_TARGET_ARCHITECTURE_IS_GPU)
+    # Hermetic tests for GPUs still need to link against __internal__ targets.
+    set(internal_targets TRUE)
+  else
+    set(internal_targets FALSE)
+  endif()
+
   # TODO: Instead of gathering internal object files from entrypoints,
   # collect the object files with public names of entrypoints.
   get_object_files_for_test(
       link_object_files skipped_entrypoints_list
-      INTERNAL FALSE
+      INTERNAL ${internal_targets}
       ${fq_deps_list}
   )
 

>From beaf40ab7b8cc1426d4bdb82de79b38566c58514 Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue at google.com>
Date: Fri, 26 Jan 2024 12:10:03 -0500
Subject: [PATCH 06/11] Address comments and fix missing parentheses.

---
 libc/cmake/modules/LLVMLibCTestRules.cmake | 2 +-
 libc/src/errno/libc_errno.cpp              | 3 +++
 libc/test/src/unistd/CMakeLists.txt        | 4 +++-
 3 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index b169d28194ec578..2c4a9bcdb1eec3b 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -774,7 +774,7 @@ function(add_libc_hermetic_test test_name)
   if(LIBC_TARGET_ARCHITECTURE_IS_GPU)
     # Hermetic tests for GPUs still need to link against __internal__ targets.
     set(internal_targets TRUE)
-  else
+  else()
     set(internal_targets FALSE)
   endif()
 
diff --git a/libc/src/errno/libc_errno.cpp b/libc/src/errno/libc_errno.cpp
index b501f949e4b73e5..8a282db6724275b 100644
--- a/libc/src/errno/libc_errno.cpp
+++ b/libc/src/errno/libc_errno.cpp
@@ -6,6 +6,9 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/properties/architectures.h"
+
 #include "libc_errno.h"
 
 #ifdef LIBC_TARGET_ARCH_IS_GPU
diff --git a/libc/test/src/unistd/CMakeLists.txt b/libc/test/src/unistd/CMakeLists.txt
index 37cd9ea419bed21..059cff1cdc0c842 100644
--- a/libc/test/src/unistd/CMakeLists.txt
+++ b/libc/test/src/unistd/CMakeLists.txt
@@ -430,9 +430,11 @@ add_libc_unittest(
     libc.src.unistd.sysconf
 )
 
+# getopt_test requires access to set_getopt_state which is only available for
+# __internal__ targets, hence the use of UNIT_TEST_ONLY.
 add_libc_test(
   getopt_test
-  UNIT_TEST_ONLY # set_getopt_state only exposed for __internal__ target.
+  UNIT_TEST_ONLY
   SUITE
     libc_unistd_unittests
   SRCS

>From 0f65a11ca4d907bece67953328b0adca50aaed9c Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue at google.com>
Date: Fri, 26 Jan 2024 14:34:52 -0500
Subject: [PATCH 07/11] Update integration tests and provide a global errno for
 GPU.

---
 libc/cmake/modules/LLVMLibCTestRules.cmake | 11 ++++++++---
 libc/src/errno/libc_errno.cpp              | 12 ++++++++----
 libc/src/errno/libc_errno.h                |  4 ++--
 libc/test/src/string/CMakeLists.txt        | 12 ++++++++++++
 4 files changed, 30 insertions(+), 9 deletions(-)

diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index 2c4a9bcdb1eec3b..b5948b29970ddab 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -564,11 +564,16 @@ function(add_integration_test test_name)
 
   list(REMOVE_DUPLICATES fq_deps_list)
 
-  # TODO: Instead of gathering internal object files from entrypoints,
-  # collect the object files with public names of entrypoints.
+  if(LIBC_TARGET_ARCHITECTURE_IS_GPU)
+  # Hermetic tests for GPUs still need to link against __internal__ targets.
+    set(internal_targets TRUE)
+  else()
+    set(internal_targets FALSE)
+  endif()
+
   get_object_files_for_test(
       link_object_files skipped_entrypoints_list
-      INTERNAL FALSE
+      INTERNAL ${internal_targets}
       ${fq_deps_list}
   )
 
diff --git a/libc/src/errno/libc_errno.cpp b/libc/src/errno/libc_errno.cpp
index 8a282db6724275b..71ea4a27626a5c2 100644
--- a/libc/src/errno/libc_errno.cpp
+++ b/libc/src/errno/libc_errno.cpp
@@ -12,10 +12,14 @@
 #include "libc_errno.h"
 
 #ifdef LIBC_TARGET_ARCH_IS_GPU
-// If we are targeting the GPU we currently don't support 'errno'. We simply
-// consume it.
-void LIBC_NAMESPACE::Errno::operator=(int) {}
-LIBC_NAMESPACE::Errno::operator int() { return 0; }
+// LIBC_THREAD_LOCAL on GPU currently does nothing.  So essentially this is just
+// a global errno for gpu to use for now.
+extern "C" {
+LIBC_THREAD_LOCAL int __llvmlibc_gpu_errno;
+}
+
+void LIBC_NAMESPACE::Errno::operator=(int a) { __llvmlibc_gpu_errno = a; }
+LIBC_NAMESPACE::Errno::operator int() { return __llvmlibc_gpu_errno; }
 
 #elif !defined(LIBC_COPT_PUBLIC_PACKAGING)
 // This mode is for unit testing.  We just use our internal errno.
diff --git a/libc/src/errno/libc_errno.h b/libc/src/errno/libc_errno.h
index c19aadcb865e304..143aba5992baf2d 100644
--- a/libc/src/errno/libc_errno.h
+++ b/libc/src/errno/libc_errno.h
@@ -23,12 +23,12 @@
 
 // Unit and hermetic tests should:
 // - #include "src/errno/libc_errno.h"
-// - DO NOT #include <errno.h>
+// - NOT #include <errno.h>
 // - Only use `libc_errno` in the code
 // - Depend on libc.src.errno.errno
 
 // Integration tests should:
-// - DO NOT #include "src/errno/libc_errno.h"
+// - NOT #include "src/errno/libc_errno.h"
 // - #include <errno.h>
 // - Use regular `errno` in the code
 // - Still depend on libc.src.errno.errno
diff --git a/libc/test/src/string/CMakeLists.txt b/libc/test/src/string/CMakeLists.txt
index 6088289532d771d..a0c50abcee98d77 100644
--- a/libc/test/src/string/CMakeLists.txt
+++ b/libc/test/src/string/CMakeLists.txt
@@ -419,7 +419,18 @@ add_libc_test(
 )
 
 # Tests all implementations that can run on the target CPU.
+
+# TODO: For GPUs, hermetic tests still need to get linked against
+# __internal__ targets.  Update this test generator so that hermetic tests
+# work on GPUs again.
 function(add_libc_multi_impl_test name)
+  if(LIBC_TARGET_ARCHITECTURE_IS_GPU)
+  # Hermetic tests for GPUs still need to link against __internal__ targets.
+    set(unit_test_only "UNIT_TEST_ONLY")
+  else()
+    set(unit_test_only "")
+  endif()
+
   get_property(fq_implementations GLOBAL PROPERTY ${name}_implementations)
   foreach(fq_config_name IN LISTS fq_implementations)
     get_target_property(required_cpu_features ${fq_config_name} REQUIRE_CPU_FEATURES)
@@ -430,6 +441,7 @@ function(add_libc_multi_impl_test name)
       string(SUBSTRING ${fq_config_name} ${name_loc} -1 target_name)
       add_libc_test(
         ${target_name}_test
+        ${unit_test_only}
         SUITE
           libc-string-tests
         COMPILE_OPTIONS

>From fdd072d7da639f7196cc319cffb82511d8f665f5 Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue at google.com>
Date: Sat, 27 Jan 2024 12:05:08 -0500
Subject: [PATCH 08/11] Enable C symbol atexit for nVidia GPU target for
 testing.

---
 libc/src/stdlib/CMakeLists.txt | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index a4d51fb9a11eef1..bf3b2c321ca9fda 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -363,6 +363,13 @@ add_entrypoint_object(
     libc.src.__support.OSUtil.osutil
 )
 
+# Tests for nVidia GPUs need to be compiled with -fno-use-cxa-atexit, so we need
+# to export the C symbol `atexit` also for __internal__ targets.
+set(atexit_compile_options "")
+if(LIBC_GPU_TARGET_ARCHITECTURE_IS_NVPTX)
+  set(atexit_compile_options "-DLIBC_COPT_PUBLIC_PACKAGING")
+endif()
+
 add_entrypoint_object(
   atexit
   SRCS
@@ -371,6 +378,8 @@ add_entrypoint_object(
     atexit.h
   CXX_STANDARD
     20 # For constinit of the atexit callback list.
+  COMPILE_OPTIONS
+    ${atexit_compile_options}
   DEPENDS
     libc.src.__support.fixedvector
     libc.src.__support.blockstore

>From 2d59b7937f04f5a11d56d1f84bae2e0689124630 Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue at google.com>
Date: Sat, 27 Jan 2024 12:19:53 -0500
Subject: [PATCH 09/11] Only set LIBC_COPT_PUBLIC_PACKAGING before atexit
 definition.

---
 libc/src/stdlib/CMakeLists.txt | 2 +-
 libc/src/stdlib/atexit.cpp     | 6 ++++++
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index bf3b2c321ca9fda..f56649877a1ae58 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -367,7 +367,7 @@ add_entrypoint_object(
 # to export the C symbol `atexit` also for __internal__ targets.
 set(atexit_compile_options "")
 if(LIBC_GPU_TARGET_ARCHITECTURE_IS_NVPTX)
-  set(atexit_compile_options "-DLIBC_COPT_PUBLIC_PACKAGING")
+  set(atexit_compile_options "-DLIBC_ATEXIT_EXPORT_C_SYMBOL")
 endif()
 
 add_entrypoint_object(
diff --git a/libc/src/stdlib/atexit.cpp b/libc/src/stdlib/atexit.cpp
index 10dff42b1be9254..b691682eca00fe6 100644
--- a/libc/src/stdlib/atexit.cpp
+++ b/libc/src/stdlib/atexit.cpp
@@ -78,6 +78,12 @@ extern "C" int __cxa_atexit(AtExitCallback *callback, void *payload, void *) {
   return add_atexit_unit({callback, payload});
 }
 
+#if !defined(LIBC_COPT_PUBLIC_PACKAGING) && defined(LIBC_ATEXIT_EXPORT_C_SYMBOL)
+// Tests for nVidia GPUs need to be compiled with -fno-use-cxa-atexit, so we
+// need to export the C symbol `atexit` also for __internal__ targets.
+#define LIBC_COPT_PUBLIC_PACKAGING
+#endif
+
 LLVM_LIBC_FUNCTION(int, atexit, (StdCAtExitCallback * callback)) {
   return add_atexit_unit(
       {&stdc_at_exit_func, reinterpret_cast<void *>(callback)});

>From 0b5ebb99fbf332c71105b01a697ade056785a51f Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue at google.com>
Date: Sat, 27 Jan 2024 13:08:53 -0500
Subject: [PATCH 10/11] Another attempt at providing atexit C symbol for nVidia
 targets.

---
 libc/src/stdlib/atexit.cpp | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/libc/src/stdlib/atexit.cpp b/libc/src/stdlib/atexit.cpp
index b691682eca00fe6..918da829b1abc4b 100644
--- a/libc/src/stdlib/atexit.cpp
+++ b/libc/src/stdlib/atexit.cpp
@@ -78,15 +78,19 @@ extern "C" int __cxa_atexit(AtExitCallback *callback, void *payload, void *) {
   return add_atexit_unit({callback, payload});
 }
 
-#if !defined(LIBC_COPT_PUBLIC_PACKAGING) && defined(LIBC_ATEXIT_EXPORT_C_SYMBOL)
-// Tests for nVidia GPUs need to be compiled with -fno-use-cxa-atexit, so we
-// need to export the C symbol `atexit` also for __internal__ targets.
-#define LIBC_COPT_PUBLIC_PACKAGING
-#endif
-
 LLVM_LIBC_FUNCTION(int, atexit, (StdCAtExitCallback * callback)) {
   return add_atexit_unit(
       {&stdc_at_exit_func, reinterpret_cast<void *>(callback)});
 }
 
 } // namespace LIBC_NAMESPACE
+
+#if !defined(LIBC_COPT_PUBLIC_PACKAGING) && defined(LIBC_ATEXIT_EXPORT_C_SYMBOL)
+// Tests for nVidia GPUs need to be compiled with -fno-use-cxa-atexit, so we
+// need to export the C symbol `atexit` also for __internal__ targets.
+extern "C" {
+int atexit(StdCAtExitCallback *callback) {
+  return LIBC_NAMESPACE::atexit(callback);
+}
+}
+#endif // LIBC_ATEXIT_EXPORT_C_SYMBOL

>From 1682b6a633107511f3b0ef3c6f3636a6da828217 Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue at google.com>
Date: Sat, 27 Jan 2024 13:12:32 -0500
Subject: [PATCH 11/11] Fix missing LIBC_NAMESPACE.

---
 libc/src/stdlib/atexit.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libc/src/stdlib/atexit.cpp b/libc/src/stdlib/atexit.cpp
index 918da829b1abc4b..769c231e0972941 100644
--- a/libc/src/stdlib/atexit.cpp
+++ b/libc/src/stdlib/atexit.cpp
@@ -89,7 +89,7 @@ LLVM_LIBC_FUNCTION(int, atexit, (StdCAtExitCallback * callback)) {
 // Tests for nVidia GPUs need to be compiled with -fno-use-cxa-atexit, so we
 // need to export the C symbol `atexit` also for __internal__ targets.
 extern "C" {
-int atexit(StdCAtExitCallback *callback) {
+int atexit(LIBC_NAMESPACE::StdCAtExitCallback *callback) {
   return LIBC_NAMESPACE::atexit(callback);
 }
 }



More information about the libc-commits mailing list