[libc-commits] [libc] 7410007 - [libc] Allow hermetic tests to use external startup objects (#212557)
via libc-commits
libc-commits at lists.llvm.org
Thu Oct 8 02:03:33 PDT 2026
Author: Simi Pallipurath
Date: 2026-10-08T10:03:28+01:00
New Revision: 7410007b21584c391df9022981f5e724d95fe73e
URL: https://github.com/llvm/llvm-project/commit/7410007b21584c391df9022981f5e724d95fe73e
DIFF: https://github.com/llvm/llvm-project/commit/7410007b21584c391df9022981f5e724d95fe73e.diff
LOG: [libc] Allow hermetic tests to use external startup objects (#212557)
Adds LLVM_LIBC_HERMETIC_TEST_USE_INTERNAL_STARTUP to control whether
libc hermetic tests link against LLVM libc’s own crt1.o.
When the option is OFF, hermetic tests no longer require or link
libc.startup.${LIBC_TARGET_OS}.crt1. This allows baremetal
configurations to provide startup externally through
LIBC_TEST_LINK_OPTIONS_DEFAULT while still running hermetic tests.
For baremetal external-startup mode, the patch also adds the required
process startup/termination dependencies and ensures exit uses the
public packaging object, since external startup code may reference the
public C exit symbol.
Tested on Arm Toolchain for Embedded:
https://github.com/arm/arm-toolchain
Assisted-by: Codex
Added:
libc/test/src/__support/external_startup_link_test.cpp
libc/test/src/__support/minimal_external_crt1_for_test.cpp
Modified:
libc/CMakeLists.txt
libc/cmake/modules/LLVMLibCTestRules.cmake
libc/test/src/__support/CMakeLists.txt
Removed:
################################################################################
diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt
index f44a84d5ae48a..86ddcd2df264f 100644
--- a/libc/CMakeLists.txt
+++ b/libc/CMakeLists.txt
@@ -262,6 +262,9 @@ if(LIBC_ENABLE_COVERAGE)
CACHE INTERNAL "Clang profile runtime")
endif()
endif()
+option(LLVM_LIBC_HERMETIC_TEST_USE_INTERNAL_STARTUP
+ "Link hermetic tests against LLVM libc's own startup object"
+ ON)
option(LLVM_LIBC_IMPLEMENTATION_DEFINED_TEST_BEHAVIOR "Build LLVM libc tests assuming our implementation-defined behavior" ON)
option(LLVM_LIBC_ENABLE_LINTING "Enables linting of libc source files" OFF)
option(LLVM_LIBC_ALL_HEADERS "Outputs all functions in header files, regardless of whether they are enabled on this target" OFF)
diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index 917e0ce2a9b05..ef157c8456a3a 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -210,6 +210,14 @@ function(get_object_files_for_test result skipped_entrypoints_list)
if(fq_target_name STREQUAL "libc.test.include.issignaling_c_test" OR fq_target_name STREQUAL "libc.test.include.iscanonical_c_test")
string(REPLACE ".__internal__" "" object_file_raw ${object_file_raw})
endif()
+ # Adding libc.src.stdlib.exit normally contributes the internal entrypoint
+ # object. External baremetal startup code can reference the public C exit
+ # symbol, so use the public-packaging object for exit in this configuration.
+ if(dep STREQUAL "libc.src.stdlib.exit" AND
+ NOT LLVM_LIBC_HERMETIC_TEST_USE_INTERNAL_STARTUP AND
+ LIBC_TARGET_OS_IS_BAREMETAL)
+ string(REPLACE ".__internal__" "" object_file_raw ${object_file_raw})
+ endif()
list(APPEND dep_obj ${object_file_raw})
endif()
endif()
@@ -751,20 +759,50 @@ endfunction()
# COMPILE_OPTIONS <list of special compile options for the test>
# LINK_LIBRARIES <list of linking libraries for this target>
# LOADER_ARGS <list of special args to loaders (like the GPU loader)>
+# STARTUP <startup object target used when internal startup is disabled>
# )
function(add_libc_hermetic test_name)
- if(NOT TARGET libc.startup.${LIBC_TARGET_OS}.crt1)
- message(VERBOSE "Skipping ${fq_target_name} as it is not available on ${LIBC_TARGET_OS}.")
- return()
- endif()
+ get_fq_target_name(${test_name} fq_target_name)
+ get_fq_target_name(${test_name}.libc fq_libc_target_name)
+
cmake_parse_arguments(
"HERMETIC_TEST"
"IS_GPU_BENCHMARK;NO_RUN_POSTBUILD;C_TEST" # Optional arguments
- "SUITE;CXX_STANDARD" # Single value arguments
+ "SUITE;CXX_STANDARD;STARTUP" # Single value arguments
"SRCS;HDRS;DEPENDS;ARGS;ENV;COMPILE_OPTIONS;LINK_LIBRARIES;FLAGS;LOADER_ARGS" # Multi-value arguments
${ARGN}
)
+ set(startup_target libc.startup.${LIBC_TARGET_OS}.crt1)
+ set(startup_target_dep "")
+ if(LLVM_LIBC_HERMETIC_TEST_USE_INTERNAL_STARTUP)
+ if(HERMETIC_TEST_STARTUP)
+ message(FATAL_ERROR
+ "Hermetic test ${fq_target_name} provides an external STARTUP target "
+ "while internal startup is enabled.")
+ endif()
+ if(NOT TARGET ${startup_target})
+ if(LIBC_CMAKE_VERBOSE_LOGGING)
+ message(STATUS "Skipping ${fq_target_name} as ${startup_target} is not available on ${LIBC_TARGET_OS}.")
+ endif()
+ return()
+ endif()
+ set(startup_target_dep ${startup_target})
+ elseif(HERMETIC_TEST_STARTUP)
+ get_fq_dep_name(startup_target_dep ${HERMETIC_TEST_STARTUP})
+ if(NOT TARGET ${startup_target_dep})
+ message(FATAL_ERROR
+ "Hermetic test ${fq_target_name} startup target ${startup_target_dep} does not exist.")
+ endif()
+ endif()
+
+ if(NOT startup_target_dep AND NOT LIBC_TEST_LINK_OPTIONS_DEFAULT)
+ if(LIBC_CMAKE_VERBOSE_LOGGING)
+ message(STATUS "Skipping ${fq_target_name} as it has no startup provider.")
+ endif()
+ return()
+ endif()
+
if(NOT HERMETIC_TEST_SUITE)
message(FATAL_ERROR "SUITE not specified for ${fq_target_name}")
endif()
@@ -772,14 +810,11 @@ function(add_libc_hermetic test_name)
message(FATAL_ERROR "The SRCS list for add_integration_test is missing.")
endif()
- get_fq_target_name(${test_name} fq_target_name)
- get_fq_target_name(${test_name}.libc fq_libc_target_name)
-
get_fq_deps_list(fq_deps_list ${HERMETIC_TEST_DEPENDS})
list(APPEND fq_deps_list
# Hermetic tests use the platform's startup object. So, their deps also
# have to be collected.
- libc.startup.${LIBC_TARGET_OS}.crt1
+ ${startup_target_dep}
# We always add the memory functions objects. This is because the
# compiler's codegen can emit calls to the C memory functions.
libc.src.__support.StringUtil.error_to_string
@@ -818,6 +853,16 @@ function(add_libc_hermetic test_name)
)
endif()
+ if(NOT LLVM_LIBC_HERMETIC_TEST_USE_INTERNAL_STARTUP AND LIBC_TARGET_OS_IS_BAREMETAL)
+ # External baremetal startup objects still need libc's process startup and
+ # termination entrypoints, even when the test itself does not depend on them.
+ list(APPEND fq_deps_list
+ libc.src.stdlib.atexit
+ libc.src.stdlib.exit
+ libc.startup.baremetal.init
+ )
+ endif()
+
if(libc.src.compiler.__stack_chk_fail IN_LIST TARGET_LLVMLIBC_ENTRYPOINTS)
# __stack_chk_fail should always be included if supported to allow building
# libc with the stack protector enabled.
@@ -1003,7 +1048,7 @@ function(add_libc_hermetic test_name)
target_link_libraries(
${fq_build_target_name}
PRIVATE
- libc.startup.${LIBC_TARGET_OS}.crt1
+ ${startup_target_dep}
${HERMETIC_TEST_LINK_LIBRARIES}
${fq_target_name}.__libc__
${coverage_link_libs}
diff --git a/libc/test/src/__support/CMakeLists.txt b/libc/test/src/__support/CMakeLists.txt
index 2c92302d04b2c..c71385d98898b 100644
--- a/libc/test/src/__support/CMakeLists.txt
+++ b/libc/test/src/__support/CMakeLists.txt
@@ -300,6 +300,26 @@ add_libc_test(
libc.src.__support.memory_size
)
+if(LIBC_TARGET_OS_IS_BAREMETAL AND
+ NOT LLVM_LIBC_HERMETIC_TEST_USE_INTERNAL_STARTUP)
+ add_object_library(
+ minimal_external_crt1_for_test
+ SRCS
+ minimal_external_crt1_for_test.cpp
+ )
+
+ add_libc_hermetic(
+ external_startup_link_test
+ NO_RUN_POSTBUILD
+ STARTUP
+ .minimal_external_crt1_for_test
+ SUITE
+ libc-support-tests
+ SRCS
+ external_startup_link_test.cpp
+ )
+endif()
+
add_libc_test(
str_to_float_comparison_test
NO_RUN_POSTBUILD
diff --git a/libc/test/src/__support/external_startup_link_test.cpp b/libc/test/src/__support/external_startup_link_test.cpp
new file mode 100644
index 0000000000000..749e34c3f7792
--- /dev/null
+++ b/libc/test/src/__support/external_startup_link_test.cpp
@@ -0,0 +1,8 @@
+// This file intentionally contains no test cases. CMake's add_executable
+// requires a source file, but successfully linking the executable is the test.
+// When LLVM_LIBC_HERMETIC_TEST_USE_INTERNAL_STARTUP is disabled, a hermetic
+// test must link the minimal external startup object supplied by this test and
+// must not link LLVM libc's internal crt1. Both startup objects define _start,
+// so accidentally linking the internal crt1 as well causes a duplicate-symbol
+// error. A successful link therefore verifies that disabling internal startup
+// selects only the provided minimal_external_crt1_for_test.cpp object.
diff --git a/libc/test/src/__support/minimal_external_crt1_for_test.cpp b/libc/test/src/__support/minimal_external_crt1_for_test.cpp
new file mode 100644
index 0000000000000..9bf1852fb6349
--- /dev/null
+++ b/libc/test/src/__support/minimal_external_crt1_for_test.cpp
@@ -0,0 +1,7 @@
+// A minimal external crt1 object used only by link tests.
+// Executables linked with this object must not be run.
+
+extern "C" [[noreturn]] void _start() {
+ for (;;) {
+ }
+}
More information about the libc-commits
mailing list