1# Copyright 2019 The Marl Authors.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15cmake_minimum_required(VERSION 3.0)
16
17include(cmake/parse_version.cmake)
18parse_version("${CMAKE_CURRENT_SOURCE_DIR}/CHANGES.md" MARL)
19
20set(CMAKE_CXX_STANDARD 11)
21
22project(Marl
23    VERSION   "${MARL_VERSION_MAJOR}.${MARL_VERSION_MINOR}.${MARL_VERSION_PATCH}"
24    LANGUAGES C CXX ASM
25)
26
27include(CheckCXXSourceCompiles)
28
29# MARL_IS_SUBPROJECT is 1 if added via add_subdirectory() from another project.
30get_directory_property(MARL_IS_SUBPROJECT PARENT_DIRECTORY)
31if(MARL_IS_SUBPROJECT)
32    set(MARL_IS_SUBPROJECT 1)
33endif()
34
35###########################################################
36# Options
37###########################################################
38function(option_if_not_defined name description default)
39    if(NOT DEFINED ${name})
40        option(${name} ${description} ${default})
41    endif()
42endfunction()
43
44option_if_not_defined(MARL_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
45option_if_not_defined(MARL_BUILD_EXAMPLES "Build example applications" OFF)
46option_if_not_defined(MARL_BUILD_TESTS "Build tests" OFF)
47option_if_not_defined(MARL_BUILD_BENCHMARKS "Build benchmarks" OFF)
48option_if_not_defined(MARL_BUILD_SHARED "Build marl as a shared / dynamic library (default static)" OFF)
49option_if_not_defined(MARL_ASAN "Build marl with address sanitizer" OFF)
50option_if_not_defined(MARL_MSAN "Build marl with memory sanitizer" OFF)
51option_if_not_defined(MARL_TSAN "Build marl with thread sanitizer" OFF)
52option_if_not_defined(MARL_INSTALL "Create marl install target" OFF)
53option_if_not_defined(MARL_FULL_BENCHMARK "Run benchmarks for [0 .. numLogicalCPUs] with no stepping" OFF)
54option_if_not_defined(MARL_FIBERS_USE_UCONTEXT "Use ucontext instead of assembly for fibers (ignored for platforms that do not support ucontext)" OFF)
55option_if_not_defined(MARL_DEBUG_ENABLED "Enable debug checks even in release builds" OFF)
56
57###########################################################
58# Directories
59###########################################################
60function(set_if_not_defined name value)
61    if(NOT DEFINED ${name})
62        set(${name} ${value} PARENT_SCOPE)
63    endif()
64endfunction()
65
66set(MARL_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
67set(MARL_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include)
68set_if_not_defined(MARL_THIRD_PARTY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party)
69set_if_not_defined(MARL_GOOGLETEST_DIR ${MARL_THIRD_PARTY_DIR}/googletest)
70set_if_not_defined(MARL_BENCHMARK_DIR ${MARL_THIRD_PARTY_DIR}/benchmark)
71
72###########################################################
73# Submodules
74###########################################################
75if(MARL_BUILD_TESTS)
76    if(NOT EXISTS ${MARL_GOOGLETEST_DIR}/.git)
77        message(WARNING "third_party/googletest submodule missing.")
78        message(WARNING "Run: `git submodule update --init` to build tests.")
79        set(MARL_BUILD_TESTS OFF)
80    endif()
81endif(MARL_BUILD_TESTS)
82
83if(MARL_BUILD_BENCHMARKS)
84    if(NOT EXISTS ${MARL_BENCHMARK_DIR}/.git)
85        message(WARNING "third_party/benchmark submodule missing.")
86        message(WARNING "Run: `git submodule update --init` to build benchmarks.")
87        set(MARL_BUILD_BENCHMARKS OFF)
88    endif()
89endif(MARL_BUILD_BENCHMARKS)
90
91if(MARL_BUILD_BENCHMARKS)
92    set(BENCHMARK_ENABLE_TESTING FALSE CACHE BOOL FALSE FORCE)
93    add_subdirectory(${MARL_BENCHMARK_DIR})
94endif(MARL_BUILD_BENCHMARKS)
95
96###########################################################
97# Compiler feature tests
98###########################################################
99# Check that the Clang Thread Safety Analysis' try_acquire_capability behaves
100# correctly. This is broken on some earlier versions of clang.
101# See: https://bugs.llvm.org/show_bug.cgi?id=32954
102set(SAVE_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
103set(CMAKE_REQUIRED_FLAGS "-Wthread-safety -Werror")
104check_cxx_source_compiles(
105    "int main() {
106      struct __attribute__((capability(\"mutex\"))) Mutex {
107        void Unlock() __attribute__((release_capability)) {};
108        bool TryLock() __attribute__((try_acquire_capability(true))) { return true; };
109      };
110      Mutex m;
111      if (m.TryLock()) {
112        m.Unlock();  // Should not warn.
113      }
114      return 0;
115    }"
116    MARL_THREAD_SAFETY_ANALYSIS_SUPPORTED)
117set(CMAKE_REQUIRED_FLAGS ${SAVE_CMAKE_REQUIRED_FLAGS})
118
119# Check whether ucontext is supported.
120set(SAVE_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
121set(CMAKE_REQUIRED_FLAGS "-Werror")
122check_cxx_source_compiles(
123    "#include <ucontext.h>
124    int main() {
125      ucontext_t ctx;
126      getcontext(&ctx);
127      makecontext(&ctx, nullptr, 2, 1, 2);
128      swapcontext(&ctx, &ctx);
129      return 0;
130    }"
131    MARL_UCONTEXT_SUPPORTED)
132set(CMAKE_REQUIRED_FLAGS ${SAVE_CMAKE_REQUIRED_FLAGS})
133if (MARL_FIBERS_USE_UCONTEXT AND NOT MARL_UCONTEXT_SUPPORTED)
134    # Disable MARL_FIBERS_USE_UCONTEXT and warn if MARL_UCONTEXT_SUPPORTED is 0.
135    message(WARNING "MARL_FIBERS_USE_UCONTEXT is enabled, but ucontext is not supported by the target. Disabling")
136    set(MARL_FIBERS_USE_UCONTEXT 0)
137endif()
138
139if(MARL_IS_SUBPROJECT)
140    # Export supported flags as this may be useful to parent projects
141    set(MARL_THREAD_SAFETY_ANALYSIS_SUPPORTED PARENT_SCOPE ${MARL_THREAD_SAFETY_ANALYSIS_SUPPORTED})
142    set(MARL_UCONTEXT_SUPPORTED               PARENT_SCOPE ${MARL_UCONTEXT_SUPPORTED})
143endif()
144
145###########################################################
146# File lists
147###########################################################
148set(MARL_LIST
149    ${MARL_SRC_DIR}/debug.cpp
150    ${MARL_SRC_DIR}/memory.cpp
151    ${MARL_SRC_DIR}/scheduler.cpp
152    ${MARL_SRC_DIR}/thread.cpp
153    ${MARL_SRC_DIR}/trace.cpp
154)
155if(NOT MSVC)
156    list(APPEND MARL_LIST
157        ${MARL_SRC_DIR}/osfiber_aarch64.c
158        ${MARL_SRC_DIR}/osfiber_arm.c
159        ${MARL_SRC_DIR}/osfiber_asm_aarch64.S
160        ${MARL_SRC_DIR}/osfiber_asm_arm.S
161        ${MARL_SRC_DIR}/osfiber_asm_mips64.S
162        ${MARL_SRC_DIR}/osfiber_asm_ppc64.S
163        ${MARL_SRC_DIR}/osfiber_asm_x64.S
164        ${MARL_SRC_DIR}/osfiber_asm_x86.S
165        ${MARL_SRC_DIR}/osfiber_mips64.c
166        ${MARL_SRC_DIR}/osfiber_ppc64.c
167        ${MARL_SRC_DIR}/osfiber_x64.c
168        ${MARL_SRC_DIR}/osfiber_x86.c
169    )
170    # CMAKE_OSX_ARCHITECTURES settings aren't propagated to assembly files when
171    # building for Apple platforms (https://gitlab.kitware.com/cmake/cmake/-/issues/20771),
172    # we treat assembly files as C files to work around this bug.
173    set_source_files_properties(
174        ${MARL_SRC_DIR}/osfiber_asm_aarch64.S
175        ${MARL_SRC_DIR}/osfiber_asm_arm.S
176        ${MARL_SRC_DIR}/osfiber_asm_mips64.S
177        ${MARL_SRC_DIR}/osfiber_asm_ppc64.S
178        ${MARL_SRC_DIR}/osfiber_asm_x64.S
179        ${MARL_SRC_DIR}/osfiber_asm_x86.S
180        PROPERTIES LANGUAGE C
181    )
182endif(NOT MSVC)
183
184###########################################################
185# OS libraries
186###########################################################
187find_package(Threads REQUIRED)
188
189###########################################################
190# Functions
191###########################################################
192function(marl_set_target_options target)
193    if(MARL_THREAD_SAFETY_ANALYSIS_SUPPORTED)
194        target_compile_options(${target} PRIVATE "-Wthread-safety")
195    endif()
196
197    # Enable all warnings
198    if(MSVC)
199        target_compile_options(${target} PRIVATE "-W4")
200    else()
201        target_compile_options(${target} PRIVATE "-Wall")
202    endif()
203
204    # Disable specific, pedantic warnings
205    if(MSVC)
206        target_compile_options(${target} PRIVATE
207            "-D_CRT_SECURE_NO_WARNINGS"
208            "/wd4127" # conditional expression is constant
209            "/wd4324" # structure was padded due to alignment specifier
210        )
211    endif()
212
213    # Treat all warnings as errors
214    if(MARL_WARNINGS_AS_ERRORS)
215        if(MSVC)
216            target_compile_options(${target} PRIVATE "/WX")
217        else()
218            target_compile_options(${target} PRIVATE "-Werror")
219        endif()
220    endif(MARL_WARNINGS_AS_ERRORS)
221
222    if(MARL_ASAN)
223        target_compile_options(${target} PUBLIC "-fsanitize=address")
224        target_link_libraries(${target} PUBLIC "-fsanitize=address")
225    elseif(MARL_MSAN)
226        target_compile_options(${target} PUBLIC "-fsanitize=memory")
227        target_link_libraries(${target} PUBLIC "-fsanitize=memory")
228    elseif(MARL_TSAN)
229        target_compile_options(${target} PUBLIC "-fsanitize=thread")
230        target_link_libraries(${target} PUBLIC "-fsanitize=thread")
231    endif()
232
233    if(MARL_FIBERS_USE_UCONTEXT)
234        target_compile_definitions(${target} PRIVATE "MARL_FIBERS_USE_UCONTEXT=1")
235    endif()
236
237    if(MARL_DEBUG_ENABLED)
238        target_compile_definitions(${target} PRIVATE "MARL_DEBUG_ENABLED=1")
239    endif()
240
241    target_include_directories(${target} PUBLIC $<BUILD_INTERFACE:${MARL_INCLUDE_DIR}>)
242endfunction(marl_set_target_options)
243
244###########################################################
245# Targets
246###########################################################
247
248# marl
249if(MARL_BUILD_SHARED OR BUILD_SHARED_LIBS)
250    add_library(marl SHARED ${MARL_LIST})
251    if(MSVC)
252        target_compile_definitions(marl
253            PRIVATE "MARL_BUILDING_DLL=1"
254            PUBLIC  "MARL_DLL=1"
255        )
256    endif()
257else()
258    add_library(marl ${MARL_LIST})
259endif()
260
261if(NOT MSVC)
262    # Public API symbols are made visible with the MARL_EXPORT annotation.
263    target_compile_options(marl PRIVATE "-fvisibility=hidden")
264endif()
265
266set_target_properties(marl PROPERTIES
267    POSITION_INDEPENDENT_CODE 1
268    VERSION ${MARL_VERSION}
269    SOVERSION "${MARL_VERSION_MAJOR}"
270)
271
272marl_set_target_options(marl)
273
274target_link_libraries(marl PUBLIC Threads::Threads)
275
276# install
277if(MARL_INSTALL)
278    include(CMakePackageConfigHelpers)
279    include(GNUInstallDirs)
280
281    configure_package_config_file(
282        ${CMAKE_CURRENT_SOURCE_DIR}/cmake/marl-config.cmake.in
283        ${CMAKE_CURRENT_BINARY_DIR}/marl-config.cmake
284        INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/marl
285    )
286
287    install(DIRECTORY ${MARL_INCLUDE_DIR}/marl
288        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
289        USE_SOURCE_PERMISSIONS
290    )
291
292    install(TARGETS marl
293        EXPORT marl-targets
294        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
295        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
296        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
297        INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
298    )
299
300    install(EXPORT marl-targets
301        FILE marl-targets.cmake
302        NAMESPACE marl::
303        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/marl
304    )
305
306    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/marl-config.cmake
307        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/marl
308    )
309endif(MARL_INSTALL)
310
311# tests
312if(MARL_BUILD_TESTS)
313    set(MARL_TEST_LIST
314        ${MARL_SRC_DIR}/blockingcall_test.cpp
315        ${MARL_SRC_DIR}/conditionvariable_test.cpp
316        ${MARL_SRC_DIR}/containers_test.cpp
317        ${MARL_SRC_DIR}/dag_test.cpp
318        ${MARL_SRC_DIR}/defer_test.cpp
319        ${MARL_SRC_DIR}/event_test.cpp
320        ${MARL_SRC_DIR}/marl_test.cpp
321        ${MARL_SRC_DIR}/marl_test.h
322        ${MARL_SRC_DIR}/memory_test.cpp
323        ${MARL_SRC_DIR}/osfiber_test.cpp
324        ${MARL_SRC_DIR}/parallelize_test.cpp
325        ${MARL_SRC_DIR}/pool_test.cpp
326        ${MARL_SRC_DIR}/scheduler_test.cpp
327        ${MARL_SRC_DIR}/thread_test.cpp
328        ${MARL_SRC_DIR}/ticket_test.cpp
329        ${MARL_SRC_DIR}/waitgroup_test.cpp
330        ${MARL_GOOGLETEST_DIR}/googletest/src/gtest-all.cc
331        ${MARL_GOOGLETEST_DIR}/googlemock/src/gmock-all.cc
332    )
333
334    set(MARL_TEST_INCLUDE_DIR
335        ${MARL_GOOGLETEST_DIR}/googletest/include/
336        ${MARL_GOOGLETEST_DIR}/googlemock/include/
337        ${MARL_GOOGLETEST_DIR}/googletest/
338        ${MARL_GOOGLETEST_DIR}/googlemock/
339    )
340
341    add_executable(marl-unittests ${MARL_TEST_LIST})
342
343    set_target_properties(marl-unittests PROPERTIES
344        INCLUDE_DIRECTORIES "${MARL_TEST_INCLUDE_DIR}"
345        FOLDER "Tests"
346    )
347
348    marl_set_target_options(marl-unittests)
349
350    target_link_libraries(marl-unittests PRIVATE marl)
351endif(MARL_BUILD_TESTS)
352
353# benchmarks
354if(MARL_BUILD_BENCHMARKS)
355    set(MARL_BENCHMARK_LIST
356        ${MARL_SRC_DIR}/blockingcall_bench.cpp
357        ${MARL_SRC_DIR}/defer_bench.cpp
358        ${MARL_SRC_DIR}/event_bench.cpp
359        ${MARL_SRC_DIR}/marl_bench.cpp
360        ${MARL_SRC_DIR}/non_marl_bench.cpp
361        ${MARL_SRC_DIR}/scheduler_bench.cpp
362        ${MARL_SRC_DIR}/ticket_bench.cpp
363        ${MARL_SRC_DIR}/waitgroup_bench.cpp
364    )
365
366    add_executable(marl-benchmarks ${MARL_BENCHMARK_LIST})
367    set_target_properties(${target} PROPERTIES FOLDER "Benchmarks")
368
369    marl_set_target_options(marl-benchmarks)
370
371    target_compile_definitions(marl-benchmarks PRIVATE
372        "MARL_FULL_BENCHMARK=${MARL_FULL_BENCHMARK}"
373    )
374
375    target_link_libraries(marl-benchmarks PRIVATE benchmark::benchmark marl)
376endif(MARL_BUILD_BENCHMARKS)
377
378# examples
379if(MARL_BUILD_EXAMPLES)
380    function(build_example target)
381        add_executable(${target} "${CMAKE_CURRENT_SOURCE_DIR}/examples/${target}.cpp")
382        set_target_properties(${target} PROPERTIES FOLDER "Examples")
383        marl_set_target_options(${target})
384        target_link_libraries(${target} PRIVATE marl)
385    endfunction(build_example)
386
387    build_example(fractal)
388    build_example(hello_task)
389    build_example(primes)
390    build_example(tasks_in_tasks)
391endif(MARL_BUILD_EXAMPLES)
392