Skip to content

Commit

Permalink
[libc] Add rule named add_libc_hermetic_test which adds a hermetic …
Browse files Browse the repository at this point in the history
…test.

A convenience wrapper name `add_libc_test` is also added which adds both
a unit test and a hermetic test. The ctype tests have been switched over
to use add_libc_test.

Reviewed By: jhuber6

Differential Revision: https://reviews.llvm.org/D148756
  • Loading branch information
Siva Chandra Reddy committed Apr 24, 2023
1 parent e831f73 commit 1e8960c
Show file tree
Hide file tree
Showing 10 changed files with 450 additions and 163 deletions.
4 changes: 4 additions & 0 deletions libc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,15 @@ option(LLVM_LIBC_ENABLE_LINTING "Enables linting of libc source files" OFF)
option(LIBC_GPU_BUILD "Build libc for the GPU. All CPU build options will be ignored." OFF)
set(LIBC_TARGET_TRIPLE "" CACHE STRING "The target triple for the libc build.")

set(LIBC_ENABLE_UNITTESTS ON)
set(LIBC_ENABLE_HERMETIC_TESTS ON)

# Defines LIBC_TARGET_ARCHITECTURE and associated macros.
include(LLVMLibCArchitectures)

if(LIBC_TARGET_ARCHITECTURE_IS_GPU)
include(prepare_libc_gpu_build)
set(LIBC_ENABLE_UNITTESTS OFF)
endif()

include(LLVMLibCCheckMPFR)
Expand Down
165 changes: 159 additions & 6 deletions libc/cmake/modules/LLVMLibCTestRules.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function(create_libc_unittest fq_target_name)

cmake_parse_arguments(
"LIBC_UNITTEST"
"NO_RUN_POSTBUILD;NO_LIBC_UNITTEST_TEST_MAIN" # Optional arguments
"NO_RUN_POSTBUILD" # Optional arguments
"SUITE;CXX_STANDARD" # Single value arguments
"SRCS;HDRS;DEPENDS;COMPILE_OPTIONS;LINK_LIBRARIES;FLAGS" # Multi-value arguments
${ARGN}
Expand Down Expand Up @@ -179,11 +179,7 @@ function(create_libc_unittest fq_target_name)
)

# LibcUnitTest should not depend on anything in LINK_LIBRARIES.
if(NO_LIBC_UNITTEST_TEST_MAIN)
list(APPEND link_libraries LibcUnitTest)
else()
list(APPEND link_libraries LibcUnitTest LibcUnitTestMain)
endif()
list(APPEND link_libraries LibcTestMain LibcUnitTest)

target_link_libraries(${fq_build_target_name} PRIVATE ${link_libraries})

Expand Down Expand Up @@ -386,6 +382,8 @@ function(add_libc_fuzzer target_name)

endfunction(add_libc_fuzzer)

# DEPRECATED: Use add_hermetic_test instead.
#
# Rule to add an integration test. An integration test is like a unit test
# but does not use the system libc. Not even the startup objects from the
# system libc are linked in to the final executable. The final exe is fully
Expand Down Expand Up @@ -544,3 +542,158 @@ function(add_integration_test test_name)
)
add_dependencies(${INTEGRATION_TEST_SUITE} ${fq_target_name})
endfunction(add_integration_test)

set(LIBC_HERMETIC_TEST_COMPILE_OPTIONS ${LIBC_COMPILE_OPTIONS_DEFAULT}
-fpie -ffreestanding -fno-exceptions -fno-rtti)
# The GPU build requires overriding the default CMake triple and architecture.
if(LIBC_GPU_TARGET_ARCHITECTURE_IS_AMDGPU)
list(APPEND LIBC_HERMETIC_TEST_COMPILE_OPTIONS
-mcpu=${LIBC_GPU_TARGET_ARCHITECTURE} -flto --target=${LIBC_GPU_TARGET_TRIPLE})
elseif(LIBC_GPU_TARGET_ARCHITECTURE_IS_NVPTX)
get_nvptx_compile_options(nvptx_options ${LIBC_GPU_TARGET_ARCHITECTURE})
list(APPEND ${nvptx_options} --target=${LIBC_GPU_TARGET_TRIPLE})
endif()

