blob: 33ff545110bd7f32312d64eb280d07abbc35c239 [file] [log] [blame] [edit]
# Copyright 2019 The Marl Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cmake_minimum_required(VERSION 3.0)
include(cmake/parse_version.cmake)
parse_version("${CMAKE_CURRENT_SOURCE_DIR}/CHANGES.md" MARL)
set(CMAKE_CXX_STANDARD 11)
project(Marl
VERSION "${MARL_VERSION_MAJOR}.${MARL_VERSION_MINOR}.${MARL_VERSION_PATCH}"
LANGUAGES C CXX ASM
)
if (EMSCRIPTEN)
add_compile_options(-O3 -pthread)
endif()
include(CheckCXXSourceCompiles)
# MARL_IS_SUBPROJECT is 1 if added via add_subdirectory() from another project.
get_directory_property(MARL_IS_SUBPROJECT PARENT_DIRECTORY)
if(MARL_IS_SUBPROJECT)
set(MARL_IS_SUBPROJECT 1)
endif()
###########################################################
# Options
###########################################################
function(option_if_not_defined name description default)
if(NOT DEFINED ${name})
option(${name} ${description} ${default})
endif()
endfunction()
option_if_not_defined(MARL_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
option_if_not_defined(MARL_BUILD_EXAMPLES "Build example applications" OFF)
option_if_not_defined(MARL_BUILD_TESTS "Build tests" OFF)
option_if_not_defined(MARL_BUILD_BENCHMARKS "Build benchmarks" OFF)
option_if_not_defined(MARL_BUILD_SHARED "Build marl as a shared / dynamic library (default static)" OFF)
option_if_not_defined(MARL_USE_PTHREAD_THREAD_LOCAL "Use pthreads for thread local storage" OFF)
option_if_not_defined(MARL_ASAN "Build marl with address sanitizer" OFF)
option_if_not_defined(MARL_MSAN "Build marl with memory sanitizer" OFF)
option_if_not_defined(MARL_TSAN "Build marl with thread sanitizer" OFF)
option_if_not_defined(MARL_UBSAN "Build marl with undefined-behavior sanitizer" OFF)
option_if_not_defined(MARL_INSTALL "Create marl install target" OFF)
option_if_not_defined(MARL_FULL_BENCHMARK "Run benchmarks for [0 .. numLogicalCPUs] with no stepping" OFF)
option_if_not_defined(MARL_FIBERS_USE_UCONTEXT "Use ucontext instead of assembly for fibers (ignored for platforms that do not support ucontext)" OFF)
option_if_not_defined(MARL_DEBUG_ENABLED "Enable debug checks even in release builds" OFF)
###########################################################
# Directories
###########################################################
function(set_if_not_defined name value)
if(NOT DEFINED ${name})
set(${name} ${value} PARENT_SCOPE)
endif()
endfunction()
set(MARL_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
set(MARL_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include)
set_if_not_defined(MARL_THIRD_PARTY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party)
set_if_not_defined(MARL_GOOGLETEST_DIR ${MARL_THIRD_PARTY_DIR}/googletest)
set_if_not_defined(MARL_BENCHMARK_DIR ${MARL_THIRD_PARTY_DIR}/benchmark)
###########################################################
# Submodules
###########################################################
if(MARL_BUILD_TESTS)
if(NOT EXISTS ${MARL_GOOGLETEST_DIR}/.git)
message(WARNING "third_party/googletest submodule missing.")
message(WARNING "Run: `git submodule update --init` to build tests.")
set(MARL_BUILD_TESTS OFF)
endif()
endif(MARL_BUILD_TESTS)
if(MARL_BUILD_BENCHMARKS)
if(NOT EXISTS ${MARL_BENCHMARK_DIR}/.git)
message(WARNING "third_party/benchmark submodule missing.")
message(WARNING "Run: `git submodule update --init` to build benchmarks.")
set(MARL_BUILD_BENCHMARKS OFF)
endif()
endif(MARL_BUILD_BENCHMARKS)
if(MARL_BUILD_BENCHMARKS)
set(BENCHMARK_ENABLE_TESTING FALSE CACHE BOOL FALSE FORCE)
add_subdirectory(${MARL_BENCHMARK_DIR})
endif(MARL_BUILD_BENCHMARKS)
###########################################################
# Compiler feature tests
###########################################################
# Check that the Clang Thread Safety Analysis' try_acquire_capability behaves
# correctly. This is broken on some earlier versions of clang.
# See: https://bugs.llvm.org/show_bug.cgi?id=32954
set(SAVE_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "-Wthread-safety -Werror")
check_cxx_source_compiles(
"int main() {
struct __attribute__((capability(\"mutex\"))) Mutex {
void Unlock() __attribute__((release_capability)) {};
bool TryLock() __attribute__((try_acquire_capability(true))) { return true; };
};
Mutex m;
if (m.TryLock()) {
m.Unlock(); // Should not warn.
}
return 0;
}"
MARL_THREAD_SAFETY_ANALYSIS_SUPPORTED)
set(CMAKE_REQUIRED_FLAGS ${SAVE_CMAKE_REQUIRED_FLAGS})
# Check whether ucontext is supported.
set(SAVE_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "-Werror")
check_cxx_source_compiles(
"#include <ucontext.h>
int main() {
ucontext_t ctx;
getcontext(&ctx);
makecontext(&ctx, nullptr, 2, 1, 2);
swapcontext(&ctx, &ctx);
return 0;
}"
MARL_UCONTEXT_SUPPORTED)
set(CMAKE_REQUIRED_FLAGS ${SAVE_CMAKE_REQUIRED_FLAGS})
if (MARL_FIBERS_USE_UCONTEXT AND NOT MARL_UCONTEXT_SUPPORTED)
# Disable MARL_FIBERS_USE_UCONTEXT and warn if MARL_UCONTEXT_SUPPORTED is 0.
message(WARNING "MARL_FIBERS_USE_UCONTEXT is enabled, but ucontext is not supported by the target. Disabling")
set(MARL_FIBERS_USE_UCONTEXT 0)
endif()
if(MARL_IS_SUBPROJECT)
# Export supported flags as this may be useful to parent projects
set(MARL_THREAD_SAFETY_ANALYSIS_SUPPORTED PARENT_SCOPE ${MARL_THREAD_SAFETY_ANALYSIS_SUPPORTED})
set(MARL_UCONTEXT_SUPPORTED PARENT_SCOPE ${MARL_UCONTEXT_SUPPORTED})
endif()
###########################################################
# File lists
###########################################################
set(MARL_LIST
${MARL_SRC_DIR}/debug.cpp
${MARL_SRC_DIR}/memory.cpp
${MARL_SRC_DIR}/scheduler.cpp
${MARL_SRC_DIR}/thread.cpp
${MARL_SRC_DIR}/trace.cpp
)
if(NOT MSVC)
list(APPEND MARL_LIST
${MARL_SRC_DIR}/osfiber_aarch64.c
${MARL_SRC_DIR}/osfiber_arm.c
${MARL_SRC_DIR}/osfiber_asm_aarch64.S
${MARL_SRC_DIR}/osfiber_asm_arm.S
${MARL_SRC_DIR}/osfiber_asm_loongarch64.S
${MARL_SRC_DIR}/osfiber_asm_mips64.S
${MARL_SRC_DIR}/osfiber_asm_ppc64.S
${MARL_SRC_DIR}/osfiber_asm_rv64.S
${MARL_SRC_DIR}/osfiber_asm_x64.S
${MARL_SRC_DIR}/osfiber_asm_x86.S
${MARL_SRC_DIR}/osfiber_loongarch64.c
${MARL_SRC_DIR}/osfiber_mips64.c
${MARL_SRC_DIR}/osfiber_ppc64.c
${MARL_SRC_DIR}/osfiber_rv64.c
${MARL_SRC_DIR}/osfiber_x64.c
${MARL_SRC_DIR}/osfiber_x86.c
${MARL_SRC_DIR}/osfiber_emscripten.cpp
)
# CMAKE_OSX_ARCHITECTURES settings aren't propagated to assembly files when
# building for Apple platforms (https://gitlab.kitware.com/cmake/cmake/-/issues/20771),
# we treat assembly files as C files to work around this bug.
set_source_files_properties(
${MARL_SRC_DIR}/osfiber_asm_aarch64.S
${MARL_SRC_DIR}/osfiber_asm_arm.S
${MARL_SRC_DIR}/osfiber_asm_loongarch64.S
${MARL_SRC_DIR}/osfiber_asm_mips64.S
${MARL_SRC_DIR}/osfiber_asm_ppc64.S
${MARL_SRC_DIR}/osfiber_asm_x64.S
${MARL_SRC_DIR}/osfiber_asm_x86.S
PROPERTIES LANGUAGE C
)
endif(NOT MSVC)
###########################################################
# OS libraries
###########################################################
find_package(Threads REQUIRED)
###########################################################
# Functions
###########################################################
function(marl_set_target_options target)
if(MARL_THREAD_SAFETY_ANALYSIS_SUPPORTED)
target_compile_options(${target} PRIVATE "-Wthread-safety")
endif()
# Enable all warnings
if(MSVC)
target_compile_options(${target} PRIVATE "-W4")
else()
target_compile_options(${target} PRIVATE "-Wall")
endif()
# Disable specific, pedantic warnings
if(MSVC)
target_compile_options(${target} PRIVATE
"-D_CRT_SECURE_NO_WARNINGS"
"/wd4127" # conditional expression is constant
"/wd4324" # structure was padded due to alignment specifier
)
endif()
# Treat all warnings as errors
if(MARL_WARNINGS_AS_ERRORS)
if(MSVC)
target_compile_options(${target} PRIVATE "/WX")
else()
target_compile_options(${target} PRIVATE "-Werror")
endif()
endif(MARL_WARNINGS_AS_ERRORS)
if(MARL_USE_PTHREAD_THREAD_LOCAL)
target_compile_definitions(${target} PRIVATE "MARL_USE_PTHREAD_THREAD_LOCAL=1")
target_link_libraries(${target} PUBLIC pthread)
endif()
if(MARL_ASAN)
target_compile_options(${target} PUBLIC "-fsanitize=address")
target_link_libraries(${target} PUBLIC "-fsanitize=address")
elseif(MARL_MSAN)
target_compile_options(${target} PUBLIC "-fsanitize=memory")
target_link_libraries(${target} PUBLIC "-fsanitize=memory")
elseif(MARL_TSAN)
target_compile_options(${target} PUBLIC "-fsanitize=thread")
target_link_libraries(${target} PUBLIC "-fsanitize=thread")
elseif(MARL_UBSAN)
target_compile_options(${target} PUBLIC "-fsanitize=undefined")
target_link_libraries(${target} PUBLIC "-fsanitize=undefined")
endif()
if(MARL_FIBERS_USE_UCONTEXT)
target_compile_definitions(${target} PRIVATE "MARL_FIBERS_USE_UCONTEXT=1")
endif()
if(MARL_DEBUG_ENABLED)
target_compile_definitions(${target} PRIVATE "MARL_DEBUG_ENABLED=1")
endif()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^rv.*")
target_link_libraries(${target} INTERFACE atomic) #explicitly use -latomic for RISC-V linking
endif()
target_include_directories(${target} PUBLIC $<BUILD_INTERFACE:${MARL_INCLUDE_DIR}>)
endfunction(marl_set_target_options)
###########################################################
# Targets
###########################################################
# marl
if(MARL_BUILD_SHARED OR BUILD_SHARED_LIBS)
add_library(marl SHARED ${MARL_LIST})
if(MSVC)
target_compile_definitions(marl
PRIVATE "MARL_BUILDING_DLL=1"
PUBLIC "MARL_DLL=1"
)
endif()
else()
add_library(marl ${MARL_LIST})
endif()
if(NOT MSVC)
# Public API symbols are made visible with the MARL_EXPORT annotation.
target_compile_options(marl PRIVATE "-fvisibility=hidden")
endif()
set_target_properties(marl PROPERTIES
POSITION_INDEPENDENT_CODE 1
VERSION ${MARL_VERSION}
SOVERSION "${MARL_VERSION_MAJOR}"
)
marl_set_target_options(marl)
target_link_libraries(marl PUBLIC Threads::Threads)
# install
if(MARL_INSTALL)
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/marl-config.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/marl-config.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/marl
)
install(DIRECTORY ${MARL_INCLUDE_DIR}/marl
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
USE_SOURCE_PERMISSIONS
)
install(TARGETS marl
EXPORT marl-targets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(EXPORT marl-targets
FILE marl-targets.cmake
NAMESPACE marl::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/marl
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/marl-config.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/marl
)
endif(MARL_INSTALL)
# tests
if(MARL_BUILD_TESTS)
set(MARL_TEST_LIST
${MARL_SRC_DIR}/blockingcall_test.cpp
${MARL_SRC_DIR}/conditionvariable_test.cpp
${MARL_SRC_DIR}/containers_test.cpp
${MARL_SRC_DIR}/dag_test.cpp
${MARL_SRC_DIR}/defer_test.cpp
${MARL_SRC_DIR}/event_test.cpp
${MARL_SRC_DIR}/marl_test.cpp
${MARL_SRC_DIR}/marl_test.h
${MARL_SRC_DIR}/memory_test.cpp
${MARL_SRC_DIR}/osfiber_test.cpp
${MARL_SRC_DIR}/parallelize_test.cpp
${MARL_SRC_DIR}/pool_test.cpp
${MARL_SRC_DIR}/scheduler_test.cpp
${MARL_SRC_DIR}/thread_test.cpp
${MARL_SRC_DIR}/ticket_test.cpp
${MARL_SRC_DIR}/waitgroup_test.cpp
${MARL_GOOGLETEST_DIR}/googletest/src/gtest-all.cc
${MARL_GOOGLETEST_DIR}/googlemock/src/gmock-all.cc
)
set(MARL_TEST_INCLUDE_DIR
${MARL_GOOGLETEST_DIR}/googletest/include/
${MARL_GOOGLETEST_DIR}/googlemock/include/
${MARL_GOOGLETEST_DIR}/googletest/
${MARL_GOOGLETEST_DIR}/googlemock/
)
add_executable(marl-unittests ${MARL_TEST_LIST})
set_target_properties(marl-unittests PROPERTIES
INCLUDE_DIRECTORIES "${MARL_TEST_INCLUDE_DIR}"
FOLDER "Tests"
)
marl_set_target_options(marl-unittests)
target_link_libraries(marl-unittests PRIVATE marl)
endif(MARL_BUILD_TESTS)
# benchmarks
if(MARL_BUILD_BENCHMARKS)
set(MARL_BENCHMARK_LIST
${MARL_SRC_DIR}/blockingcall_bench.cpp
${MARL_SRC_DIR}/defer_bench.cpp
${MARL_SRC_DIR}/event_bench.cpp
${MARL_SRC_DIR}/marl_bench.cpp
${MARL_SRC_DIR}/non_marl_bench.cpp
${MARL_SRC_DIR}/scheduler_bench.cpp
${MARL_SRC_DIR}/ticket_bench.cpp
${MARL_SRC_DIR}/waitgroup_bench.cpp
)
add_executable(marl-benchmarks ${MARL_BENCHMARK_LIST})
set_target_properties(${target} PROPERTIES FOLDER "Benchmarks")
marl_set_target_options(marl-benchmarks)
target_compile_definitions(marl-benchmarks PRIVATE
"MARL_FULL_BENCHMARK=${MARL_FULL_BENCHMARK}"
)
target_link_libraries(marl-benchmarks PRIVATE benchmark::benchmark marl)
endif(MARL_BUILD_BENCHMARKS)
# examples
if(MARL_BUILD_EXAMPLES)
function(build_example target)
add_executable(${target} "${CMAKE_CURRENT_SOURCE_DIR}/examples/${target}.cpp")
set_target_properties(${target} PROPERTIES FOLDER "Examples")
marl_set_target_options(${target})
target_link_libraries(${target} PRIVATE marl)
if (EMSCRIPTEN)
target_link_options(${target} PRIVATE
-O1
-pthread -sPTHREAD_POOL_SIZE=2 -sPROXY_TO_PTHREAD
-sASYNCIFY # -sASYNCIFY_STACK_SIZE=1000000
-sALLOW_MEMORY_GROWTH=1 -sASSERTIONS
-sENVIRONMENT=web,worker
"SHELL:--shell-file ${CMAKE_CURRENT_SOURCE_DIR}/examples/shell.emscripten.html")
set_target_properties(${target} PROPERTIES SUFFIX .html)
endif()
endfunction(build_example)
build_example(fractal)
build_example(hello_task)
build_example(primes)
build_example(tasks_in_tasks)
endif(MARL_BUILD_EXAMPLES)