# Rule to add a hermetic test. A hermetic test is one whose executable is fully
# statically linked and consists of pieces drawn only from LLVM's libc. Nothing,
# including the startup objects, come from the system libc.
#
# Usage:
# add_libc_hermetic_test(
# <target name>
# SUITE <the suite to which the test should belong>
# SRCS <src1.cpp> [src2.cpp ...]
# HDRS [hdr1.cpp ...]
# DEPENDS <list of entrypoint or other object targets>
# ARGS <list of command line arguments to be passed to the test>
# ENV <list of environment variables to set before running the test>
# COMPILE_OPTIONS <list of special compile options for the test>
# LOADER_ARGS <list of special args to loaders (like the GPU loader)>
# )
function(add_libc_hermetic_test 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()
cmake_parse_arguments(
"HERMETIC_TEST"
"" # No optional arguments
"SUITE" # Single value arguments
"SRCS;HDRS;DEPENDS;ARGS;ENV;COMPILE_OPTIONS;LOADER_ARGS" # Multi-value arguments
${ARGN}
)

if(NOT HERMETIC_TEST_SUITE)
message(FATAL_ERROR "SUITE not specified for ${fq_target_name}")
endif()
if(NOT HERMETIC_TEST_SRCS)
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
# We always add the memory functions objects. This is because the
# compiler's codegen can emit calls to the C memory functions.
libc.src.string.bcmp
libc.src.string.bzero
libc.src.string.memcmp
libc.src.string.memcpy
libc.src.string.memmove
libc.src.string.memset
)
list(REMOVE_DUPLICATES fq_deps_list)

# 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)
set(msg "Skipping unittest ${fq_target_name} as it has missing deps: "
"${skipped_entrypoints_list}.")
message(STATUS ${msg})
return()
endif()
list(REMOVE_DUPLICATES link_object_files)

# Make a library of all deps
add_library(
${fq_target_name}.__libc__
STATIC
EXCLUDE_FROM_ALL
${link_object_files}
)
set_target_properties(${fq_target_name}.__libc__
PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
set_target_properties(${fq_target_name}.__libc__
PROPERTIES ARCHIVE_OUTPUT_NAME ${fq_target_name}.libc)

set(fq_build_target_name ${fq_target_name}.__build__)
add_executable(
${fq_build_target_name}
EXCLUDE_FROM_ALL
# The NVIDIA 'nvlink' linker does not currently support static libraries.
$<$<BOOL:${LIBC_GPU_TARGET_ARCHITECTURE_IS_NVPTX}>:${link_object_files}>
${HERMETIC_TEST_SRCS}
${HERMETIC_TEST_HDRS}
)
set_target_properties(${fq_build_target_name}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
#OUTPUT_NAME ${fq_target_name}
)
target_include_directories(
${fq_build_target_name}
PRIVATE
${LIBC_SOURCE_DIR}
${LIBC_BUILD_DIR}
${LIBC_BUILD_DIR}/include
)
target_compile_options(${fq_build_target_name}
PRIVATE ${LIBC_HERMETIC_TEST_COMPILE_OPTIONS} ${HERMETIC_TEST_COMPILE_OPTIONS})

target_link_options(${fq_build_target_name} PRIVATE -nostdlib -static)
target_link_libraries(
${fq_build_target_name}
libc.startup.${LIBC_TARGET_OS}.crt1
LibcTestMain LibcHermeticTest
# The NVIDIA 'nvlink' linker does not currently support static libraries.
$<$<NOT:$<BOOL:${LIBC_GPU_TARGET_ARCHITECTURE_IS_NVPTX}>>:${fq_target_name}.__libc__>)
add_dependencies(${fq_build_target_name}
LibcHermeticTest
${HERMETIC_TEST_DEPENDS})

# Tests on the GPU require an external loader utility to launch the kernel.
if(TARGET libc.utils.gpu.loader)
add_dependencies(${fq_build_target_name} libc.utils.gpu.loader)
get_target_property(gpu_loader_exe libc.utils.gpu.loader "EXECUTABLE")
endif()

set(test_cmd ${HERMETIC_TEST_ENV}
$<$<BOOL:${LIBC_TARGET_ARCHITECTURE_IS_GPU}>:${gpu_loader_exe}> ${HERMETIC_TEST_LOADER_ARGS}
$<TARGET_FILE:${fq_build_target_name}> ${HERMETIC_TEST_ARGS})
add_custom_target(
${fq_target_name}
COMMAND ${test_cmd}
COMMAND_EXPAND_LISTS
COMMENT "Running hermetic test ${fq_target_name}"
)

add_dependencies(${HERMETIC_TEST_SUITE} ${fq_target_name})
add_dependencies(libc-hermetic-tests ${fq_target_name})
endfunction(add_libc_hermetic_test)

# A convenience function to add both a unit test as well as a hermetic test.
function(add_libc_test test_name)
if(LIBC_ENABLE_UNITTESTS)
add_libc_unittest(${test_name}.__unit__ ${ARGN})
endif()
if(LIBC_ENABLE_HERMETIC_TESTS)
add_libc_hermetic_test(${test_name}.__hermetic__ ${ARGN})
endif()
endfunction(add_libc_test)
3 changes: 2 additions & 1 deletion libc/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
add_custom_target(check-libc)
add_custom_target(libc-unit-tests)
add_dependencies(check-libc libc-unit-tests)
add_custom_target(libc-hermetic-tests)
add_dependencies(check-libc libc-unit-tests libc-hermetic-tests)

add_custom_target(exhaustive-check-libc)
add_custom_target(libc-long-running-tests)
Expand Down
46 changes: 30 additions & 16 deletions libc/test/UnitTest/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,38 +1,52 @@
set(libc_unit_test_srcs
ExecuteFunction.h
set(libc_test_srcs_common
Test.h
LibcTest.cpp
LibcTest.h
TestLogger.cpp
TestLogger.h
)

set(libc_death_test_srcs ExecuteFunction.h)
if(${LIBC_TARGET_OS} STREQUAL "linux")
list(APPEND libc_unit_test_srcs ExecuteFunctionUnix.cpp)
list(APPEND libc_death_test_srcs
LibcDeathTestExecutors.cpp ExecuteFunctionUnix.cpp)
endif()

add_library(
LibcUnitTest
${libc_unit_test_srcs}
${libc_test_srcs_common}
${libc_death_test_srcs}
)

target_include_directories(LibcUnitTest PUBLIC ${LIBC_SOURCE_DIR})
add_dependencies(
LibcUnitTest
libc.src.__support.CPP.string
libc.src.__support.CPP.string_view
libc.src.__support.CPP.type_traits
libc.src.__support.OSUtil.osutil
libc.src.__support.uint128)
add_library(
LibcHermeticTest
${libc_test_srcs_common}
HermeticTestUtils.cpp
)

foreach(lib LibcUnitTest LibcHermeticTest)
target_include_directories(${lib} PUBLIC ${LIBC_SOURCE_DIR})
target_compile_options(${lib} PRIVATE ${LIBC_HERMETIC_TEST_COMPILE_OPTIONS}
-fno-exceptions -fno-rtti)
add_dependencies(${lib}
libc.src.__support.CPP.string
libc.src.__support.CPP.string_view
libc.src.__support.CPP.type_traits
libc.src.__support.OSUtil.osutil
libc.src.__support.uint128
)
endforeach()

target_compile_options(LibcHermeticTest PRIVATE -ffreestanding -nostdlib -nostdlib++)

add_library(
LibcUnitTestMain
LibcTestMain
LibcTestMain.cpp
)

target_include_directories(LibcUnitTestMain PUBLIC ${LIBC_SOURCE_DIR})
add_dependencies(LibcUnitTestMain LibcUnitTest)
target_link_libraries(LibcUnitTestMain PUBLIC LibcUnitTest)
target_include_directories(LibcTestMain PUBLIC ${LIBC_SOURCE_DIR})
target_compile_options(LibcTestMain PRIVATE -fno-exceptions -fno-rtti)
add_dependencies(LibcTestMain LibcUnitTest)

add_header_library(
string_utils
Expand Down
91 changes: 91 additions & 0 deletions libc/test/UnitTest/HermeticTestUtils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//===-- Implementation of libc death test executors -----------------------===//
//
// 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 <stddef.h>
#include <stdint.h>

namespace __llvm_libc {

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);

} // namespace __llvm_libc

namespace {

// 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
// requires. Hence, as a work around for this problem, we use a simple allocator
// which just hands out continuous blocks from a statically allocated chunk of
// memory.
static uint8_t memory[16384];
static uint8_t *ptr = memory;

} // anonymous namespace

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 __llvm_libc::bcmp(lhs, rhs, count);
}
void bzero(void *ptr, size_t count) { __llvm_libc::bzero(ptr, count); }
int memcmp(const void *lhs, const void *rhs, size_t count) {
return __llvm_libc::memcmp(lhs, rhs, count);
}
void *memcpy(void *__restrict dst, const void *__restrict src, size_t count) {
return __llvm_libc::memcpy(dst, src, count);
}
void *memmove(void *dst, const void *src, size_t count) {
return __llvm_libc::memmove(dst, src, count);
}
void *memset(void *ptr, int value, size_t count) {
return __llvm_libc::memset(ptr, value, count);
}

void *malloc(size_t s) {
void *mem = ptr;
ptr += s;
return mem;
}

void free(void *) {}

void *realloc(void *ptr, size_t s) {
free(ptr);
return malloc(s);
}

// The unit test framework uses pure virtual functions. Since hermetic tests
// cannot depend C++ runtime libraries, implement dummy functions to support
// the virtual function runtime.
void __cxa_pure_virtual() {
// A pure virtual being called is an error so we just trap.
__builtin_trap();
}

// Integration tests are linked with -nostdlib. BFD linker expects
// __dso_handle when -nostdlib is used.
void *__dso_handle = nullptr;

} // extern "C"

void operator delete(void *) {
// The libc runtime should not use the global delete operator. Hence,
// we just trap here to catch any such accidental usages.
__builtin_trap();
}
Loading

0 comments on commit 1e8960c

Please sign in to comment.