├── .clang-format ├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── _config.yml ├── compile_cl.bat ├── compile_cl_c99_debug.bat ├── compile_cl_debug.bat ├── compile_clang.sh ├── compile_clang_cov.sh ├── compile_clang_debug.sh ├── examples ├── compile_cl.bat ├── compile_cl_debug.bat ├── compile_clang.sh ├── compile_clang_debug.sh ├── helpers │ ├── counting_task.h │ ├── nadir_sync.h │ ├── perf_timer.h │ ├── stealing_worker.h │ ├── task_producer.h │ └── thread_worker.h ├── multi_threaded_task_consumer.cpp ├── multi_threaded_task_producer.cpp ├── multi_threaded_task_producer_and_consumer.cpp ├── single_threaded_task_consumer.cpp └── stealing_multi_threaded_task_producer_and_consumer.cpp ├── find_mvsc.bat ├── src └── bikeshed.h ├── test ├── main.cpp ├── test.cpp └── test_c99.c ├── third-party └── jctest │ └── src │ └── jc_test.h ├── travis-cl.sh └── travis-clang.sh /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | AccessModifierOffset: 0 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: true 7 | AlignConsecutiveDeclarations: true 8 | AlignEscapedNewlines: DontAlign 9 | AlignOperands: false 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Empty 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: true 20 | AlwaysBreakTemplateDeclarations: MultiLine 21 | BinPackArguments: false 22 | BinPackParameters: false 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: false 29 | AfterObjCDeclaration: true 30 | AfterStruct: true 31 | AfterUnion: true 32 | AfterExternBlock: true 33 | BeforeCatch: true 34 | BeforeElse: true 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Custom 41 | BreakBeforeInheritanceComma: false 42 | BreakInheritanceList: BeforeComma 43 | BreakBeforeTernaryOperators: false 44 | BreakConstructorInitializersBeforeComma: true 45 | BreakConstructorInitializers: BeforeComma 46 | BreakAfterJavaFieldAnnotations: false 47 | BreakStringLiterals: true 48 | ColumnLimit: 0 49 | CommentPragmas: '^ IWYU pragma:' 50 | CompactNamespaces: false 51 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 52 | ConstructorInitializerIndentWidth: 4 53 | ContinuationIndentWidth: 0 54 | Cpp11BracedListStyle: false 55 | DerivePointerAlignment: false 56 | DisableFormat: false 57 | ExperimentalAutoDetectBinPacking: false 58 | FixNamespaceComments: true 59 | ForEachMacros: 60 | - foreach 61 | - Q_FOREACH 62 | - BOOST_FOREACH 63 | IncludeBlocks: Preserve 64 | IncludeCategories: 65 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 66 | Priority: 2 67 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 68 | Priority: 3 69 | - Regex: '.*' 70 | Priority: 1 71 | IncludeIsMainRegex: '(Test)?$' 72 | IndentCaseLabels: true 73 | IndentPPDirectives: AfterHash 74 | IndentWidth: 4 75 | IndentWrappedFunctionNames: false 76 | JavaScriptQuotes: Leave 77 | JavaScriptWrapImports: true 78 | KeepEmptyLinesAtTheStartOfBlocks: false 79 | MacroBlockBegin: '' 80 | MacroBlockEnd: '' 81 | MaxEmptyLinesToKeep: 1 82 | NamespaceIndentation: Inner 83 | ObjCBinPackProtocolList: Auto 84 | ObjCBlockIndentWidth: 2 85 | ObjCSpaceAfterProperty: false 86 | ObjCSpaceBeforeProtocolList: true 87 | PenaltyBreakAssignment: 2 88 | PenaltyBreakBeforeFirstCallParameter: 19 89 | PenaltyBreakComment: 300 90 | PenaltyBreakFirstLessLess: 120 91 | PenaltyBreakString: 1000 92 | PenaltyBreakTemplateDeclaration: 10 93 | PenaltyExcessCharacter: 1000000 94 | PenaltyReturnTypeOnItsOwnLine: 60 95 | PointerAlignment: Left 96 | ReflowComments: true 97 | SortIncludes: false 98 | SortUsingDeclarations: false 99 | SpaceAfterCStyleCast: false 100 | SpaceAfterTemplateKeyword: true 101 | SpaceBeforeAssignmentOperators: true 102 | SpaceBeforeCpp11BracedList: true 103 | SpaceBeforeCtorInitializerColon: true 104 | SpaceBeforeInheritanceColon: true 105 | SpaceBeforeParens: ControlStatements 106 | SpaceBeforeRangeBasedForLoopColon: true 107 | SpaceInEmptyParentheses: false 108 | SpacesBeforeTrailingComments: 1 109 | SpacesInAngles: false 110 | SpacesInContainerLiterals: true 111 | SpacesInCStyleCastParentheses: false 112 | SpacesInParentheses: false 113 | SpacesInSquareBrackets: false 114 | Standard: Cpp11 115 | StatementMacros: 116 | - Q_UNUSED 117 | - QT_REQUIRE_VERSION 118 | TabWidth: 4 119 | UseTab: Never 120 | ... 121 | 122 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | build/** 35 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third-party/nadir"] 2 | path = third-party/nadir 3 | url = https://github.com/DanEngelbrecht/nadir.git 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | sudo: true 3 | compiler: 4 | - clang 5 | 6 | os: 7 | - linux 8 | - osx 9 | - windows 10 | 11 | install: 12 | - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then choco install visualstudio2017-workload-vctools -y; fi 13 | 14 | script: 15 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then sh ./travis-clang.sh; fi 16 | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sh ./travis-clang.sh; fi 17 | - if [ "$TRAVIS_OS_NAME" == "windows" ]; then sh ./travis-cl.sh; fi 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Dan Engelbrecht 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | |Branch | OSX / Linux / Windows | 2 | |------------|-----------------------| 3 | |master | [![Build Status](https://travis-ci.org/DanEngelbrecht/bikeshed.svg?branch=master)](https://travis-ci.org/DanEngelbrecht/bikeshed?branch=master) | 4 | |master | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/3f4c844382cc4314ada8d8c7ac27e544)](https://app.codacy.com/app/DanEngelbrecht/bikeshed?utm_source=github.com&utm_medium=referral&utm_content=DanEngelbrecht/bikeshed&utm_campaign=Badge_Grade_Dashboard) | 5 | 6 | # bikeshed 7 | Lock free hierarchical work scheduler 8 | Builds with MSVC, Clang and GCC, header only, C99 compliant, MIT license. 9 | 10 | See github for latest version: https://github.com/DanEngelbrecht/bikeshed 11 | 12 | See design blogs at: https://danengelbrecht.github.io 13 | 14 | ## Version history 15 | 16 | ### Version v1.0 29/5 2019 17 | 18 | **Release 1.0** 19 | 20 | ### Fixes 21 | - Use explicit int32_t for instead of long to ensure 32-bit values on GCC/Clang x64-builds 22 | - Corrected URL to blog in README.md 23 | - Added sample code for performance tests (in examples folder) 24 | 25 | ### Version v0.4 18/5 2019 26 | 27 | **Pre-release 4** 28 | 29 | ### API changes 30 | - Bikeshed_AddDependencies to take array of parent task task_ids 31 | - Bikeshed_ReadyCallback now gets called per channel range 32 | 33 | ### API additions 34 | - Bikeshed_FreeTasks 35 | - BIKESHED_L1CACHE_SIZE to control ready head alignement - no padding/alignement added by default 36 | - BIKESHED_CPU_YIELD to control yielding in high-contention scenarios 37 | 38 | ### Fixes 39 | - Added default (non-atomic) implementations for helper for unsupported platforms/compilers 40 | - Codacy analisys and badge 41 | - More input validation on public apis when BIKESHED_ASSERTS is enabled 42 | - Batch allocation of task indexes 43 | - Batch allocation of dependency indexes 44 | - Batch free of task indexes 45 | - Batch free of dependency indexes 46 | - Fixed broken channel range detection when resolving dependencies 47 | 48 | ### Version v0.3 1/5 2019 49 | 50 | **Pre-release 3** 51 | 52 | #### Fixes 53 | 54 | - Ready callback is now called when a task is readied via dependency resolve 55 | - Tasks are readied in batch when possible 56 | 57 | ### Version v0.2 29/4 2019 58 | 59 | **Pre-release 2** 60 | 61 | #### Fixes 62 | 63 | - Internal cleanups 64 | - Fixed warnings and removed clang warning suppressions 65 | - `-Wno-sign-conversion` 66 | - `-Wno-unused-macros` 67 | - `-Wno-c++98-compat` 68 | - `-Wno-implicit-fallthrough` 69 | - Made it compile cleanly with clang++ on Windows 70 | 71 | ### Version v0.1 26/4 2019 72 | 73 | **Pre-release 1** 74 | 75 | ## Features 76 | - Generic tasks scheduling with dependecies between tasks 77 | - Each task has zero or many dependecies (as defined by user) 78 | - User should Ready any tasks that can execute (has zero dependencies) 79 | - Automatic ready of tasks that reaches zero dependecies 80 | - Automatic free of tasks that has completed 81 | - A task can have many parents and many child dependecies 82 | - Task channels - execute tasks based on task channel 83 | - No memory allocations once shed is created 84 | - Minimal dependencies 85 | - Memory allocation and threading are users responsability 86 | - Lifetime of data associated with tasks is users responsability 87 | - Configurable and optional assert (fatal error) behavior 88 | - Configurable platform dependant functions with default implementation provided 89 | - Header only - define `BIKESHED_IMPLEMENTATION` in one compilation unit and include `bikeshed.h` 90 | 91 | ## Non-features 92 | - Cyclic dependency detection and resolving 93 | - API is designed to help user avoid cyclic dependecies but does not do any analisys 94 | - Built in threading or syncronization code - API to add it is available 95 | - Unlimited number of active tasks - currently limited to 8 388 607 *active* tasks 96 | - Cancelling of tasks 97 | - Tagging of tasks 98 | 99 | ## Dependencies 100 | Minimal dependecies with default overridable method for atomic operations. 101 | - `` 102 | - `` 103 | - The default (optional) MSVC implementation depends on ``. 104 | 105 | ### Optional default methods 106 | The default implementations for the atomic and CPU yield functions can be overridden with your own implementation by overriding the macros: 107 | - `BIKESHED_ATOMICADD` Atomically adds a 32-bit signed integer to another 32-bit signed integer and returns the result 108 | - `BIKESHED_ATOMICCAS` Atomically exchange a 32-bit signed integer with another 32-bit signed integer if the value to be swapped matches the provided compare value, returns the old value. 109 | - `BIKESHED_CPU_YIELD` Yield CPU (mm_pause() / YieldProcessor()). 110 | 111 | ## Test code dependecies 112 | 113 | Test code has dependencies added as drop-in headers from 114 | - https://github.com/JCash/jctest for unit test validation 115 | 116 | Test code has dependencies added as git sub-modules from 117 | - https://github.com/DanEngelbrecht/nadir for threading and syncronization 118 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /compile_cl.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | call .\find_mvsc.bat 4 | 5 | if NOT DEFINED VCINSTALLDIR ( 6 | echo "No Visual Studio installation found, aborting, try running run vcvarsall.bat first!" 7 | exit 1 8 | ) 9 | 10 | IF NOT EXIST build ( 11 | mkdir build 12 | ) 13 | 14 | pushd build 15 | 16 | cl.exe /nologo /Zi /O2 /D_CRT_SECURE_NO_WARNINGS /D_HAS_EXCEPTIONS=0 /EHsc /Wall /wd5045 /wd4514 /wd4710 /wd4820 /wd4820 /wd4668 /wd4464 /wd5039 /wd4255 /wd4626 /wd4711 ..\third-party\nadir\src\nadir.cpp ..\test\test_c99.c ..\test\test.cpp ..\test\main.cpp /link /out:test.exe /pdb:test.pdb 17 | 18 | popd 19 | 20 | exit /B %ERRORLEVEL% 21 | -------------------------------------------------------------------------------- /compile_cl_c99_debug.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | call .\find_mvsc.bat 4 | 5 | if NOT DEFINED VCINSTALLDIR ( 6 | echo "No Visual Studio installation found, aborting, try running run vcvarsall.bat first!" 7 | exit 1 8 | ) 9 | 10 | IF NOT EXIST build ( 11 | mkdir build 12 | ) 13 | 14 | pushd build 15 | 16 | cl.exe /nologo /Zi /DBIKESHED_ASSERTS /D_CRT_SECURE_NO_WARNINGS /D_HAS_EXCEPTIONS=0 /EHsc /W4 ..\third-party\nadir\src\nadir.cpp ..\test\test_c99.c ..\test\main.cpp /link /out:test_debug.exe /pdb:test_debug.pdb 17 | 18 | popd 19 | 20 | exit /B %ERRORLEVEL% 21 | 22 | -------------------------------------------------------------------------------- /compile_cl_debug.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | call .\find_mvsc.bat 4 | 5 | if NOT DEFINED VCINSTALLDIR ( 6 | echo "No Visual Studio installation found, aborting, try running run vcvarsall.bat first!" 7 | exit 1 8 | ) 9 | 10 | IF NOT EXIST build ( 11 | mkdir build 12 | ) 13 | 14 | pushd build 15 | 16 | cl.exe /nologo /Zi /DBIKESHED_ASSERTS /D_CRT_SECURE_NO_WARNINGS /D_HAS_EXCEPTIONS=0 /EHsc /Wall /wd5045 /wd4514 /wd4710 /wd4820 /wd4820 /wd4668 /wd4464 /wd5039 /wd4255 /wd4626 ..\third-party\nadir\src\nadir.cpp ..\test\test_c99.c ..\test\test.cpp ..\test\main.cpp /link /out:test_debug.exe /pdb:test_debug.pdb 17 | 18 | popd 19 | 20 | exit /B %ERRORLEVEL% 21 | 22 | -------------------------------------------------------------------------------- /compile_clang.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set +e 4 | mkdir -p build 5 | 6 | OPT=-O3 7 | #DISASSEMBLY='-S -masm=intel' 8 | ASAN="" 9 | CXXFLAGS="$CXXFLAGS -Wall -Weverything -pedantic -Wno-zero-as-null-pointer-constant -Wno-old-style-cast -Wno-global-constructors -Wno-padded" 10 | ARCH=-m64 11 | 12 | clang++ -o ./build/test $OPT $DISASSEMBLY $ARCH -std=c++14 $CXXFLAGS $ASAN -Isrc third-party/nadir/src/nadir.cpp test/test_c99.c test/test.cpp test/main.cpp -pthread 13 | -------------------------------------------------------------------------------- /compile_clang_cov.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set +e 4 | mkdir -p build 5 | 6 | OPT=-O3 7 | #DISASSEMBLY='-S -masm=intel' 8 | ASAN="" 9 | CXXFLAGS="$CXXFLAGS -Wall -Weverything -pedantic -Wno-zero-as-null-pointer-constant -Wno-old-style-cast -Wno-global-constructors -Wno-padded" 10 | ARCH=-m64 11 | CXXFLAGS="$CXXFLAGS -fprofile-instr-generate -fcoverage-mapping" 12 | 13 | clang++ -o ./build/test_cov $OPT $DISASSEMBLY $ARCH -std=c++14 -DBIKESHED_ASSERTS $CXXFLAGS $ASAN -Isrc third-party/nadir/src/nadir.cpp test/test_c99.c test/test.cpp test/main.cpp -pthread 14 | 15 | pushd build 16 | ./test_cov 17 | xcrun llvm-profdata merge -o test_cov.profdata default.profraw 18 | xcrun llvm-cov show ./test_cov -instr-profile=test_cov.profdata >test_cov.txt 19 | popd 20 | -------------------------------------------------------------------------------- /compile_clang_debug.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set +e 4 | mkdir -p build 5 | 6 | #OPT=-O3 7 | OPT="-g -O1" 8 | #DISASSEMBLY='-S -masm=intel' 9 | ASAN="-fsanitize=address -fno-omit-frame-pointer" 10 | CXXFLAGS="$CXXFLAGS -Wall -Weverything -pedantic -Wno-zero-as-null-pointer-constant -Wno-old-style-cast -Wno-global-constructors -Wno-padded" 11 | ARCH=-m64 12 | 13 | clang++ -o ./build/test_debug -DBIKESHED_ASSERTS $OPT $DISASSEMBLY $ARCH -std=c++14 $CXXFLAGS $ASAN -Isrc third-party/nadir/src/nadir.cpp test/test_c99.c test/test.cpp test/main.cpp -pthread 14 | -------------------------------------------------------------------------------- /examples/compile_cl.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | call ..\find_mvsc.bat 4 | 5 | if NOT DEFINED VCINSTALLDIR ( 6 | echo "No Visual Studio installation found, aborting, try running run vcvarsall.bat first!" 7 | exit 1 8 | ) 9 | 10 | IF NOT EXIST ..\build ( 11 | mkdir ..\build 12 | ) 13 | 14 | pushd ..\build 15 | 16 | cl.exe /nologo /Zi /O2 /D_CRT_SECURE_NO_WARNINGS /D_HAS_EXCEPTIONS=0 /EHsc /W4 ..\third-party\nadir\src\nadir.cpp ..\examples\%1.cpp /link /out:%1.exe /pdb:%1.pdb 17 | 18 | popd 19 | 20 | exit /B %ERRORLEVEL% 21 | -------------------------------------------------------------------------------- /examples/compile_cl_debug.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | call ..\find_mvsc.bat 4 | 5 | if NOT DEFINED VCINSTALLDIR ( 6 | echo "No Visual Studio installation found, aborting, try running run vcvarsall.bat first!" 7 | exit 1 8 | ) 9 | 10 | IF NOT EXIST ..\build ( 11 | mkdir ..\build 12 | ) 13 | 14 | pushd ..\build 15 | 16 | cl.exe /nologo /Zi /DBIKESHED_ASSERTS /D_CRT_SECURE_NO_WARNINGS /D_HAS_EXCEPTIONS=0 /EHsc /W4 ..\third-party\nadir\src\nadir.cpp ..\examples\%1.cpp /link /out:%1_debug.exe /pdb:%1_debug.pdb 17 | 18 | popd 19 | 20 | exit /B %ERRORLEVEL% 21 | 22 | -------------------------------------------------------------------------------- /examples/compile_clang.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set +e 4 | mkdir -p ../build 5 | 6 | OPT=-O3 7 | #DISASSEMBLY='-S -masm=intel' 8 | ASAN="" 9 | CXXFLAGS="$CXXFLAGS -Wall -Weverything -pedantic -Wno-zero-as-null-pointer-constant -Wno-old-style-cast -Wno-sign-conversion -Wno-padded -Wno-unused-macros -Wno-c++98-compat -Wno-implicit-fallthrough -Wno-zero-as-null-pointer-constant -Wno-global-constructors" 10 | ARCH=-m64 11 | 12 | clang++ -o ../build/$1 $OPT $DISASSEMBLY $ARCH -std=c++14 $CXXFLAGS $ASAN -Isrc ../third-party/nadir/src/nadir.cpp $1.cpp -pthread 13 | -------------------------------------------------------------------------------- /examples/compile_clang_debug.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set +e 4 | mkdir -p ../build 5 | 6 | #OPT=-O3 7 | OPT="-g -O1" 8 | #DISASSEMBLY='-S -masm=intel' 9 | ASAN="-fsanitize=address -fno-omit-frame-pointer" 10 | CXXFLAGS="$CXXFLAGS -Wall -Weverything -pedantic -Wno-zero-as-null-pointer-constant -Wno-old-style-cast -Wno-sign-conversion -Wno-padded -Wno-unused-macros -Wno-c++98-compat -Wno-implicit-fallthrough -Wno-zero-as-null-pointer-constant -Wno-global-constructors" 11 | ARCH=-m64 12 | 13 | clang++ -o ../build/$1_debug -DBIKESHED_ASSERTS $OPT $DISASSEMBLY $ARCH -std=c++14 $CXXFLAGS $ASAN -Isrc ../third-party/nadir/src/nadir.cpp $1.cpp -pthread 14 | -------------------------------------------------------------------------------- /examples/helpers/counting_task.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../third-party/nadir/src/nadir.h" 4 | #include "../src/bikeshed.h" 5 | 6 | struct CountingTask 7 | { 8 | CountingTask() 9 | : execute_count(0) 10 | { 11 | } 12 | static Bikeshed_TaskResult Compute(Bikeshed , Bikeshed_TaskID , uint8_t , void* context) 13 | { 14 | CountingTask* _this = reinterpret_cast(context); 15 | nadir::AtomicAdd32(_this->execute_count, 1); 16 | return BIKESHED_TASK_RESULT_COMPLETE; 17 | } 18 | long volatile* execute_count; 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /examples/helpers/nadir_sync.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct NadirSync 4 | { 5 | Bikeshed_ReadyCallback m_ReadyCallback; 6 | explicit NadirSync() 7 | : m_ReadyCallback { signal } 8 | , m_Lock(nadir::CreateLock(malloc(nadir::GetNonReentrantLockSize()))) 9 | , m_ConditionVariable(nadir::CreateConditionVariable(malloc(nadir::GetConditionVariableSize()), m_Lock)) 10 | { 11 | } 12 | ~NadirSync() 13 | { 14 | nadir::DeleteConditionVariable(m_ConditionVariable); 15 | free(m_ConditionVariable); 16 | nadir::DeleteNonReentrantLock(m_Lock); 17 | free(m_Lock); 18 | } 19 | 20 | void WakeAll() 21 | { 22 | nadir::LockNonReentrantLock(this->m_Lock); 23 | nadir::WakeAll(this->m_ConditionVariable); 24 | nadir::UnlockNonReentrantLock(this->m_Lock); 25 | } 26 | 27 | static void signal(Bikeshed_ReadyCallback* , uint8_t, uint32_t ) 28 | { 29 | } 30 | nadir::HNonReentrantLock m_Lock; 31 | nadir::HConditionVariable m_ConditionVariable; 32 | }; 33 | -------------------------------------------------------------------------------- /examples/helpers/perf_timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(_WIN32) 6 | # include 7 | #endif 8 | 9 | #if defined(_WIN32) 10 | # include 11 | #elif defined(__APPLE__) 12 | # include 13 | #else 14 | # include 15 | #endif 16 | 17 | static uint64_t g_TicksPerSecond = 1000000u; 18 | 19 | inline void SetupTime() 20 | { 21 | #if defined(_WIN32) 22 | QueryPerformanceFrequency((LARGE_INTEGER*)&g_TicksPerSecond); 23 | #elif defined(__APPLE__) 24 | mach_timebase_info_data_t info; 25 | mach_timebase_info(&info); 26 | g_TicksPerSecond = (info.denom * (uint64_t)1000000000ul) / info.numer; 27 | #endif 28 | } 29 | 30 | inline uint64_t Tick() 31 | { 32 | uint64_t now; 33 | #if defined(_WIN32) 34 | QueryPerformanceCounter((LARGE_INTEGER*)&now); 35 | #elif defined(__APPLE__) 36 | now = mach_absolute_time(); 37 | #else 38 | timeval tv; 39 | gettimeofday(&tv, 0); 40 | now = ((uint64_t)tv.tv_sec) * 1000000 + tv.tv_usec; 41 | #endif 42 | return now; 43 | } 44 | 45 | inline double TicksToSeconds(uint64_t ticks) 46 | { 47 | return (double)ticks / (double)g_TicksPerSecond; 48 | } 49 | -------------------------------------------------------------------------------- /examples/helpers/stealing_worker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct StealingNodeWorker 4 | { 5 | StealingNodeWorker() 6 | : stop(0) 7 | , shed(0) 8 | , condition_variable(0) 9 | , thread(0) 10 | , index(0) 11 | , count(1) 12 | { 13 | } 14 | 15 | ~StealingNodeWorker() 16 | { 17 | } 18 | 19 | bool CreateThread(Bikeshed in_shed, nadir::HConditionVariable in_condition_variable, uint32_t worker_index, uint32_t worker_count, nadir::TAtomic32* in_stop) 20 | { 21 | shed = in_shed; 22 | stop = in_stop; 23 | condition_variable = in_condition_variable; 24 | index = worker_index; 25 | count = worker_count; 26 | thread = nadir::CreateThread(malloc(nadir::GetThreadSize()), StealingNodeWorker::Execute, 0, this); 27 | return thread != 0; 28 | } 29 | 30 | void Join() 31 | { 32 | nadir::JoinThread(thread, nadir::TIMEOUT_INFINITE); 33 | } 34 | 35 | void Dispose() 36 | { 37 | nadir::DeleteThread(thread); 38 | free(thread); 39 | } 40 | 41 | static int32_t ExecuteOne(Bikeshed shed, uint32_t start_channel, uint32_t channel_count) 42 | { 43 | uint32_t channel = start_channel; 44 | for (uint32_t c = 0; c < channel_count; ++c) 45 | { 46 | if (Bikeshed_ExecuteOne(shed, (uint8_t)channel)) 47 | { 48 | if (start_channel != channel) 49 | { 50 | return 2; 51 | } 52 | return 1; 53 | } 54 | channel = (channel + 1) % channel_count; 55 | } 56 | return 0; 57 | } 58 | 59 | static int32_t Execute(void* context) 60 | { 61 | StealingNodeWorker* _this = reinterpret_cast(context); 62 | 63 | while (*_this->stop == 0) 64 | { 65 | ExecuteOne(_this->shed, _this->index, _this->count); 66 | } 67 | return 0; 68 | } 69 | 70 | nadir::TAtomic32* stop; 71 | Bikeshed shed; 72 | nadir::HConditionVariable condition_variable; 73 | nadir::HThread thread; 74 | uint32_t index; 75 | uint32_t count; 76 | }; 77 | 78 | -------------------------------------------------------------------------------- /examples/helpers/task_producer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "counting_task.h" 4 | 5 | struct TaskProducer 6 | { 7 | 8 | TaskProducer() 9 | : executed_count(0) 10 | , start(0) 11 | , end(0) 12 | , channel(0) 13 | , shed(0) 14 | , condition_variable(0) 15 | , thread(0) 16 | { 17 | } 18 | 19 | ~TaskProducer() 20 | { 21 | } 22 | 23 | bool CreateThread(Bikeshed in_shed, nadir::HConditionVariable in_condition_variable, uint32_t in_start, uint32_t in_end, uint8_t in_channel, nadir::TAtomic32* in_executed) 24 | { 25 | shed = in_shed; 26 | start = in_start; 27 | end = in_end; 28 | channel = in_channel; 29 | executed_count = in_executed; 30 | condition_variable = in_condition_variable; 31 | thread = nadir::CreateThread(malloc(nadir::GetThreadSize()), TaskProducer::Execute, 0, this); 32 | return thread != 0; 33 | } 34 | 35 | void Join() 36 | { 37 | nadir::JoinThread(thread, nadir::TIMEOUT_INFINITE); 38 | } 39 | 40 | void Dispose() 41 | { 42 | nadir::DeleteThread(thread); 43 | free(thread); 44 | } 45 | 46 | static int32_t Execute(void* context) 47 | { 48 | TaskProducer* _this = reinterpret_cast(context); 49 | 50 | for (uint32_t c = 0; c < TASK_PRODUCER_BATCH_SIZE; ++c) 51 | { 52 | _this->counting_context[c].execute_count = _this->executed_count; 53 | } 54 | 55 | nadir::SleepConditionVariable(_this->condition_variable, nadir::TIMEOUT_INFINITE); 56 | 57 | uint32_t i = _this->start; 58 | 59 | do 60 | { 61 | uint32_t batch_count = (i + TASK_PRODUCER_BATCH_SIZE) < _this->end ? TASK_PRODUCER_BATCH_SIZE : (_this->end - i); 62 | BikeShed_TaskFunc task_funcs[TASK_PRODUCER_BATCH_SIZE]; 63 | void* task_contexts[TASK_PRODUCER_BATCH_SIZE]; 64 | for (uint32_t j = 0; j < TASK_PRODUCER_BATCH_SIZE; ++j) 65 | { 66 | task_funcs[j] = CountingTask::Compute; 67 | task_contexts[j] = &_this->counting_context[j]; 68 | } 69 | Bikeshed_TaskID task_ids[TASK_PRODUCER_BATCH_SIZE]; 70 | while(0 == Bikeshed_CreateTasks(_this->shed, batch_count, task_funcs, task_contexts, task_ids)) 71 | { 72 | nadir::Sleep(1000); 73 | } 74 | Bikeshed_SetTasksChannel(_this->shed, batch_count, task_ids, _this->channel); 75 | Bikeshed_ReadyTasks(_this->shed, batch_count, task_ids); 76 | 77 | i += batch_count; 78 | } while (i != _this->end); 79 | return 0; 80 | } 81 | 82 | CountingTask counting_context[TASK_PRODUCER_BATCH_SIZE]; 83 | nadir::TAtomic32* executed_count; 84 | uint32_t start; 85 | uint32_t end; 86 | uint8_t channel; 87 | Bikeshed shed; 88 | nadir::HConditionVariable condition_variable; 89 | nadir::HThread thread; 90 | }; 91 | 92 | -------------------------------------------------------------------------------- /examples/helpers/thread_worker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct NadirWorker 4 | { 5 | NadirWorker() 6 | : stop(0) 7 | , shed(0) 8 | , condition_variable(0) 9 | , thread(0) 10 | { 11 | } 12 | 13 | ~NadirWorker() 14 | { 15 | } 16 | 17 | bool CreateThread(Bikeshed in_shed, nadir::HConditionVariable in_condition_variable, nadir::TAtomic32* in_stop) 18 | { 19 | shed = in_shed; 20 | stop = in_stop; 21 | condition_variable = in_condition_variable; 22 | thread = nadir::CreateThread(malloc(nadir::GetThreadSize()), NadirWorker::Execute, 0, this); 23 | return thread != 0; 24 | } 25 | 26 | void Join() 27 | { 28 | nadir::JoinThread(thread, nadir::TIMEOUT_INFINITE); 29 | } 30 | 31 | void Dispose() 32 | { 33 | nadir::DeleteThread(thread); 34 | free(thread); 35 | } 36 | 37 | static int32_t Execute(void* context) 38 | { 39 | NadirWorker* _this = reinterpret_cast(context); 40 | 41 | nadir::SleepConditionVariable(_this->condition_variable, nadir::TIMEOUT_INFINITE); 42 | 43 | while (*_this->stop == 0) 44 | { 45 | Bikeshed_ExecuteOne(_this->shed, 0); 46 | } 47 | return 0; 48 | } 49 | 50 | nadir::TAtomic32* stop; 51 | Bikeshed shed; 52 | nadir::HConditionVariable condition_variable; 53 | nadir::HThread thread; 54 | }; 55 | -------------------------------------------------------------------------------- /examples/multi_threaded_task_consumer.cpp: -------------------------------------------------------------------------------- 1 | #define BIKESHED_IMPLEMENTATION 2 | #define BIKESHED_L1CACHE_SIZE 64 3 | 4 | #include "../src/bikeshed.h" 5 | #include "../third-party/nadir/src/nadir.h" 6 | 7 | #include "helpers/perf_timer.h" 8 | #include "helpers/counting_task.h" 9 | #include "helpers/nadir_sync.h" 10 | #include "helpers/thread_worker.h" 11 | 12 | #include 13 | 14 | int main(int , char** ) 15 | { 16 | SetupTime(); 17 | 18 | long volatile executed_count = 0; 19 | long dispatched_count = 0; 20 | 21 | const uint32_t COUNT = 4000000; 22 | const uint32_t BATCH_COUNT = 10; 23 | 24 | nadir::TAtomic32 stop = 0; 25 | 26 | CountingTask counting_context; 27 | counting_context.execute_count = &executed_count; 28 | 29 | NadirSync sync; 30 | Bikeshed shed = Bikeshed_Create(malloc(BIKESHED_SIZE(COUNT / 10, 0, 1)), COUNT / 10, 0, 1, &sync.m_ReadyCallback); 31 | 32 | static const uint32_t CONSUMER_WORKER_COUNT = 7; 33 | NadirWorker consumer[CONSUMER_WORKER_COUNT]; 34 | for (uint32_t w = 0; w < CONSUMER_WORKER_COUNT; ++w) 35 | { 36 | consumer[w].CreateThread(shed, sync.m_ConditionVariable, &stop); 37 | } 38 | 39 | nadir::Sleep(100000); 40 | 41 | sync.WakeAll(); 42 | 43 | uint64_t start_time = Tick(); 44 | 45 | for (uint32_t i = 0; i < COUNT; i += BATCH_COUNT) 46 | { 47 | BikeShed_TaskFunc task_funcs[BATCH_COUNT]; 48 | void* task_contexts[BATCH_COUNT]; 49 | for (uint32_t j = 0; j < BATCH_COUNT; ++j) 50 | { 51 | task_funcs[j] = CountingTask::Compute; 52 | task_contexts[j] = &counting_context; 53 | } 54 | Bikeshed_TaskID task_ids[BATCH_COUNT]; 55 | Bikeshed_CreateTasks(shed, BATCH_COUNT, task_funcs, task_contexts, task_ids); 56 | Bikeshed_ReadyTasks(shed, BATCH_COUNT, task_ids); 57 | dispatched_count += BATCH_COUNT; 58 | } 59 | 60 | while (executed_count != COUNT) 61 | { 62 | Bikeshed_ExecuteOne(shed, 0); 63 | } 64 | 65 | uint64_t end_time = Tick(); 66 | 67 | nadir::AtomicAdd32(&stop, 1); 68 | sync.WakeAll(); 69 | 70 | for (uint32_t w = 0; w < CONSUMER_WORKER_COUNT; ++w) 71 | { 72 | consumer[w].Join(); 73 | } 74 | 75 | for (uint32_t w = 0; w < CONSUMER_WORKER_COUNT; ++w) 76 | { 77 | consumer[w].Dispose(); 78 | } 79 | 80 | uint64_t elapsed_ticks = end_time - start_time; 81 | double elapsed_seconds = TicksToSeconds(elapsed_ticks); 82 | double task_per_second = COUNT / elapsed_seconds; 83 | double seconds_per_task = elapsed_seconds / COUNT; 84 | 85 | double ns_per_task = 1000000000.0 * seconds_per_task; 86 | 87 | printf("MultiThreadedTaskConsumer: tasks per second: %.3lf, task time: %.3lf ns\n", task_per_second, ns_per_task); 88 | 89 | free(shed); 90 | return 0; 91 | } 92 | -------------------------------------------------------------------------------- /examples/multi_threaded_task_producer.cpp: -------------------------------------------------------------------------------- 1 | #define BIKESHED_IMPLEMENTATION 2 | #define BIKESHED_L1CACHE_SIZE 64 3 | 4 | #include "../src/bikeshed.h" 5 | #include "../third-party/nadir/src/nadir.h" 6 | 7 | #include "helpers/perf_timer.h" 8 | #include "helpers/counting_task.h" 9 | #include "helpers/nadir_sync.h" 10 | #include "helpers/task_producer.h" 11 | 12 | #include 13 | 14 | int main(int , char** ) 15 | { 16 | SetupTime(); 17 | 18 | long volatile executed_count = 0; 19 | 20 | const uint32_t COUNT = 4000000; 21 | 22 | nadir::TAtomic32 stop = 0; 23 | 24 | CountingTask counting_context; 25 | counting_context.execute_count = &executed_count; 26 | 27 | NadirSync sync; 28 | Bikeshed shed = Bikeshed_Create(malloc(BIKESHED_SIZE(COUNT, 0, 1)), COUNT, 0, 1, &sync.m_ReadyCallback); 29 | 30 | static const uint32_t PRODUCER_WORKER_COUNT = 7; 31 | TaskProducer workers[PRODUCER_WORKER_COUNT]; 32 | for (uint32_t w = 0; w < PRODUCER_WORKER_COUNT; ++w) 33 | { 34 | workers[w].CreateThread(shed, sync.m_ConditionVariable, w * COUNT / PRODUCER_WORKER_COUNT, (w + 1) * COUNT / PRODUCER_WORKER_COUNT, 0, &executed_count); 35 | } 36 | 37 | nadir::Sleep(100000); 38 | sync.WakeAll(); 39 | 40 | uint64_t start_time = Tick(); 41 | 42 | while (executed_count != COUNT) 43 | { 44 | Bikeshed_ExecuteOne(shed, 0); 45 | } 46 | 47 | uint64_t end_time = Tick(); 48 | 49 | nadir::AtomicAdd32(&stop, 1); 50 | sync.WakeAll(); 51 | 52 | for (uint32_t w = 0; w < PRODUCER_WORKER_COUNT; ++w) 53 | { 54 | workers[w].Join(); 55 | } 56 | 57 | for (uint32_t w = 0; w < PRODUCER_WORKER_COUNT; ++w) 58 | { 59 | workers[w].Dispose(); 60 | } 61 | 62 | uint64_t elapsed_ticks = end_time - start_time; 63 | double elapsed_seconds = TicksToSeconds(elapsed_ticks); 64 | double task_per_second = COUNT / elapsed_seconds; 65 | double seconds_per_task = elapsed_seconds / COUNT; 66 | 67 | double ns_per_task = 1000000000.0 * seconds_per_task; 68 | 69 | printf("MultiThreadedTaskProducer: tasks per second: %.3lf, task time: %.3lf ns\n", task_per_second, ns_per_task); 70 | 71 | free(shed); 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /examples/multi_threaded_task_producer_and_consumer.cpp: -------------------------------------------------------------------------------- 1 | #define BIKESHED_IMPLEMENTATION 2 | #define BIKESHED_L1CACHE_SIZE 64 3 | 4 | #include "../src/bikeshed.h" 5 | #include "../third-party/nadir/src/nadir.h" 6 | 7 | #include "helpers/perf_timer.h" 8 | #include "helpers/counting_task.h" 9 | #include "helpers/nadir_sync.h" 10 | #include "helpers/thread_worker.h" 11 | #include "helpers/task_producer.h" 12 | 13 | #include 14 | 15 | #define PROCESSOR_CORE_COUNT 8 16 | 17 | int main(int , char** ) 18 | { 19 | SetupTime(); 20 | 21 | long volatile executed_count = 0; 22 | 23 | const uint32_t COUNT = 4000000; 24 | 25 | nadir::TAtomic32 stop = 0; 26 | 27 | NadirSync sync; 28 | Bikeshed shed = Bikeshed_Create(malloc(BIKESHED_SIZE(COUNT, 0, 1)), COUNT, 0, 1, &sync.m_ReadyCallback); 29 | 30 | static const uint32_t PRODUCER_WORKER_COUNT = PROCESSOR_CORE_COUNT; 31 | TaskProducer producer[PRODUCER_WORKER_COUNT]; 32 | for (uint32_t w = 0; w < PRODUCER_WORKER_COUNT; ++w) 33 | { 34 | producer[w].CreateThread(shed, sync.m_ConditionVariable, w * COUNT / PRODUCER_WORKER_COUNT, (w + 1) * COUNT / PRODUCER_WORKER_COUNT, 0, &executed_count); 35 | } 36 | 37 | static const uint32_t CONSUMER_WORKER_COUNT = PROCESSOR_CORE_COUNT - 1; 38 | NadirWorker consumers[CONSUMER_WORKER_COUNT]; 39 | for (uint32_t w = 0; w < CONSUMER_WORKER_COUNT; ++w) 40 | { 41 | consumers[w].CreateThread(shed, sync.m_ConditionVariable, &stop); 42 | } 43 | 44 | nadir::Sleep(100000); 45 | sync.WakeAll(); 46 | 47 | uint64_t start_time = Tick(); 48 | 49 | while (executed_count != COUNT) 50 | { 51 | Bikeshed_ExecuteOne(shed, 0); 52 | } 53 | 54 | uint64_t end_time = Tick(); 55 | 56 | nadir::AtomicAdd32(&stop, 1); 57 | sync.WakeAll(); 58 | 59 | for (uint32_t w = 0; w < PRODUCER_WORKER_COUNT; ++w) 60 | { 61 | producer[w].Join(); 62 | } 63 | 64 | for (uint32_t w = 0; w < PRODUCER_WORKER_COUNT; ++w) 65 | { 66 | producer[w].Dispose(); 67 | } 68 | 69 | for (uint32_t w = 0; w < CONSUMER_WORKER_COUNT; ++w) 70 | { 71 | consumers[w].Join(); 72 | } 73 | 74 | for (uint32_t w = 0; w < CONSUMER_WORKER_COUNT; ++w) 75 | { 76 | consumers[w].Dispose(); 77 | } 78 | 79 | uint64_t elapsed_ticks = end_time - start_time; 80 | double elapsed_seconds = TicksToSeconds(elapsed_ticks); 81 | double task_per_second = COUNT / elapsed_seconds; 82 | double seconds_per_task = elapsed_seconds / COUNT; 83 | 84 | double ns_per_task = 1000000000.0 * seconds_per_task; 85 | 86 | printf("MultiThreadedTaskProducerAndConsumer: tasks per second: %.3lf, task time: %.3lf ns\n", task_per_second, ns_per_task); 87 | 88 | free(shed); 89 | return 0; 90 | } 91 | -------------------------------------------------------------------------------- /examples/single_threaded_task_consumer.cpp: -------------------------------------------------------------------------------- 1 | #define BIKESHED_IMPLEMENTATION 2 | #define BIKESHED_L1CACHE_SIZE 64 3 | 4 | #include "../src/bikeshed.h" 5 | #include "../third-party/nadir/src/nadir.h" 6 | 7 | #include "helpers/perf_timer.h" 8 | #include "helpers/counting_task.h" 9 | 10 | #include 11 | 12 | int main(int , char** ) 13 | { 14 | SetupTime(); 15 | 16 | long volatile executed_count = 0; 17 | long dispatched_count = 0; 18 | 19 | const uint32_t COUNT = 4000000; 20 | const uint32_t BATCH_COUNT = 10; 21 | 22 | CountingTask counting_context; 23 | counting_context.execute_count = &executed_count; 24 | 25 | Bikeshed shed = Bikeshed_Create(malloc(BIKESHED_SIZE(BATCH_COUNT, 0, 1)), BATCH_COUNT, 0, 1, 0); 26 | 27 | uint64_t start_time = Tick(); 28 | 29 | for (uint32_t i = 0; i < COUNT; i += BATCH_COUNT) 30 | { 31 | BikeShed_TaskFunc task_funcs[BATCH_COUNT]; 32 | void* task_contexts[BATCH_COUNT]; 33 | for (uint32_t j = 0; j < BATCH_COUNT; ++j) 34 | { 35 | task_funcs[j] = CountingTask::Compute; 36 | task_contexts[j] = &counting_context; 37 | } 38 | Bikeshed_TaskID task_ids[BATCH_COUNT]; 39 | Bikeshed_CreateTasks(shed, BATCH_COUNT, task_funcs, task_contexts, task_ids); 40 | Bikeshed_ReadyTasks(shed, BATCH_COUNT, task_ids); 41 | dispatched_count += BATCH_COUNT; 42 | for (uint32_t j = 0; j < BATCH_COUNT; ++j) 43 | { 44 | Bikeshed_ExecuteOne(shed, 0); 45 | } 46 | } 47 | 48 | uint64_t end_time = Tick(); 49 | 50 | uint64_t elapsed_ticks = end_time - start_time; 51 | double elapsed_seconds = TicksToSeconds(elapsed_ticks); 52 | double task_per_second = COUNT / elapsed_seconds; 53 | double seconds_per_task = elapsed_seconds / COUNT; 54 | 55 | double ns_per_task = 1000000000.0 * seconds_per_task; 56 | 57 | printf("SingleThreadedTaskConsumer: tasks per second: %.3lf, task time: %.3lf ns\n", task_per_second, ns_per_task); 58 | 59 | free(shed); 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /examples/stealing_multi_threaded_task_producer_and_consumer.cpp: -------------------------------------------------------------------------------- 1 | #define BIKESHED_IMPLEMENTATION 2 | #define BIKESHED_L1CACHE_SIZE 64 3 | //#define BIKESHED_CPU_YIELD 4 | 5 | #include "../src/bikeshed.h" 6 | #include "../third-party/nadir/src/nadir.h" 7 | 8 | #define PROCESSOR_CORE_COUNT 8 9 | #define PRODUCER_WORKER_COUNT (PROCESSOR_CORE_COUNT / 2) 10 | #define CONSUMER_WORKER_COUNT ((PROCESSOR_CORE_COUNT / 2) - 1) 11 | #define TASK_PRODUCER_BATCH_SIZE 8 12 | #define CHANNEL_COUNT PRODUCER_WORKER_COUNT 13 | #define STEALING_CHANNEL_COUNT CHANNEL_COUNT 14 | 15 | #include "helpers/perf_timer.h" 16 | #include "helpers/counting_task.h" 17 | #include "helpers/nadir_sync.h" 18 | #include "helpers/stealing_worker.h" 19 | #include "helpers/task_producer.h" 20 | 21 | #include 22 | 23 | int main(int , char** ) 24 | { 25 | SetupTime(); 26 | 27 | long volatile executed_count = 0; 28 | 29 | const uint32_t COUNT = 0x1ffffffu; 30 | 31 | nadir::TAtomic32 stop = 0; 32 | 33 | NadirSync sync; 34 | Bikeshed shed = Bikeshed_Create(malloc(BIKESHED_SIZE(0x7fffffu, 0, CHANNEL_COUNT)), 0x7fffffu, 0, CHANNEL_COUNT, &sync.m_ReadyCallback); 35 | 36 | TaskProducer producer[PRODUCER_WORKER_COUNT]; 37 | for (uint32_t w = 0; w < PRODUCER_WORKER_COUNT; ++w) 38 | { 39 | producer[w].CreateThread(shed, sync.m_ConditionVariable, w * COUNT / PRODUCER_WORKER_COUNT, (w + 1) * COUNT / PRODUCER_WORKER_COUNT, (uint8_t)(w * CHANNEL_COUNT / PRODUCER_WORKER_COUNT), &executed_count); 40 | } 41 | 42 | StealingNodeWorker consumers[CONSUMER_WORKER_COUNT]; 43 | for (uint32_t w = 0; w < CONSUMER_WORKER_COUNT; ++w) 44 | { 45 | consumers[w].CreateThread(shed, sync.m_ConditionVariable, (uint8_t)((w + 1) * CHANNEL_COUNT / (CONSUMER_WORKER_COUNT + 1)), STEALING_CHANNEL_COUNT, &stop); 46 | } 47 | 48 | nadir::Sleep(100000); 49 | sync.WakeAll(); 50 | 51 | uint64_t start_time = Tick(); 52 | 53 | while (executed_count != COUNT) 54 | { 55 | StealingNodeWorker::ExecuteOne(shed, 0, STEALING_CHANNEL_COUNT); 56 | } 57 | 58 | uint64_t end_time = Tick(); 59 | 60 | nadir::AtomicAdd32(&stop, 1); 61 | sync.WakeAll(); 62 | 63 | for (uint32_t w = 0; w < PRODUCER_WORKER_COUNT; ++w) 64 | { 65 | producer[w].Join(); 66 | } 67 | 68 | for (uint32_t w = 0; w < PRODUCER_WORKER_COUNT; ++w) 69 | { 70 | producer[w].Dispose(); 71 | } 72 | 73 | for (uint32_t w = 0; w < CONSUMER_WORKER_COUNT; ++w) 74 | { 75 | consumers[w].Join(); 76 | } 77 | 78 | for (uint32_t w = 0; w < CONSUMER_WORKER_COUNT; ++w) 79 | { 80 | consumers[w].Dispose(); 81 | } 82 | 83 | uint64_t elapsed_ticks = end_time - start_time; 84 | double elapsed_seconds = TicksToSeconds(elapsed_ticks); 85 | double task_per_second = COUNT / elapsed_seconds; 86 | double seconds_per_task = elapsed_seconds / COUNT; 87 | 88 | double ns_per_task = 1000000000.0 * seconds_per_task; 89 | 90 | printf("StealingMultiThreadedTaskProducerAndConsumer: tasks per second: %.3lf, task time: %.3lf ns\n", task_per_second, ns_per_task); 91 | 92 | free(shed); 93 | 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /find_mvsc.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | 3 | if NOT DEFINED VCINSTALLDIR ( 4 | if exist "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" ( 5 | call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64 6 | ) 7 | ) 8 | 9 | if NOT DEFINED VCINSTALLDIR ( 10 | if exist "C:\Program Files (x86)\Microsoft Visual Studio 15.0\VC\vcvarsall.bat" ( 11 | call "C:\Program Files (x86)\Microsoft Visual Studio 15.0\VC\vcvarsall.bat" amd64 12 | ) 13 | ) 14 | 15 | if NOT DEFINED VCINSTALLDIR ( 16 | if exist "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" ( 17 | call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 18 | ) 19 | ) 20 | 21 | if NOT DEFINED VCINSTALLDIR ( 22 | if exist "C:\Program Files (x86)\Microsoft Visual Studio 13.0\VC\vcvarsall.bat" ( 23 | call "C:\Program Files (x86)\Microsoft Visual Studio 13.0\VC\vcvarsall.bat" amd64 24 | ) 25 | ) 26 | 27 | if NOT DEFINED VCINSTALLDIR ( 28 | if exist "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" ( 29 | call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" amd64 30 | ) 31 | ) 32 | 33 | echo Visual Studio installed at %VCINSTALLDIR% 34 | 35 | -------------------------------------------------------------------------------- /src/bikeshed.h: -------------------------------------------------------------------------------- 1 | #ifndef BIKESHED_INCLUDEGUARD_PRIVATE_H 2 | #define BIKESHED_INCLUDEGUARD_PRIVATE_H 3 | 4 | /* 5 | bikeshed.h - public domain - Dan Engelbrecht @DanEngelbrecht, 2019 6 | 7 | # BIKESHED 8 | 9 | Lock free hierarchical work scheduler, builds with MSVC, Clang and GCC, header only, C99 compliant, MIT license. 10 | 11 | See github for latest version: https://github.com/DanEngelbrecht/bikeshed 12 | 13 | See design blogs at: https://danengelbrecht.github.io 14 | 15 | ## Version history 16 | 17 | ### Version v0.4 18/5 2019 18 | 19 | **Pre-release 4** 20 | 21 | ### API changes 22 | - Bikeshed_AddDependencies to take array of parent task task_ids 23 | - Bikeshed_ReadyCallback now gets called per channel range 24 | 25 | ### API additions 26 | - Bikeshed_FreeTasks 27 | - BIKESHED_L1CACHE_SIZE to control ready head alignement - no padding/alignement added by default 28 | - BIKESHED_CPU_YIELD to control yielding in high-contention scenarios 29 | 30 | ### Fixes 31 | - Added default (non-atomic) implementations for helper for unsupported platforms/compilers 32 | - Codacy analisys and badge 33 | - More input validation on public apis when BIKESHED_ASSERTS is enabled 34 | - Batch allocation of task indexes 35 | - Batch allocation of dependency indexes 36 | - Batch free of task indexes 37 | - Batch free of dependency indexes 38 | - Fixed broken channel range detection when resolving dependencies 39 | 40 | ### Version v0.3 1/5 2019 41 | 42 | **Pre-release 3** 43 | 44 | #### Fixes 45 | 46 | - Ready callback is now called when a task is readied via dependency resolve 47 | - Tasks are readied in batch when possible 48 | 49 | ### Version v0.2 29/4 2019 50 | 51 | **Pre-release 2** 52 | 53 | #### Fixes 54 | 55 | - Internal cleanups 56 | - Fixed warnings and removed clang warning suppressions 57 | - `-Wno-sign-conversion` 58 | - `-Wno-unused-macros` 59 | - `-Wno-c++98-compat` 60 | - `-Wno-implicit-fallthrough` 61 | - Made it compile cleanly with clang++ on Windows 62 | 63 | ### Version v0.1 26/4 2019 64 | 65 | **Pre-release 1** 66 | 67 | ## Usage 68 | In *ONE* source file, put: 69 | 70 | ```C 71 | #define BIKESHED_IMPLEMENTATION 72 | // Define any overrides of platform specific implementations before including bikeshed.h. 73 | #include "bikeshed.h" 74 | ``` 75 | 76 | Other source files should just #include "bikeshed.h" 77 | 78 | ## Macros 79 | 80 | BIKESHED_IMPLEMENTATION 81 | Define in one compilation unit to include implementation. 82 | 83 | BIKESHED_ASSERTS 84 | Define if you want Bikeshed to validate API usage, make sure to set the assert callback using 85 | Bikeshed_SetAssert and take appropriate action if triggered. 86 | 87 | BIKESHED_ATOMICADD 88 | Macro for platform specific implementation of an atomic addition. Returns the result of the operation. 89 | #define BIKESHED_ATOMICADD(value, amount) return MyAtomicAdd(value, amount) 90 | 91 | BIKESHED_ATOMICCAS 92 | Macro for platform specific implementation of an atomic compare and swap. Returns the previous value of "store". 93 | #define BIKESHED_ATOMICCAS(store, compare, value) return MyAtomicCAS(store, compare, value); 94 | 95 | BIKESHED_CPU_YIELD 96 | Macro to yield the CPU when there is contention. 97 | 98 | BIKESHED_L1CACHE_SIZE 99 | Macro to specify the L1 cache line size - used to align data and ready heads to cache line boundaries. If not 100 | defined the data will no be padded. If the CPU cacheline size is 64, set BIKESHED_L1CACHE_SIZE to reduce cache thrashing. 101 | 102 | ## Notes 103 | Macros, typedefs, structs and functions suffixed with "_private" are internal and subject to change. 104 | 105 | ## Dependencies 106 | Standard C library headers: 107 | #include 108 | #include 109 | 110 | If either BIKESHED_ATOMICADD, BIKESHED_ATOMICCAS or BIKESHED_CPU_YIELD is not defined the MSVC/Windows implementation will 111 | include to get access to `_InterlockedExchangeAdd`, `_InterlockedCompareExchange` and `YieldProcessor` respectively. 112 | 113 | The GCC/clang implementation relies on the compiler intrisics `__sync_fetch_and_add` and `__sync_val_compare_and_swap`. 114 | If BIKESHED_CPU_YIELD is not defined will be included to get access to `_mm_pause` 115 | 116 | ## License 117 | 118 | MIT License 119 | 120 | Copyright (c) 2019 Dan Engelbrecht 121 | 122 | Permission is hereby granted, free of charge, to any person obtaining a copy 123 | of this software and associated documentation files (the "Software"), to deal 124 | in the Software without restriction, including without limitation the rights 125 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 126 | copies of the Software, and to permit persons to whom the Software is 127 | furnished to do so, subject to the following conditions: 128 | 129 | The above copyright notice and this permission notice shall be included in all 130 | copies or substantial portions of the Software. 131 | 132 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 133 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 134 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 135 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 136 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 137 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 138 | SOFTWARE. 139 | */ 140 | 141 | #include 142 | #include 143 | 144 | #ifdef __cplusplus 145 | extern "C" { 146 | #endif 147 | 148 | // ------------- Public API Begin 149 | 150 | // Custom assert hook 151 | // If BIKESHED_ASSERTS is defined, Bikeshed will validate input parameters to make sure 152 | // correct API usage. 153 | // The callback will be called if an API error is detected and the library function will bail. 154 | // 155 | // Warning: If an API function asserts the state of the Bikeshed might be compromised. 156 | // To reset the assert callback call Bikeshed_SetAssert(0); 157 | typedef void (*Bikeshed_Assert)(const char* expression, const char* file, int line); 158 | void Bikeshed_SetAssert(Bikeshed_Assert assert_func); 159 | 160 | // Bikeshed handle 161 | typedef struct Bikeshed_Shed_private* Bikeshed; 162 | 163 | // Callback instance and hook for the library user to implement. 164 | // Called when one or more tasks are "readied" - either by a call to Bikeshed_ReadyTasks or if 165 | // a task has been resolved due to all dependencies have finished. 166 | // 167 | // struct MyReadyCallback { 168 | // // Add the Bikeshed_ReadyCallback struct *first* in your struct 169 | // struct Bikeshed_ReadyCallback cb; 170 | // // Add any custom data here 171 | // static void Ready(struct Bikeshed_ReadyCallback* ready_callback, uint32_t ready_count) 172 | // { 173 | // MyReadyCallback* _this = (MyReadyCallback*)ready_callback; 174 | // // Your code goes here 175 | // } 176 | // }; 177 | // MyReadyCallback myCallback; 178 | // myCallback.cb.SignalReady = MyReadyCallback::Ready; 179 | // bikeshed = CreateBikeshed(mem, max_task_count, max_dependency_count, channel_count, &myCallback.cb); 180 | // 181 | // The Bikeshed_ReadyCallback instance must have a lifetime that starts before and ends after the Bikeshed instance. 182 | struct Bikeshed_ReadyCallback 183 | { 184 | void (*SignalReady)(struct Bikeshed_ReadyCallback* ready_callback, uint8_t channel, uint32_t ready_count); 185 | }; 186 | 187 | // Task identifier 188 | typedef uint32_t Bikeshed_TaskID; 189 | 190 | // Result codes for task execution 191 | enum Bikeshed_TaskResult 192 | { 193 | BIKESHED_TASK_RESULT_COMPLETE, // Task is complete, dependecies will be resolved and the task is automatically freed 194 | BIKESHED_TASK_RESULT_BLOCKED // Task is blocked, call ReadyTasks on the task id when ready to execute again 195 | }; 196 | 197 | // Task execution callback 198 | // A task execution function may: 199 | // - Create new tasks 200 | // - Add new dependencies to tasks that are not "Ready" or "Executing" 201 | // - Ready other tasks that are not "Ready" or "Executing" 202 | // - Deallocate any memory associated with the context 203 | // 204 | // A task execution function may not: 205 | // - Add dependencies to the executing task 206 | // - Ready the executing task 207 | // - Destroy the Bikeshed instance 208 | // 209 | // A task execution function should not: 210 | // - Block, if the function blocks the thread that called Bikeshed_ExecuteOne will block, return BIKESHED_TASK_RESULT_BLOCKED instead 211 | // - Call Bikeshed_ExecuteOne, this works but makes little sense 212 | typedef enum Bikeshed_TaskResult (*BikeShed_TaskFunc)(Bikeshed shed, Bikeshed_TaskID task_id, uint8_t channel, void* context); 213 | 214 | // Calculates the memory needed for a Bikeshed instance 215 | // BIKESHED_SIZE is a macro which allows the Bikeshed to be allocated on the stack without heap allocations 216 | // 217 | // max_task_count: 1 to 8 388 607 tasks 218 | // max_dependency_count: 0 to 8 388 607 dependencies 219 | // channel_count: 1 to 255 channels 220 | #define BIKESHED_SIZE(max_task_count, max_dependency_count, channel_count) 221 | 222 | // Create a Bikeshed at the provided memory location 223 | // Use BIKESHED_SIZE to get the required size of the memory block 224 | // max_task_count, max_dependency_count and channel_count must match call to BIKESHED_SIZE 225 | // 226 | // ready_callback is optional and may be 0 227 | // 228 | // The returned Bikeshed is a typed pointer that points to same address as `mem` 229 | Bikeshed Bikeshed_Create(void* mem, uint32_t max_task_count, uint32_t max_dependency_count, uint8_t channel_count, struct Bikeshed_ReadyCallback* ready_callback); 230 | 231 | // Clones the state of a Bikeshed 232 | // Use BIKESHED_SIZE to get the required size of the memory block 233 | // max_task_count, max_dependency_count and channel_count must match call to BIKESHED_SIZE 234 | // 235 | // Makes a complete copy of the current state of a Bikeshed, executing on the clone copy will not affect the 236 | // bikeshed state of the original. 237 | // 238 | // The returned Bikeshed is a typed pointer that points to same address as mem 239 | Bikeshed Bikeshed_CloneState(void* mem, Bikeshed original, uint32_t shed_size); 240 | 241 | // Creates one or more tasks 242 | // Reserves task_count number of tasks from the internal Bikeshed state 243 | // Caller is responsible for making sure the context pointers are valid until the corresponding task is executed 244 | // Tasks will not be executed until they are readied with Bikeshed_ReadyTasks or if it has dependencies that have all been resolved 245 | // Returns: 246 | // 1 - Success 247 | // 0 - Not enough free task instances in the bikeshed 248 | int Bikeshed_CreateTasks(Bikeshed shed, uint32_t task_count, BikeShed_TaskFunc* task_functions, void** contexts, Bikeshed_TaskID* out_task_ids); 249 | 250 | // Frees one or more tasks 251 | // Explicitly freeing tasks should only be done when you need to revert a Bikeshed_CreateTasks without 252 | // actually executing the created tasks. The task may not be ready or have any ready task dependencies. 253 | // Make sure to free tasks that are dependencies of this task first. 254 | // Any tasks that has dependencies on a freed task will have the dependencies removed. 255 | // Trying to free a tasks that has unresolved (or un-freed) dependencies is not allowed. 256 | void Bikeshed_FreeTasks(Bikeshed shed, uint32_t task_count, const Bikeshed_TaskID* task_ids); 257 | 258 | // Set the channel of one or more tasks 259 | // The default channel for a task is 0. 260 | // 261 | // The channel is used when calling Bikeshed_ExecuteOne 262 | void Bikeshed_SetTasksChannel(Bikeshed shed, uint32_t task_count, Bikeshed_TaskID* task_ids, uint8_t channel); 263 | 264 | // Readies one or more tasks for execution 265 | // Tasks must not have any unresolved dependencies 266 | // Task execution order is not guarranteed - use dependecies to eforce task execution order 267 | // The Bikeshed_ReadyCallback of the shed (if any is set) will be called with task_count as number of readied tasks 268 | void Bikeshed_ReadyTasks(Bikeshed shed, uint32_t task_count, const Bikeshed_TaskID* task_ids); 269 | 270 | // Add dependencies to a task 271 | // A task can have zero or more dependencies 272 | // If a task has been made ready with Bikeshed_ReadyTasks you may not add dependencies to the task. 273 | // A task that has been made ready may not be added as a dependency to another task. 274 | // A task may have multiple "parents" - it is valid to add the same task as child to more than one task. 275 | // Creating a task hierarchy that has circular dependencies makes it impossible to resolve. 276 | // 277 | // Returns: 278 | // 1 - Success 279 | // 0 - Not enough free dependency instances in the bikeshed 280 | int Bikeshed_AddDependencies(Bikeshed shed, uint32_t task_count, const Bikeshed_TaskID* task_id, uint32_t dependency_task_count, const Bikeshed_TaskID* dependency_task_ids); 281 | 282 | // Execute one task 283 | // Checks the ready queue of channel and executes the task callback 284 | // Any parent dependencies are resolved and if any parent gets all its dependencies resolved they will be made ready for execution. 285 | // 286 | // Returns: 287 | // 1 - Executed one task 288 | // 2 - No task are ready for execution 289 | int Bikeshed_ExecuteOne(Bikeshed shed, uint8_t channel); 290 | 291 | // ------------- Public API End 292 | 293 | 294 | typedef uint32_t Bikeshed_TaskIndex_private; 295 | typedef uint32_t Bikeshed_DependencyIndex_private; 296 | typedef uint32_t Bikeshed_ReadyIndex_private; 297 | 298 | struct Bikeshed_Dependency_private 299 | { 300 | Bikeshed_TaskIndex_private m_ParentTaskIndex; 301 | Bikeshed_DependencyIndex_private m_NextDependencyIndex; 302 | }; 303 | 304 | struct Bikeshed_Task_private 305 | { 306 | int32_t volatile m_ChildDependencyCount; 307 | Bikeshed_TaskID m_TaskID; 308 | uint32_t m_Channel : 8; 309 | uint32_t m_FirstDependencyIndex : 24; 310 | BikeShed_TaskFunc m_TaskFunc; 311 | void* m_TaskContext; 312 | }; 313 | 314 | struct AtomicIndex 315 | { 316 | int32_t volatile m_Index; 317 | #if defined(BIKESHED_L1CACHE_SIZE) 318 | uint8_t padding[(BIKESHED_L1CACHE_SIZE) - sizeof(int32_t volatile)]; 319 | #endif 320 | }; 321 | 322 | struct Bikeshed_Shed_private 323 | { 324 | struct AtomicIndex m_ReadyGeneration; 325 | struct AtomicIndex m_TaskIndexHead; 326 | struct AtomicIndex m_DependencyIndexHead; 327 | struct AtomicIndex m_DependencyIndexGeneration; 328 | struct AtomicIndex m_TaskIndexGeneration; 329 | struct AtomicIndex m_TaskGeneration; 330 | struct AtomicIndex* m_ReadyHeads; 331 | int32_t* m_ReadyIndexes; 332 | int32_t* m_TaskIndexes; 333 | int32_t* m_DependencyIndexes; 334 | struct Bikeshed_Task_private* m_Tasks; 335 | struct Bikeshed_Dependency_private* m_Dependencies; 336 | struct Bikeshed_ReadyCallback* m_ReadyCallback; 337 | }; 338 | 339 | #define BIKESHED_ALIGN_SIZE_PRIVATE(x, align) (((x) + ((align)-1)) & ~((align)-1)) 340 | 341 | #if defined(BIKESHED_L1CACHE_SIZE) 342 | # define BIKESHED_SHED_ALIGNEMENT_PRIVATE (BIKESHED_L1CACHE_SIZE) 343 | #else 344 | # define BIKESHED_SHED_ALIGNEMENT_PRIVATE 4u 345 | #endif 346 | 347 | #undef BIKESHED_SIZE 348 | #define BIKESHED_SIZE(max_task_count, max_dependency_count, channel_count) \ 349 | ((uint32_t)BIKESHED_ALIGN_SIZE_PRIVATE((uint32_t)sizeof(struct Bikeshed_Shed_private), BIKESHED_SHED_ALIGNEMENT_PRIVATE) + \ 350 | (uint32_t)BIKESHED_ALIGN_SIZE_PRIVATE((uint32_t)(sizeof(struct AtomicIndex) * ((channel_count) - 1) + sizeof(int32_t volatile)), 4u) + \ 351 | (uint32_t)BIKESHED_ALIGN_SIZE_PRIVATE((uint32_t)(sizeof(int32_t volatile) * (max_task_count)), 4u) + \ 352 | (uint32_t)BIKESHED_ALIGN_SIZE_PRIVATE((uint32_t)(sizeof(int32_t volatile) * (max_task_count)), 4u) + \ 353 | (uint32_t)BIKESHED_ALIGN_SIZE_PRIVATE((uint32_t)(sizeof(int32_t volatile) * (max_dependency_count)), 4u) + \ 354 | (uint32_t)BIKESHED_ALIGN_SIZE_PRIVATE((uint32_t)(sizeof(struct Bikeshed_Task_private) * (max_task_count)), 8u) + \ 355 | (uint32_t)BIKESHED_ALIGN_SIZE_PRIVATE((uint32_t)(sizeof(struct Bikeshed_Dependency_private) * (max_dependency_count)), 4u)) 356 | 357 | #if defined(BIKESHED_IMPLEMENTATION) 358 | 359 | #if defined(__clang__) || defined(__GNUC__) 360 | # if !defined(BIKESHED_CPU_YIELD) 361 | # include 362 | # endif 363 | #elif defined (_MSC_VER) 364 | # if !defined(BIKESHED_ATOMICADD) || !defined(BIKESHED_ATOMICCAS) || !defined(BIKESHED_CPU_YIELD) 365 | # if !defined(_WINDOWS_) 366 | # define WIN32_LEAN_AND_MEAN 367 | # include 368 | # undef WIN32_LEAN_AND_MEAN 369 | # endif 370 | # endif 371 | #endif 372 | 373 | #if !defined(BIKESHED_ATOMICADD) 374 | # if defined(__clang__) || defined(__GNUC__) 375 | # define BIKESHED_ATOMICADD_PRIVATE(value, amount) (__sync_add_and_fetch (value, amount)) 376 | # elif defined(_MSC_VER) 377 | # define BIKESHED_ATOMICADD_PRIVATE(value, amount) (_InterlockedExchangeAdd((volatile LONG *)value, amount) + amount) 378 | # else 379 | inline int32_t Bikeshed_NonAtomicAdd(volatile int32_t* store, int32_t value) { *store += value; return *store; } 380 | # define BIKESHED_ATOMICADD_PRIVATE(value, amount) (Bikeshed_NonAtomicAdd(value, amount)) 381 | # endif 382 | #else 383 | # define BIKESHED_ATOMICADD_PRIVATE BIKESHED_ATOMICADD 384 | #endif 385 | 386 | #if !defined(BIKESHED_ATOMICCAS) 387 | # if defined(__clang__) || defined(__GNUC__) 388 | # define BIKESHED_ATOMICCAS_PRIVATE(store, compare, value) __sync_val_compare_and_swap(store, compare, value) 389 | # elif defined(_MSC_VER) 390 | # define BIKESHED_ATOMICCAS_PRIVATE(store, compare, value) _InterlockedCompareExchange((volatile LONG *)store, value, compare) 391 | # else 392 | inline int32_t Bikeshed_NonAtomicCAS(volatile int32_t* store, int32_t compare, int32_t value) { int32_t old = *store; if (old == compare) { *store = value; } return old; } 393 | # define BIKESHED_ATOMICCAS_PRIVATE(store, compare, value) Bikeshed_NonAtomicCAS(store, value, compare) 394 | # endif 395 | #else 396 | # define BIKESHED_ATOMICCAS_PRIVATE BIKESHED_ATOMICCAS 397 | #endif 398 | 399 | #if !defined(BIKESHED_CPU_YIELD) 400 | # if defined(__clang__) || defined(__GNUC__) 401 | # define BIKESHED_CPU_YIELD_PRIVATE _mm_pause(); 402 | # elif defined(_MSC_VER) 403 | # define BIKESHED_CPU_YIELD_PRIVATE YieldProcessor(); 404 | # else 405 | # define BIKESHED_CPU_YIELD_PRIVATE void(); 406 | # endif 407 | #else 408 | # define BIKESHED_CPU_YIELD_PRIVATE BIKESHED_CPU_YIELD 409 | #endif 410 | 411 | #if defined(BIKESHED_ASSERTS) 412 | # define BIKESHED_FATAL_ASSERT_PRIVATE(x, bail) \ 413 | if (!(x)) \ 414 | { \ 415 | if (Bikeshed_Assert_private) { Bikeshed_Assert_private(#x, __FILE__, __LINE__); } \ 416 | bail; \ 417 | } 418 | static Bikeshed_Assert Bikeshed_Assert_private = 0; 419 | void Bikeshed_SetAssert(Bikeshed_Assert assert_func) { Bikeshed_Assert_private = assert_func; } 420 | #else 421 | # define BIKESHED_FATAL_ASSERT_PRIVATE(x, y) 422 | void Bikeshed_SetAssert(Bikeshed_Assert assert_func) { (void)assert_func; } 423 | #endif 424 | 425 | #define BIKESHED_GENERATION_SHIFT_PRIVATE 23u 426 | #define BIKESHED_INDEX_MASK_PRIVATE 0x007fffffu 427 | #define BIKESHED_GENERATION_MASK_PRIVATE 0xff800000u 428 | 429 | #define BIKESHED_TASK_ID_PRIVATE(index, generation) (((Bikeshed_TaskID)(generation) << BIKESHED_GENERATION_SHIFT_PRIVATE) + index) 430 | #define BIKESHED_TASK_GENERATION_PRIVATE(task_id) ((int32_t)(task_id >> BIKESHED_GENERATION_SHIFT_PRIVATE)) 431 | #define BIKESHED_TASK_INDEX_PRIVATE(task_id) ((Bikeshed_TaskIndex_private)(task_id & BIKESHED_INDEX_MASK_PRIVATE)) 432 | 433 | static void Bikeshed_PoolInitialize_private(int32_t volatile* generation, int32_t volatile* head, int32_t volatile* items, uint32_t fill_count) 434 | { 435 | *generation = 0; 436 | if (fill_count == 0) 437 | { 438 | *head = 0; 439 | return; 440 | } 441 | *head = 1; 442 | for (uint32_t i = 0; i < fill_count - 1; ++i) 443 | { 444 | items[i] = (int32_t)(i + 2); 445 | } 446 | items[fill_count - 1] = 0; 447 | } 448 | 449 | static void Bikeshed_PushRange_private(int32_t volatile* head, uint32_t gen, uint32_t head_index, int32_t* tail_index) 450 | { 451 | uint32_t new_head = gen | head_index; 452 | uint32_t current_head = (uint32_t)*head; 453 | *tail_index = (int32_t)(BIKESHED_TASK_INDEX_PRIVATE(current_head)); 454 | 455 | while (BIKESHED_ATOMICCAS_PRIVATE(head, (int32_t)current_head, (int32_t)new_head) != (int32_t)current_head) 456 | { 457 | BIKESHED_CPU_YIELD_PRIVATE 458 | current_head = (uint32_t)*head; 459 | *tail_index = (int32_t)(BIKESHED_TASK_INDEX_PRIVATE(current_head)); 460 | } 461 | } 462 | 463 | static void Bikeshed_FreeDependencies_private(Bikeshed shed, Bikeshed_DependencyIndex_private* head_resolved_dependency_index, Bikeshed_DependencyIndex_private *tail_resolved_dependency_index, Bikeshed_DependencyIndex_private dependency_index) 464 | { 465 | if (*head_resolved_dependency_index) 466 | { 467 | shed->m_DependencyIndexes[*tail_resolved_dependency_index - 1] = (int32_t)dependency_index; 468 | *tail_resolved_dependency_index = dependency_index; 469 | } 470 | else 471 | { 472 | *head_resolved_dependency_index = dependency_index; 473 | *tail_resolved_dependency_index = dependency_index; 474 | } 475 | 476 | do 477 | { 478 | struct Bikeshed_Dependency_private* dependency = &shed->m_Dependencies[dependency_index - 1]; 479 | Bikeshed_TaskIndex_private parent_task_index = dependency->m_ParentTaskIndex; 480 | struct Bikeshed_Task_private* parent_task = &shed->m_Tasks[parent_task_index - 1]; 481 | BIKESHED_ATOMICADD_PRIVATE(&parent_task->m_ChildDependencyCount, -1); 482 | dependency_index = dependency->m_NextDependencyIndex; 483 | if (dependency_index == 0) 484 | { 485 | break; 486 | } 487 | shed->m_DependencyIndexes[*tail_resolved_dependency_index - 1] = (int32_t)dependency_index; 488 | *tail_resolved_dependency_index = dependency_index; 489 | } while (1); 490 | } 491 | 492 | static void Bikeshed_ResolveTask_private(Bikeshed shed, Bikeshed_DependencyIndex_private dependency_index) 493 | { 494 | Bikeshed_TaskIndex_private head_resolved_task_index = 0; 495 | Bikeshed_TaskIndex_private tail_resolved_task_index = 0; 496 | uint32_t resolved_task_count = 0; 497 | uint8_t channel = 0; 498 | 499 | Bikeshed_DependencyIndex_private head_resolved_dependency_index = dependency_index; 500 | Bikeshed_DependencyIndex_private tail_resolved_dependency_index = dependency_index; 501 | 502 | do 503 | { 504 | struct Bikeshed_Dependency_private* dependency = &shed->m_Dependencies[dependency_index - 1]; 505 | Bikeshed_TaskIndex_private parent_task_index = dependency->m_ParentTaskIndex; 506 | struct Bikeshed_Task_private* parent_task = &shed->m_Tasks[parent_task_index - 1]; 507 | int32_t child_dependency_count = BIKESHED_ATOMICADD_PRIVATE(&parent_task->m_ChildDependencyCount, -1); 508 | if (child_dependency_count == 0) 509 | { 510 | BIKESHED_FATAL_ASSERT_PRIVATE(0x20000000 == BIKESHED_ATOMICADD_PRIVATE(&shed->m_Tasks[parent_task_index - 1].m_ChildDependencyCount, 0x20000000), return) 511 | if (resolved_task_count++ == 0) 512 | { 513 | head_resolved_task_index = parent_task_index; 514 | channel = (uint8_t)parent_task->m_Channel; 515 | } 516 | else if (channel == parent_task->m_Channel) 517 | { 518 | shed->m_ReadyIndexes[tail_resolved_task_index - 1] = (int32_t)parent_task_index; 519 | } 520 | else 521 | { 522 | uint32_t gen = (((uint32_t)BIKESHED_ATOMICADD_PRIVATE(&shed->m_ReadyGeneration.m_Index, 1)) << BIKESHED_GENERATION_SHIFT_PRIVATE) & BIKESHED_GENERATION_MASK_PRIVATE; 523 | Bikeshed_PushRange_private(&shed->m_ReadyHeads[channel].m_Index, gen, head_resolved_task_index, &shed->m_ReadyIndexes[tail_resolved_task_index-1]); 524 | if (shed->m_ReadyCallback) 525 | { 526 | shed->m_ReadyCallback->SignalReady(shed->m_ReadyCallback, channel, resolved_task_count - 1); 527 | } 528 | head_resolved_task_index = parent_task_index; 529 | channel = (uint8_t)parent_task->m_Channel; 530 | resolved_task_count = 1; 531 | } 532 | tail_resolved_task_index = parent_task_index; 533 | } 534 | dependency_index = dependency->m_NextDependencyIndex; 535 | shed->m_DependencyIndexes[tail_resolved_dependency_index - 1] = (int32_t)dependency_index; 536 | if (dependency_index == 0) 537 | { 538 | break; 539 | } 540 | tail_resolved_dependency_index = dependency_index; 541 | } while (1); 542 | 543 | { 544 | uint32_t gen = (((uint32_t)BIKESHED_ATOMICADD_PRIVATE(&shed->m_DependencyIndexGeneration.m_Index, 1)) << BIKESHED_GENERATION_SHIFT_PRIVATE) & BIKESHED_GENERATION_MASK_PRIVATE; 545 | Bikeshed_PushRange_private(&shed->m_DependencyIndexHead.m_Index, gen, head_resolved_dependency_index, &shed->m_DependencyIndexes[tail_resolved_dependency_index-1]); 546 | } 547 | 548 | if (resolved_task_count > 0) 549 | { 550 | uint32_t gen = (((uint32_t)BIKESHED_ATOMICADD_PRIVATE(&shed->m_ReadyGeneration.m_Index, 1)) << BIKESHED_GENERATION_SHIFT_PRIVATE) & BIKESHED_GENERATION_MASK_PRIVATE; 551 | Bikeshed_PushRange_private(&shed->m_ReadyHeads[channel].m_Index, gen, head_resolved_task_index, &shed->m_ReadyIndexes[tail_resolved_task_index-1]); 552 | if (shed->m_ReadyCallback) 553 | { 554 | shed->m_ReadyCallback->SignalReady(shed->m_ReadyCallback, channel, resolved_task_count); 555 | } 556 | } 557 | } 558 | 559 | Bikeshed Bikeshed_Create(void* mem, uint32_t max_task_count, uint32_t max_dependency_count, uint8_t channel_count, struct Bikeshed_ReadyCallback* sync_primitive) 560 | { 561 | BIKESHED_FATAL_ASSERT_PRIVATE(mem != 0, return 0) 562 | BIKESHED_FATAL_ASSERT_PRIVATE(max_task_count > 0, return 0) 563 | BIKESHED_FATAL_ASSERT_PRIVATE(max_task_count == BIKESHED_TASK_INDEX_PRIVATE(max_task_count), return 0) 564 | BIKESHED_FATAL_ASSERT_PRIVATE(max_dependency_count == BIKESHED_TASK_INDEX_PRIVATE(max_dependency_count), return 0) 565 | BIKESHED_FATAL_ASSERT_PRIVATE(channel_count >= 1, return 0) 566 | 567 | Bikeshed shed = (Bikeshed)mem; 568 | shed->m_TaskGeneration.m_Index = 1; 569 | shed->m_ReadyCallback = sync_primitive; 570 | 571 | uint8_t* p = (uint8_t*)mem; 572 | p += BIKESHED_ALIGN_SIZE_PRIVATE((uint32_t)sizeof(struct Bikeshed_Shed_private), BIKESHED_SHED_ALIGNEMENT_PRIVATE); 573 | shed->m_ReadyHeads = (struct AtomicIndex*)(void*)p; 574 | p += BIKESHED_ALIGN_SIZE_PRIVATE((uint32_t)(sizeof(struct AtomicIndex) * (channel_count - 1) + sizeof(int32_t volatile)), 4u); 575 | shed->m_ReadyIndexes = (int32_t*)(void*)p; 576 | p += BIKESHED_ALIGN_SIZE_PRIVATE((uint32_t)(sizeof(int32_t) * max_task_count), 4u); 577 | shed->m_TaskIndexes = (int32_t*)(void*)p; 578 | p += BIKESHED_ALIGN_SIZE_PRIVATE((uint32_t)(sizeof(int32_t) * max_task_count), 4u); 579 | shed->m_DependencyIndexes = (int32_t*)(void*)p; 580 | p += BIKESHED_ALIGN_SIZE_PRIVATE((uint32_t)(sizeof(int32_t) * max_dependency_count), 4u); 581 | shed->m_Tasks = (struct Bikeshed_Task_private*)((void*)p); 582 | p += BIKESHED_ALIGN_SIZE_PRIVATE((uint32_t)(sizeof(struct Bikeshed_Task_private) * max_task_count), 8u); 583 | shed->m_Dependencies = (struct Bikeshed_Dependency_private*)((void*)p); 584 | 585 | Bikeshed_PoolInitialize_private(&shed->m_TaskIndexGeneration.m_Index, &shed->m_TaskIndexHead.m_Index, shed->m_TaskIndexes, max_task_count); 586 | Bikeshed_PoolInitialize_private(&shed->m_DependencyIndexGeneration.m_Index, &shed->m_DependencyIndexHead.m_Index, shed->m_DependencyIndexes, max_dependency_count); 587 | for (uint8_t channel = 0; channel < channel_count; ++channel) 588 | { 589 | shed->m_ReadyHeads[channel].m_Index = 0; 590 | } 591 | 592 | return shed; 593 | } 594 | 595 | Bikeshed Bikeshed_CloneState(void* mem, Bikeshed original, uint32_t shed_size) 596 | { 597 | BIKESHED_FATAL_ASSERT_PRIVATE(mem != 0, return 0) 598 | BIKESHED_FATAL_ASSERT_PRIVATE(original != 0, return 0) 599 | BIKESHED_FATAL_ASSERT_PRIVATE(shed_size >= sizeof(struct Bikeshed_Shed_private), return 0) 600 | 601 | memcpy(mem, original, shed_size); 602 | 603 | Bikeshed shed = (Bikeshed)mem; 604 | uint8_t* p = (uint8_t*)mem; 605 | shed->m_ReadyHeads = (struct AtomicIndex*)(void*)(&p[(uintptr_t)original->m_ReadyHeads - (uintptr_t)original]); 606 | shed->m_ReadyIndexes = (int32_t*)(void*)(&p[(uintptr_t)original->m_ReadyIndexes - (uintptr_t)original]); 607 | shed->m_TaskIndexes = (int32_t*)(void*)(&p[(uintptr_t)original->m_TaskIndexes - (uintptr_t)original]); 608 | shed->m_DependencyIndexes = (int32_t*)(void*)(&p[(uintptr_t)original->m_DependencyIndexes - (uintptr_t)original]); 609 | shed->m_Tasks = (struct Bikeshed_Task_private*)(void*)(&p[(uintptr_t)original->m_Tasks - (uintptr_t)original]); 610 | shed->m_Dependencies = (struct Bikeshed_Dependency_private*)(void*)(&p[(uintptr_t)original->m_Dependencies - (uintptr_t)original]); 611 | 612 | return shed; 613 | } 614 | 615 | int Bikeshed_CreateTasks(Bikeshed shed, uint32_t task_count, BikeShed_TaskFunc* task_functions, void** contexts, Bikeshed_TaskID* out_task_ids) 616 | { 617 | BIKESHED_FATAL_ASSERT_PRIVATE(shed != 0, return 0) 618 | BIKESHED_FATAL_ASSERT_PRIVATE(task_count > 0, return 0) 619 | BIKESHED_FATAL_ASSERT_PRIVATE(task_functions != 0, return 0) 620 | BIKESHED_FATAL_ASSERT_PRIVATE(contexts != 0, return 0) 621 | BIKESHED_FATAL_ASSERT_PRIVATE(out_task_ids != 0, return 0) 622 | 623 | do 624 | { 625 | uint32_t head = (uint32_t)shed->m_TaskIndexHead.m_Index; 626 | Bikeshed_TaskIndex_private task_index = BIKESHED_TASK_INDEX_PRIVATE(head); 627 | if (task_index == 0) 628 | { 629 | return 0; 630 | } 631 | out_task_ids[0] = task_index; 632 | for (uint32_t t = 1; t < task_count; ++t) 633 | { 634 | task_index = (uint32_t)shed->m_TaskIndexes[task_index - 1]; 635 | if (task_index == 0) 636 | { 637 | return 0; 638 | } 639 | out_task_ids[t] = task_index; 640 | } 641 | uint32_t new_head = (head & BIKESHED_GENERATION_MASK_PRIVATE) | (uint32_t)shed->m_TaskIndexes[task_index - 1]; 642 | if (BIKESHED_ATOMICCAS_PRIVATE(&shed->m_TaskIndexHead.m_Index, (int32_t)head, (int32_t)new_head) == (int32_t)head) 643 | { 644 | break; 645 | } 646 | } while (1); 647 | 648 | int32_t generation = BIKESHED_ATOMICADD_PRIVATE(&shed->m_TaskGeneration.m_Index, 1); 649 | for (uint32_t i = 0; i < task_count; ++i) 650 | { 651 | Bikeshed_TaskIndex_private task_index = out_task_ids[i]; 652 | Bikeshed_TaskID task_id = BIKESHED_TASK_ID_PRIVATE(task_index, generation); 653 | out_task_ids[i] = task_id; 654 | struct Bikeshed_Task_private* task = &shed->m_Tasks[task_index - 1]; 655 | task->m_TaskID = task_id; 656 | task->m_ChildDependencyCount = 0; 657 | task->m_FirstDependencyIndex = 0; 658 | task->m_Channel = 0; 659 | task->m_TaskFunc = task_functions[i]; 660 | task->m_TaskContext = contexts[i]; 661 | } 662 | return 1; 663 | } 664 | 665 | void Bikeshed_FreeTasks(Bikeshed shed, uint32_t task_count, const Bikeshed_TaskID* task_ids) 666 | { 667 | BIKESHED_FATAL_ASSERT_PRIVATE(shed != 0, return) 668 | BIKESHED_FATAL_ASSERT_PRIVATE(task_count > 0, return) 669 | BIKESHED_FATAL_ASSERT_PRIVATE(task_ids != 0, return) 670 | 671 | Bikeshed_TaskIndex_private head_free_task_index = BIKESHED_TASK_INDEX_PRIVATE(task_ids[0]); 672 | Bikeshed_TaskIndex_private tail_free_task_index = head_free_task_index; 673 | 674 | Bikeshed_DependencyIndex_private head_resolved_dependency_index = 0; 675 | Bikeshed_DependencyIndex_private tail_resolved_dependency_index = 0; 676 | 677 | struct Bikeshed_Task_private* task = &shed->m_Tasks[tail_free_task_index - 1]; 678 | BIKESHED_FATAL_ASSERT_PRIVATE(task->m_ChildDependencyCount == 0, return) 679 | if (task->m_FirstDependencyIndex) 680 | { 681 | Bikeshed_FreeDependencies_private(shed, &head_resolved_dependency_index, &tail_resolved_dependency_index, task->m_FirstDependencyIndex); 682 | task->m_FirstDependencyIndex = 0; 683 | } 684 | 685 | for (uint32_t i = 1; i < task_count; ++i) 686 | { 687 | Bikeshed_TaskIndex_private next_free_task_index = BIKESHED_TASK_INDEX_PRIVATE(task_ids[i]); 688 | 689 | task = &shed->m_Tasks[next_free_task_index - 1]; 690 | BIKESHED_FATAL_ASSERT_PRIVATE(task->m_ChildDependencyCount == 0, return) 691 | if (task->m_FirstDependencyIndex) 692 | { 693 | Bikeshed_FreeDependencies_private(shed, &head_resolved_dependency_index, &tail_resolved_dependency_index, task->m_FirstDependencyIndex); 694 | task->m_FirstDependencyIndex = 0; 695 | } 696 | 697 | shed->m_TaskIndexes[tail_free_task_index - 1] = (int32_t)next_free_task_index; 698 | tail_free_task_index = next_free_task_index; 699 | } 700 | 701 | if (head_resolved_dependency_index) 702 | { 703 | uint32_t gen = (((uint32_t)BIKESHED_ATOMICADD_PRIVATE(&shed->m_DependencyIndexGeneration.m_Index, 1)) << BIKESHED_GENERATION_SHIFT_PRIVATE) & BIKESHED_GENERATION_MASK_PRIVATE; 704 | Bikeshed_PushRange_private(&shed->m_DependencyIndexHead.m_Index, gen, head_resolved_dependency_index, &shed->m_DependencyIndexes[tail_resolved_dependency_index-1]); 705 | } 706 | 707 | uint32_t gen = (((uint32_t)BIKESHED_ATOMICADD_PRIVATE(&shed->m_TaskIndexGeneration.m_Index, 1)) << BIKESHED_GENERATION_SHIFT_PRIVATE) & BIKESHED_GENERATION_MASK_PRIVATE; 708 | Bikeshed_PushRange_private(&shed->m_TaskIndexHead.m_Index, gen, head_free_task_index, &shed->m_TaskIndexes[tail_free_task_index-1]); 709 | } 710 | 711 | void Bikeshed_SetTasksChannel(Bikeshed shed, uint32_t task_count, Bikeshed_TaskID* task_ids, uint8_t channel) 712 | { 713 | BIKESHED_FATAL_ASSERT_PRIVATE(shed != 0, return) 714 | BIKESHED_FATAL_ASSERT_PRIVATE(task_count > 0, return) 715 | BIKESHED_FATAL_ASSERT_PRIVATE(task_ids != 0, return) 716 | BIKESHED_FATAL_ASSERT_PRIVATE((uintptr_t)&shed->m_ReadyHeads[channel] < (uintptr_t)shed->m_ReadyIndexes, return) 717 | 718 | for (uint32_t t = 0; t < task_count; ++t) 719 | { 720 | Bikeshed_TaskIndex_private task_index = BIKESHED_TASK_INDEX_PRIVATE(task_ids[t]); 721 | struct Bikeshed_Task_private* task = &shed->m_Tasks[task_index - 1]; 722 | BIKESHED_FATAL_ASSERT_PRIVATE(task_ids[t] == task->m_TaskID, return) 723 | BIKESHED_FATAL_ASSERT_PRIVATE(task->m_ChildDependencyCount < 0x20000000, return) 724 | task->m_Channel = channel; 725 | } 726 | } 727 | 728 | int Bikeshed_AddDependencies(Bikeshed shed, uint32_t task_count, const Bikeshed_TaskID* task_ids, uint32_t dependency_task_count, const Bikeshed_TaskID* dependency_task_ids) 729 | { 730 | BIKESHED_FATAL_ASSERT_PRIVATE(shed != 0, return 0) 731 | BIKESHED_FATAL_ASSERT_PRIVATE(task_count > 0, return 0) 732 | BIKESHED_FATAL_ASSERT_PRIVATE(task_ids != 0, return 0) 733 | BIKESHED_FATAL_ASSERT_PRIVATE(dependency_task_count > 0, return 0) 734 | BIKESHED_FATAL_ASSERT_PRIVATE(dependency_task_ids != 0, return 0) 735 | 736 | uint32_t total_dependency_count = task_count * dependency_task_count; 737 | 738 | uint32_t dependency_index_head; 739 | do 740 | { 741 | dependency_index_head = (uint32_t)shed->m_DependencyIndexHead.m_Index; 742 | Bikeshed_TaskIndex_private dependency_index = BIKESHED_TASK_INDEX_PRIVATE(dependency_index_head); 743 | if (dependency_index == 0) 744 | { 745 | return 0; 746 | } 747 | for (uint32_t d = 1; d < total_dependency_count; ++d) 748 | { 749 | dependency_index = (uint32_t)shed->m_DependencyIndexes[dependency_index - 1]; 750 | if (dependency_index == 0) 751 | { 752 | return 0; 753 | } 754 | } 755 | uint32_t new_head = (dependency_index_head & BIKESHED_GENERATION_MASK_PRIVATE) | (uint32_t)shed->m_DependencyIndexes[dependency_index - 1]; 756 | if (BIKESHED_ATOMICCAS_PRIVATE(&shed->m_DependencyIndexHead.m_Index, (int32_t)dependency_index_head, (int32_t)new_head) == (int32_t)dependency_index_head) 757 | { 758 | break; 759 | } 760 | } while (1); 761 | 762 | Bikeshed_DependencyIndex_private dependency_index = BIKESHED_TASK_INDEX_PRIVATE(dependency_index_head); 763 | for (uint32_t t = 0; t < task_count; ++t) 764 | { 765 | Bikeshed_TaskID task_id = task_ids[t]; 766 | Bikeshed_TaskIndex_private task_index = BIKESHED_TASK_INDEX_PRIVATE(task_id); 767 | struct Bikeshed_Task_private* task = &shed->m_Tasks[task_index - 1]; 768 | BIKESHED_FATAL_ASSERT_PRIVATE(task_id == task->m_TaskID, return 0) 769 | BIKESHED_FATAL_ASSERT_PRIVATE(task->m_ChildDependencyCount < 0x20000000, return 0) 770 | 771 | for (uint32_t i = 0; i < dependency_task_count; ++i) 772 | { 773 | Bikeshed_TaskID dependency_task_id = dependency_task_ids[i]; 774 | Bikeshed_TaskIndex_private dependency_task_index = BIKESHED_TASK_INDEX_PRIVATE(dependency_task_id); 775 | struct Bikeshed_Task_private* dependency_task = &shed->m_Tasks[dependency_task_index - 1]; 776 | BIKESHED_FATAL_ASSERT_PRIVATE(dependency_task_id == dependency_task->m_TaskID, return 0) 777 | BIKESHED_FATAL_ASSERT_PRIVATE(dependency_task_id != task_id, return 0) 778 | 779 | struct Bikeshed_Dependency_private* dependency = &shed->m_Dependencies[dependency_index - 1]; 780 | dependency->m_ParentTaskIndex = task_index; 781 | dependency->m_NextDependencyIndex = dependency_task->m_FirstDependencyIndex; 782 | dependency_task->m_FirstDependencyIndex = dependency_index; 783 | dependency_index = (uint32_t)shed->m_DependencyIndexes[dependency_index - 1]; 784 | } 785 | BIKESHED_ATOMICADD_PRIVATE(&task->m_ChildDependencyCount, (int32_t)dependency_task_count); 786 | } 787 | 788 | return 1; 789 | } 790 | 791 | void Bikeshed_ReadyTasks(Bikeshed shed, uint32_t task_count, const Bikeshed_TaskID* task_ids) 792 | { 793 | BIKESHED_FATAL_ASSERT_PRIVATE(shed != 0, return) 794 | BIKESHED_FATAL_ASSERT_PRIVATE(task_count > 0, return) 795 | BIKESHED_FATAL_ASSERT_PRIVATE(task_ids != 0, return) 796 | Bikeshed_TaskID head_task_id = task_ids[0]; 797 | Bikeshed_TaskIndex_private head_task_index = BIKESHED_TASK_INDEX_PRIVATE(head_task_id); 798 | Bikeshed_TaskIndex_private tail_task_index = head_task_index; 799 | struct Bikeshed_Task_private* head_task = &shed->m_Tasks[head_task_index - 1]; 800 | BIKESHED_FATAL_ASSERT_PRIVATE(head_task_id == head_task->m_TaskID, return ) 801 | BIKESHED_FATAL_ASSERT_PRIVATE(0x20000000 == BIKESHED_ATOMICADD_PRIVATE(&head_task->m_ChildDependencyCount, 0x20000000), return) 802 | 803 | uint8_t channel = (uint8_t)head_task->m_Channel; 804 | uint32_t gen = (((uint32_t)BIKESHED_ATOMICADD_PRIVATE(&shed->m_ReadyGeneration.m_Index, 1)) << BIKESHED_GENERATION_SHIFT_PRIVATE) & BIKESHED_GENERATION_MASK_PRIVATE; 805 | uint32_t ready_task_count = 1; 806 | uint32_t i = 1; 807 | while (i < task_count) 808 | { 809 | Bikeshed_TaskID next_task_id = task_ids[i]; 810 | Bikeshed_TaskIndex_private next_task_index = BIKESHED_TASK_INDEX_PRIVATE(next_task_id); 811 | struct Bikeshed_Task_private* next_task = &shed->m_Tasks[next_task_index - 1]; 812 | BIKESHED_FATAL_ASSERT_PRIVATE(next_task_id == next_task->m_TaskID, return ) 813 | BIKESHED_FATAL_ASSERT_PRIVATE(0x20000000 == BIKESHED_ATOMICADD_PRIVATE(&shed->m_Tasks[next_task_index - 1].m_ChildDependencyCount, 0x20000000), return) 814 | 815 | if (next_task->m_Channel == channel) 816 | { 817 | shed->m_ReadyIndexes[tail_task_index - 1] = (int32_t)next_task_index; 818 | tail_task_index = next_task_index; 819 | ++ready_task_count; 820 | ++i; 821 | continue; 822 | } 823 | Bikeshed_PushRange_private(&shed->m_ReadyHeads[channel].m_Index, gen, head_task_index, &shed->m_ReadyIndexes[tail_task_index-1]); 824 | if (shed->m_ReadyCallback) 825 | { 826 | shed->m_ReadyCallback->SignalReady(shed->m_ReadyCallback, channel, ready_task_count); 827 | } 828 | 829 | ready_task_count = 1; 830 | channel = (uint8_t)next_task->m_Channel; 831 | head_task_index = next_task_index; 832 | tail_task_index = next_task_index; 833 | ++i; 834 | } 835 | 836 | if (ready_task_count > 0) 837 | { 838 | Bikeshed_PushRange_private(&shed->m_ReadyHeads[channel].m_Index, gen, head_task_index, &shed->m_ReadyIndexes[tail_task_index-1]); 839 | 840 | if (shed->m_ReadyCallback) 841 | { 842 | shed->m_ReadyCallback->SignalReady(shed->m_ReadyCallback, channel, ready_task_count); 843 | } 844 | } 845 | } 846 | 847 | int Bikeshed_ExecuteOne(Bikeshed shed, uint8_t channel) 848 | { 849 | BIKESHED_FATAL_ASSERT_PRIVATE(shed != 0, return 0) 850 | BIKESHED_FATAL_ASSERT_PRIVATE((uintptr_t)&shed->m_ReadyHeads[channel] < (uintptr_t)shed->m_ReadyIndexes, return 0) 851 | 852 | int32_t volatile* head = &shed->m_ReadyHeads[channel].m_Index; 853 | int32_t* items = shed->m_ReadyIndexes; 854 | uint32_t task_index = 0; 855 | do 856 | { 857 | uint32_t current_head = (uint32_t)*head; 858 | task_index = BIKESHED_TASK_INDEX_PRIVATE(current_head); 859 | if (task_index == 0) 860 | { 861 | return 0; 862 | } 863 | 864 | uint32_t next = (uint32_t)items[task_index - 1]; 865 | uint32_t new_head = (current_head & BIKESHED_GENERATION_MASK_PRIVATE) | next; 866 | 867 | if (BIKESHED_ATOMICCAS_PRIVATE(head, (int32_t)current_head, (int32_t)new_head) == (int32_t)current_head) 868 | { 869 | break; 870 | } 871 | BIKESHED_CPU_YIELD_PRIVATE 872 | } while (1); 873 | 874 | struct Bikeshed_Task_private* task = &shed->m_Tasks[task_index - 1]; 875 | Bikeshed_TaskID task_id = task->m_TaskID; 876 | 877 | enum Bikeshed_TaskResult task_result = task->m_TaskFunc(shed, task_id, channel, task->m_TaskContext); 878 | 879 | BIKESHED_FATAL_ASSERT_PRIVATE(0 == BIKESHED_ATOMICADD_PRIVATE(&task->m_ChildDependencyCount, -0x20000000), return 0) 880 | 881 | if (task_result == BIKESHED_TASK_RESULT_COMPLETE) 882 | { 883 | if (task->m_FirstDependencyIndex) 884 | { 885 | Bikeshed_ResolveTask_private(shed, task->m_FirstDependencyIndex); 886 | task->m_FirstDependencyIndex = 0; 887 | } 888 | uint32_t gen = (((uint32_t)BIKESHED_ATOMICADD_PRIVATE(&shed->m_TaskIndexGeneration.m_Index, 1)) << BIKESHED_GENERATION_SHIFT_PRIVATE) & BIKESHED_GENERATION_MASK_PRIVATE; 889 | Bikeshed_PushRange_private(&shed->m_TaskIndexHead.m_Index, gen, task_index, &shed->m_TaskIndexes[task_index-1]); 890 | } 891 | else 892 | { 893 | BIKESHED_FATAL_ASSERT_PRIVATE(BIKESHED_TASK_RESULT_BLOCKED == task_result, return 0) 894 | } 895 | 896 | return 1; 897 | } 898 | 899 | 900 | #undef BIKESHED_ATOMICADD_PRIVATE 901 | #undef BIKESHED_ATOMICCAS_PRIVATE 902 | #undef BIKESHED_GENERATION_SHIFT_PRIVATE 903 | #undef BIKESHED_INDEX_MASK_PRIVATE 904 | #undef BIKESHED_GENERATION_MASK_PRIVATE 905 | #undef BIKESHED_TASK_ID_PRIVATE 906 | #undef BIKESHED_TASK_GENERATION_PRIVATE 907 | #undef BIKESHED_TASK_INDEX_PRIVATE 908 | 909 | #endif // !defined(BIKESHED_IMPLEMENTATION) 910 | 911 | #ifdef __cplusplus 912 | } 913 | #endif 914 | 915 | #endif // BIKESHED_INCLUDEGUARD_PRIVATE_H 916 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | #define JC_TEST_IMPLEMENTATION 2 | #include "../third-party/jctest/src/jc_test.h" 3 | 4 | int main(int argc, char** argv) 5 | { 6 | jc_test_init(&argc, argv); 7 | return jc_test_run_all(); 8 | } 9 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #include "../third-party/jctest/src/jc_test.h" 2 | #include "../third-party/nadir/src/nadir.h" 3 | 4 | #include "../src/bikeshed.h" 5 | 6 | #include 7 | 8 | #if 0 9 | static void PrintTask(Bikeshed shed, uint32_t index) 10 | { 11 | Bikeshed_Task_private* task = &shed->m_Tasks[index - 1]; 12 | printf("m_Tasks[%u]\n", index); 13 | printf(" m_ChildDependencyCount = %ld\n", task->m_ChildDependencyCount); 14 | printf(" m_TaskID = %u\n", task->m_TaskID); 15 | printf(" m_Channel = %u\n", task->m_Channel); 16 | uint32_t dependencyIndex = task->m_FirstDependencyIndex; 17 | printf(" m_FirstDependencyIndex = %u\n", dependencyIndex); 18 | while (dependencyIndex) 19 | { 20 | Bikeshed_Dependency_private* dependency = &shed->m_Dependencies[dependencyIndex - 1]; 21 | printf(" Parent: [%u]\n", dependency->m_ParentTaskIndex); 22 | dependencyIndex = dependency->m_NextDependencyIndex; 23 | } 24 | } 25 | 26 | static void PrintState(Bikeshed shed) 27 | { 28 | struct AtomicIndex* ready_head = shed->m_ReadyHeads; 29 | uint32_t channel = 0; 30 | while((uintptr_t)&ready_head[channel] < (uintptr_t)shed->m_ReadyIndexes) 31 | { 32 | printf("Readyhead[%u]: %ld\n", channel, ready_head[channel].m_Index & 0xffff); 33 | 34 | ++channel; 35 | } 36 | uint32_t task_index = 1; 37 | while ((uintptr_t)&shed->m_Tasks[task_index - 1] < (uintptr_t)shed->m_Dependencies) 38 | { 39 | PrintTask(shed, task_index); 40 | ++task_index; 41 | } 42 | } 43 | #endif 44 | 45 | struct AssertAbort 46 | { 47 | AssertAbort() 48 | { 49 | Bikeshed_SetAssert(AssertAbort::Assert); 50 | } 51 | ~AssertAbort() 52 | { 53 | Bikeshed_SetAssert(0); 54 | } 55 | static void Assert(const char* expression, const char* file, int line) 56 | { 57 | printf("'%s' failed at %s(%d)", expression, file, line); 58 | ASSERT_TRUE(false); 59 | } 60 | }; 61 | 62 | struct AssertExpect 63 | { 64 | static uint32_t gAssertCount; 65 | AssertExpect() 66 | { 67 | gAssertCount = 0; 68 | Bikeshed_SetAssert(AssertExpect::Assert); 69 | } 70 | ~AssertExpect() 71 | { 72 | Bikeshed_SetAssert(0); 73 | } 74 | static void Assert(const char* , const char* , int ) 75 | { 76 | ++gAssertCount; 77 | } 78 | }; 79 | 80 | uint32_t AssertExpect::gAssertCount = 0; 81 | 82 | TEST(Bikeshed, Assert) 83 | { 84 | AssertExpect assert_expect; 85 | char mem[BIKESHED_SIZE(1, 0, 1)]; 86 | Bikeshed shed = Bikeshed_Create(mem, 1, 0, 1, 0); 87 | ASSERT_NE((Bikeshed)0, shed); 88 | #if defined(BIKESHED_ASSERTS) 89 | Bikeshed_TaskID invalid_task_id = 1; 90 | Bikeshed_ReadyTasks(shed, 1, &invalid_task_id); 91 | ASSERT_EQ(1u, AssertExpect::gAssertCount); 92 | #endif 93 | } 94 | 95 | struct TaskData 96 | { 97 | TaskData() 98 | : shed(0) 99 | , task_id(0) 100 | , executed(0) 101 | , channel(255) 102 | { 103 | } 104 | static Bikeshed_TaskResult Compute(Bikeshed shed, Bikeshed_TaskID task_id, uint8_t channel, void* context) 105 | { 106 | TaskData* task_data = reinterpret_cast(context); 107 | task_data->shed = shed; 108 | ++task_data->executed; 109 | task_data->task_id = task_id; 110 | task_data->channel = channel; 111 | return BIKESHED_TASK_RESULT_COMPLETE; 112 | } 113 | Bikeshed shed; 114 | Bikeshed_TaskID task_id; 115 | uint32_t executed; 116 | uint32_t channel; 117 | }; 118 | 119 | 120 | TEST(Bikeshed, SingleTask) 121 | { 122 | AssertAbort fatal; 123 | 124 | char mem[BIKESHED_SIZE(1, 0, 1)]; 125 | Bikeshed shed = Bikeshed_Create(mem, 1, 0, 1, 0); 126 | 127 | TaskData task; 128 | 129 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 130 | 131 | BikeShed_TaskFunc task_functions[1] = {TaskData::Compute}; 132 | void* task_contexts[1] = {&task}; 133 | 134 | Bikeshed_TaskID task_id; 135 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 1, task_functions, task_contexts, &task_id)); 136 | 137 | ASSERT_FALSE(Bikeshed_CreateTasks(shed, 1, task_functions, task_contexts, &task_id)); 138 | 139 | Bikeshed_ReadyTasks(shed, 1, &task_id); 140 | 141 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 142 | 143 | ASSERT_EQ(shed, task.shed); 144 | ASSERT_EQ(task.task_id, task_id); 145 | ASSERT_EQ(1u, task.executed); 146 | 147 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 148 | } 149 | 150 | TEST(Bikeshed, FreeTasks) 151 | { 152 | AssertAbort fatal; 153 | 154 | char mem[BIKESHED_SIZE(5, 3, 1)]; 155 | Bikeshed shed = Bikeshed_Create(mem, 5, 3, 1, 0); 156 | 157 | TaskData tasks[5]; 158 | BikeShed_TaskFunc task_functions[5] = { 159 | TaskData::Compute, 160 | TaskData::Compute, 161 | TaskData::Compute, 162 | TaskData::Compute, 163 | TaskData::Compute 164 | }; 165 | void* task_contexts[5] = { 166 | &tasks[0], 167 | &tasks[1], 168 | &tasks[2], 169 | &tasks[3], 170 | &tasks[4] 171 | }; 172 | 173 | 174 | Bikeshed_TaskID task_ids[5]; 175 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 5, task_functions, task_contexts, task_ids)); 176 | ASSERT_FALSE(Bikeshed_CreateTasks(shed, 1, task_functions, task_contexts, task_ids)); 177 | Bikeshed_FreeTasks(shed, 5, task_ids); 178 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 5, task_functions, task_contexts, task_ids)); 179 | ASSERT_FALSE(Bikeshed_CreateTasks(shed, 1, task_functions, task_contexts, task_ids)); 180 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[0], 2, &task_ids[2])); 181 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[1], 1, &task_ids[4])); 182 | Bikeshed_FreeTasks(shed, 3, &task_ids[2]); 183 | Bikeshed_FreeTasks(shed, 2, &task_ids[0]); 184 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 5, task_functions, task_contexts, task_ids)); 185 | ASSERT_FALSE(Bikeshed_AddDependencies(shed, 1, &task_ids[0], 4, &task_ids[1])); 186 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[0], 3, &task_ids[1])); 187 | ASSERT_FALSE(Bikeshed_AddDependencies(shed, 1, &task_ids[1], 1, &task_ids[4])); 188 | } 189 | 190 | TEST(Bikeshed, Blocked) 191 | { 192 | AssertAbort fatal; 193 | 194 | char mem[BIKESHED_SIZE(2, 0, 1)]; 195 | Bikeshed shed = Bikeshed_Create(mem, 2, 0, 1, 0x0); 196 | 197 | struct TaskData 198 | { 199 | TaskData() 200 | : shed(0) 201 | , task_id(0) 202 | , blocked_count(0) 203 | , executed(0) 204 | { 205 | } 206 | static Bikeshed_TaskResult Compute(Bikeshed shed, Bikeshed_TaskID task_id, uint8_t , void* task_context) 207 | { 208 | TaskData* task_data = reinterpret_cast(task_context); 209 | ++task_data->executed; 210 | if (task_data->blocked_count > 0) 211 | { 212 | --task_data->blocked_count; 213 | return BIKESHED_TASK_RESULT_BLOCKED; 214 | } 215 | task_data->shed = shed; 216 | task_data->task_id = task_id; 217 | return BIKESHED_TASK_RESULT_COMPLETE; 218 | } 219 | Bikeshed shed; 220 | Bikeshed_TaskID task_id; 221 | uint8_t blocked_count; 222 | uint32_t executed; 223 | }; 224 | 225 | TaskData tasks[2]; 226 | tasks[0].blocked_count = 1; 227 | 228 | BikeShed_TaskFunc task_functions[2] = { 229 | TaskData::Compute, 230 | TaskData::Compute 231 | }; 232 | void* task_contexts[2] = { 233 | &tasks[0], 234 | &tasks[1] 235 | }; 236 | 237 | Bikeshed_TaskID task_ids[2]; 238 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 2, task_functions, task_contexts, task_ids)); 239 | 240 | Bikeshed_ReadyTasks(shed, 2, task_ids); 241 | 242 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 243 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 244 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 245 | Bikeshed_ReadyTasks(shed, 1, &task_ids[0]); 246 | 247 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 248 | ASSERT_EQ(0, tasks[0].blocked_count); 249 | ASSERT_EQ(shed, tasks[0].shed); 250 | ASSERT_EQ(task_ids[0], tasks[0].task_id); 251 | ASSERT_EQ(2u, tasks[0].executed); 252 | 253 | ASSERT_EQ(0, tasks[0].blocked_count); 254 | ASSERT_EQ(shed, tasks[1].shed); 255 | ASSERT_EQ(task_ids[1], tasks[1].task_id); 256 | ASSERT_EQ(1u, tasks[1].executed); 257 | 258 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 259 | } 260 | 261 | TEST(Bikeshed, Sync) 262 | { 263 | AssertAbort fatal; 264 | 265 | struct FakeLock 266 | { 267 | Bikeshed_ReadyCallback m_ReadyCallback; 268 | FakeLock() 269 | : ready_count(0) 270 | { 271 | m_ReadyCallback.SignalReady = signal; 272 | } 273 | static void signal(Bikeshed_ReadyCallback* primitive, uint8_t , uint32_t ready_count) 274 | { 275 | (reinterpret_cast(primitive))->ready_count += ready_count; 276 | } 277 | uint32_t ready_count; 278 | } lock; 279 | char mem[BIKESHED_SIZE(1, 0, 1)]; 280 | Bikeshed shed = Bikeshed_Create(mem, 1, 0, 1, &lock.m_ReadyCallback); 281 | 282 | TaskData task; 283 | 284 | BikeShed_TaskFunc funcs[1] = { TaskData::Compute }; 285 | void* contexts[1] = { &task }; 286 | 287 | Bikeshed_TaskID task_id; 288 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 1, funcs, contexts, &task_id)); 289 | 290 | ASSERT_FALSE(Bikeshed_CreateTasks(shed, 1, funcs, contexts, &task_id)); 291 | 292 | Bikeshed_ReadyTasks(shed, 1, &task_id); 293 | ASSERT_EQ(1u, lock.ready_count); 294 | 295 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 296 | 297 | ASSERT_EQ(shed, task.shed); 298 | ASSERT_EQ(task.task_id, task_id); 299 | ASSERT_EQ(1u, task.executed); 300 | 301 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 302 | ASSERT_EQ(1u, lock.ready_count); 303 | } 304 | 305 | TEST(Bikeshed, ReadyOrder) 306 | { 307 | AssertAbort fatal; 308 | 309 | char mem[BIKESHED_SIZE(5, 4, 3)]; 310 | memset(mem, 0, sizeof(mem)); 311 | Bikeshed shed = Bikeshed_Create(mem, 5, 4, 3, 0); 312 | 313 | TaskData tasks[5]; 314 | BikeShed_TaskFunc funcs[5] = { 315 | TaskData::Compute, 316 | TaskData::Compute, 317 | TaskData::Compute, 318 | TaskData::Compute, 319 | TaskData::Compute 320 | }; 321 | void* contexts[5] = { 322 | &tasks[0], 323 | &tasks[1], 324 | &tasks[2], 325 | &tasks[3], 326 | &tasks[4] 327 | }; 328 | Bikeshed_TaskID task_ids[5]; 329 | 330 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 5, funcs, contexts, task_ids)); 331 | Bikeshed_SetTasksChannel(shed, 2, &task_ids[0], 0u); 332 | Bikeshed_SetTasksChannel(shed, 1, &task_ids[2], 1u); 333 | Bikeshed_SetTasksChannel(shed, 2, &task_ids[3], 2u); 334 | Bikeshed_ReadyTasks(shed, 5, &task_ids[0]); 335 | 336 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 337 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 338 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 1)); 339 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 2)); 340 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 2)); 341 | 342 | for (uint32_t i = 0; i < 5; ++i) 343 | { 344 | ASSERT_EQ(task_ids[i], tasks[i].task_id); 345 | ASSERT_EQ(shed, tasks[i].shed); 346 | ASSERT_EQ(1u, tasks[i].executed); 347 | } 348 | 349 | ASSERT_EQ(0u, tasks[0].channel); 350 | ASSERT_EQ(0u, tasks[1].channel); 351 | ASSERT_EQ(1u, tasks[2].channel); 352 | ASSERT_EQ(2u, tasks[3].channel); 353 | ASSERT_EQ(2u, tasks[4].channel); 354 | 355 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 356 | } 357 | 358 | struct ReadyCounter { 359 | ReadyCounter() 360 | : ready_count(0) 361 | { 362 | cb.SignalReady = ReadyCounter::Ready; 363 | } 364 | struct Bikeshed_ReadyCallback cb; 365 | nadir::TAtomic32 ready_count; 366 | static void Ready(struct Bikeshed_ReadyCallback* ready_callback, uint8_t, uint32_t ready_count) 367 | { 368 | ReadyCounter* _this = reinterpret_cast(ready_callback); 369 | nadir::AtomicAdd32(&_this->ready_count, (int32_t)ready_count); 370 | } 371 | }; 372 | 373 | TEST(Bikeshed, ReadyCallback) 374 | { 375 | AssertAbort fatal; 376 | 377 | ReadyCounter myCallback; 378 | char mem[BIKESHED_SIZE(3, 2, 3)]; 379 | Bikeshed shed = Bikeshed_Create(mem, 3, 2, 3, &myCallback.cb); 380 | TaskData tasks[3]; 381 | BikeShed_TaskFunc funcs[3] = { 382 | TaskData::Compute, 383 | TaskData::Compute, 384 | TaskData::Compute 385 | }; 386 | void* contexts[3] = { 387 | &tasks[0], 388 | &tasks[1], 389 | &tasks[2] 390 | }; 391 | Bikeshed_TaskID task_ids[3]; 392 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 3, funcs, contexts, task_ids)); 393 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[0], 2, &task_ids[1])); 394 | Bikeshed_ReadyTasks(shed, 2, &task_ids[1]); 395 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 396 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 397 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 398 | ASSERT_TRUE(!Bikeshed_ExecuteOne(shed, 0)); 399 | ASSERT_EQ(3, myCallback.ready_count); 400 | 401 | myCallback.ready_count = 0; 402 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 3, funcs, contexts, task_ids)); 403 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[0], 1, &task_ids[2])); 404 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[1], 1, &task_ids[2])); 405 | Bikeshed_ReadyTasks(shed, 1, &task_ids[2]); 406 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 407 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 408 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 409 | ASSERT_TRUE(!Bikeshed_ExecuteOne(shed, 0)); 410 | ASSERT_EQ(3, myCallback.ready_count); 411 | 412 | myCallback.ready_count = 0; 413 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 3, funcs, contexts, task_ids)); 414 | Bikeshed_SetTasksChannel(shed, 1, &task_ids[0], 2); 415 | Bikeshed_SetTasksChannel(shed, 1, &task_ids[1], 1); 416 | Bikeshed_SetTasksChannel(shed, 1, &task_ids[2], 0); 417 | 418 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 2, &task_ids[0], 1, &task_ids[2])); 419 | Bikeshed_ReadyTasks(shed, 1, &task_ids[2]); 420 | 421 | ASSERT_TRUE(!Bikeshed_ExecuteOne(shed, 1)); 422 | ASSERT_TRUE(!Bikeshed_ExecuteOne(shed, 2)); 423 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 424 | ASSERT_TRUE(!Bikeshed_ExecuteOne(shed, 0)); 425 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 1)); 426 | ASSERT_TRUE(!Bikeshed_ExecuteOne(shed, 1)); 427 | // TODO: Broken! 428 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 2)); 429 | ASSERT_TRUE(!Bikeshed_ExecuteOne(shed, 2)); 430 | ASSERT_EQ(3, myCallback.ready_count); 431 | } 432 | 433 | TEST(Bikeshed, Dependency) 434 | { 435 | AssertAbort fatal; 436 | 437 | char mem[BIKESHED_SIZE(5, 5, 1)]; 438 | Bikeshed shed = Bikeshed_Create(mem, 5, 5, 1, 0); 439 | 440 | TaskData tasks[5]; 441 | BikeShed_TaskFunc funcs[5] = { 442 | TaskData::Compute, 443 | TaskData::Compute, 444 | TaskData::Compute, 445 | TaskData::Compute, 446 | TaskData::Compute 447 | }; 448 | void* contexts[5] = { 449 | &tasks[0], 450 | &tasks[1], 451 | &tasks[2], 452 | &tasks[3], 453 | &tasks[4] 454 | }; 455 | Bikeshed_TaskID task_ids[5]; 456 | 457 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 5, funcs, contexts, task_ids)); 458 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[0], 3, &task_ids[1])); 459 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[3], 1, &task_ids[4])); 460 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[1], 1, &task_ids[4])); 461 | Bikeshed_ReadyTasks(shed, 1, &task_ids[2]); 462 | Bikeshed_ReadyTasks(shed, 1, &task_ids[4]); 463 | 464 | for (uint32_t i = 0; i < 5; ++i) 465 | { 466 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 467 | } 468 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 469 | 470 | for (uint32_t i = 0; i < 5; ++i) 471 | { 472 | ASSERT_EQ(shed, tasks[i].shed); 473 | ASSERT_EQ(task_ids[i], tasks[i].task_id); 474 | ASSERT_EQ(1u, tasks[i].executed); 475 | } 476 | 477 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 478 | } 479 | 480 | struct NodeWorker 481 | { 482 | NodeWorker() 483 | : stop(0) 484 | , shed(0) 485 | , condition_variable(0) 486 | , thread(0) 487 | { 488 | } 489 | 490 | ~NodeWorker() 491 | { 492 | } 493 | 494 | bool CreateThread(Bikeshed in_shed, nadir::HConditionVariable in_condition_variable, nadir::TAtomic32* in_stop) 495 | { 496 | shed = in_shed; 497 | stop = in_stop; 498 | condition_variable = in_condition_variable; 499 | thread = nadir::CreateThread(malloc(nadir::GetThreadSize()), NodeWorker::Execute, 0, this); 500 | return thread != 0; 501 | } 502 | 503 | void DisposeThread() 504 | { 505 | nadir::DeleteThread(thread); 506 | free(thread); 507 | } 508 | 509 | static int32_t Execute(void* context) 510 | { 511 | NodeWorker* _this = reinterpret_cast(context); 512 | 513 | while (*_this->stop == 0) 514 | { 515 | if (!Bikeshed_ExecuteOne(_this->shed, 0)) 516 | { 517 | nadir::SleepConditionVariable(_this->condition_variable, 1000); 518 | } 519 | } 520 | return 0; 521 | } 522 | 523 | nadir::TAtomic32* stop; 524 | Bikeshed shed; 525 | nadir::HConditionVariable condition_variable; 526 | nadir::HThread thread; 527 | }; 528 | 529 | struct NadirLock 530 | { 531 | Bikeshed_ReadyCallback m_ReadyCallback; 532 | NadirLock() 533 | : m_Lock(nadir::CreateLock(malloc(nadir::GetNonReentrantLockSize()))) 534 | , m_ConditionVariable(nadir::CreateConditionVariable(malloc(nadir::GetConditionVariableSize()), m_Lock)) 535 | , m_ReadyCount(0) 536 | { 537 | m_ReadyCallback.SignalReady = signal; 538 | } 539 | ~NadirLock() 540 | { 541 | nadir::DeleteConditionVariable(m_ConditionVariable); 542 | free(m_ConditionVariable); 543 | nadir::DeleteNonReentrantLock(m_Lock); 544 | free(m_Lock); 545 | } 546 | static void signal(Bikeshed_ReadyCallback* primitive, uint8_t , uint32_t ready_count) 547 | { 548 | NadirLock* _this = reinterpret_cast(primitive); 549 | nadir::AtomicAdd32(&_this->m_ReadyCount, (int32_t)ready_count); 550 | if (ready_count > 1) 551 | { 552 | nadir::WakeAll(_this->m_ConditionVariable); 553 | } 554 | else if (ready_count > 0) 555 | { 556 | nadir::WakeOne(_this->m_ConditionVariable); 557 | } 558 | } 559 | nadir::HNonReentrantLock m_Lock; 560 | nadir::HConditionVariable m_ConditionVariable; 561 | nadir::TAtomic32 m_ReadyCount; 562 | }; 563 | 564 | struct TaskDataWorker 565 | { 566 | TaskDataWorker() 567 | : done(0) 568 | , shed(0) 569 | , task_id(0) 570 | , channel(255) 571 | , executed(0) 572 | { 573 | } 574 | static Bikeshed_TaskResult Compute(Bikeshed shed, Bikeshed_TaskID task_id, uint8_t channel, void* context_data) 575 | { 576 | TaskDataWorker* _this = reinterpret_cast(context_data); 577 | if (nadir::AtomicAdd32(&_this->executed, 1) != 1) 578 | { 579 | exit(-1); 580 | } 581 | _this->shed = shed; 582 | _this->task_id = task_id; 583 | _this->channel = channel; 584 | if (_this->done != 0) 585 | { 586 | nadir::AtomicAdd32(_this->done, 1); 587 | } 588 | return BIKESHED_TASK_RESULT_COMPLETE; 589 | } 590 | nadir::TAtomic32* done; 591 | Bikeshed shed; 592 | Bikeshed_TaskID task_id; 593 | uint8_t channel; 594 | nadir::TAtomic32 executed; 595 | }; 596 | 597 | TEST(Bikeshed, WorkerThread) 598 | { 599 | AssertAbort fatal; 600 | 601 | NadirLock sync_primitive; 602 | 603 | nadir::TAtomic32 stop = 0; 604 | TaskDataWorker task; 605 | task.done = &stop; 606 | 607 | char mem[BIKESHED_SIZE(1, 0, 1)]; 608 | Bikeshed shed = Bikeshed_Create(mem, 1, 0, 1, &sync_primitive.m_ReadyCallback); 609 | 610 | BikeShed_TaskFunc funcs[1] = { TaskDataWorker::Compute }; 611 | void* contexts[1] = { &task }; 612 | 613 | NodeWorker thread_context; 614 | thread_context.CreateThread(shed, sync_primitive.m_ConditionVariable, &stop); 615 | 616 | Bikeshed_TaskID task_id; 617 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 1, funcs, contexts, &task_id)); 618 | Bikeshed_ReadyTasks(shed, 1, &task_id); 619 | 620 | nadir::JoinThread(thread_context.thread, nadir::TIMEOUT_INFINITE); 621 | thread_context.DisposeThread(); 622 | 623 | ASSERT_EQ(shed, task.shed); 624 | ASSERT_EQ(task_id, task.task_id); 625 | ASSERT_EQ(1, task.executed); 626 | 627 | ASSERT_EQ(1, sync_primitive.m_ReadyCount); 628 | 629 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 630 | } 631 | 632 | struct TaskData2 633 | { 634 | TaskData2() 635 | : done(0) 636 | , shed(0) 637 | , task_id(0) 638 | , executed(0) 639 | { 640 | } 641 | static Bikeshed_TaskResult Compute(Bikeshed shed, Bikeshed_TaskID task_id, uint8_t , void* context_data) 642 | { 643 | TaskData2* _this = reinterpret_cast(context_data); 644 | _this->shed = shed; 645 | _this->task_id = task_id; 646 | nadir::AtomicAdd32(_this->executed, 1); 647 | return BIKESHED_TASK_RESULT_COMPLETE; 648 | } 649 | nadir::TAtomic32* done; 650 | Bikeshed shed; 651 | Bikeshed_TaskID task_id; 652 | nadir::TAtomic32* executed; 653 | }; 654 | 655 | TEST(Bikeshed, WorkerThreads) 656 | { 657 | AssertAbort fatal; 658 | 659 | NadirLock sync_primitive; 660 | 661 | nadir::TAtomic32 stop = 0; 662 | nadir::TAtomic32 executed = 0; 663 | TaskData2 task; 664 | task.executed = &executed; 665 | 666 | Bikeshed shed = Bikeshed_Create(malloc(BIKESHED_SIZE(65535, 0, 1)), 65535, 0, 1, &sync_primitive.m_ReadyCallback); 667 | 668 | BikeShed_TaskFunc funcs[1] = { TaskData2::Compute }; 669 | void* contexts[1] = { &task }; 670 | 671 | NodeWorker thread_context[5]; 672 | thread_context[0].CreateThread(shed, sync_primitive.m_ConditionVariable, &stop); 673 | thread_context[1].CreateThread(shed, sync_primitive.m_ConditionVariable, &stop); 674 | thread_context[2].CreateThread(shed, sync_primitive.m_ConditionVariable, &stop); 675 | thread_context[3].CreateThread(shed, sync_primitive.m_ConditionVariable, &stop); 676 | thread_context[4].CreateThread(shed, sync_primitive.m_ConditionVariable, &stop); 677 | 678 | for (uint32_t i = 0; i < 65535; ++i) 679 | { 680 | Bikeshed_TaskID task_id; 681 | if (!Bikeshed_CreateTasks(shed, 1, funcs, contexts, &task_id)) { 682 | break; 683 | } 684 | Bikeshed_ReadyTasks(shed, 1, &task_id); 685 | } 686 | 687 | while (executed != 65535) 688 | { 689 | nadir::Sleep(1000); 690 | } 691 | 692 | ASSERT_EQ(65535, sync_primitive.m_ReadyCount); 693 | 694 | sync_primitive.signal(&sync_primitive.m_ReadyCallback, 0, 5); 695 | nadir::AtomicAdd32(&stop, 1); 696 | 697 | nadir::JoinThread(thread_context[0].thread, nadir::TIMEOUT_INFINITE); 698 | nadir::JoinThread(thread_context[1].thread, nadir::TIMEOUT_INFINITE); 699 | nadir::JoinThread(thread_context[2].thread, nadir::TIMEOUT_INFINITE); 700 | nadir::JoinThread(thread_context[3].thread, nadir::TIMEOUT_INFINITE); 701 | nadir::JoinThread(thread_context[4].thread, nadir::TIMEOUT_INFINITE); 702 | 703 | thread_context[0].DisposeThread(); 704 | thread_context[1].DisposeThread(); 705 | thread_context[2].DisposeThread(); 706 | thread_context[3].DisposeThread(); 707 | thread_context[4].DisposeThread(); 708 | 709 | ASSERT_EQ(shed, task.shed); 710 | ASSERT_EQ(65535, executed); 711 | 712 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 713 | 714 | free(shed); 715 | } 716 | 717 | TEST(Bikeshed, DependenciesThread) 718 | { 719 | AssertAbort fatal; 720 | 721 | NadirLock sync_primitive; 722 | 723 | nadir::TAtomic32 stop = 0; 724 | 725 | TaskDataWorker tasks[5]; 726 | tasks[0].done = &stop; 727 | BikeShed_TaskFunc funcs[5] = { 728 | TaskDataWorker::Compute, 729 | TaskDataWorker::Compute, 730 | TaskDataWorker::Compute, 731 | TaskDataWorker::Compute, 732 | TaskDataWorker::Compute 733 | }; 734 | void* contexts[5] = { 735 | &tasks[0], 736 | &tasks[1], 737 | &tasks[2], 738 | &tasks[3], 739 | &tasks[4] 740 | }; 741 | Bikeshed_TaskID task_ids[5]; 742 | 743 | char mem[BIKESHED_SIZE(5, 5, 1)]; 744 | Bikeshed shed = Bikeshed_Create(mem, 5, 5, 1, &sync_primitive.m_ReadyCallback); 745 | 746 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 5, funcs, contexts, task_ids)); 747 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[0], 3, &task_ids[1])); 748 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[3], 1, &task_ids[4])); 749 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[1], 1, &task_ids[4])); 750 | 751 | NodeWorker thread_context; 752 | thread_context.CreateThread(shed, sync_primitive.m_ConditionVariable, &stop); 753 | Bikeshed_ReadyTasks(shed, 1, &task_ids[2]); 754 | Bikeshed_ReadyTasks(shed, 1, &task_ids[4]); 755 | 756 | nadir::JoinThread(thread_context.thread, nadir::TIMEOUT_INFINITE); 757 | thread_context.DisposeThread(); 758 | 759 | for (uint32_t i = 0; i < 5; ++i) 760 | { 761 | ASSERT_EQ(shed, tasks[i].shed); 762 | ASSERT_EQ(task_ids[i], tasks[i].task_id); 763 | ASSERT_EQ(1, tasks[i].executed); 764 | } 765 | ASSERT_EQ(5, sync_primitive.m_ReadyCount); 766 | 767 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 768 | } 769 | 770 | TEST(Bikeshed, DependenciesThreads) 771 | { 772 | AssertAbort fatal; 773 | 774 | NadirLock sync_primitive; 775 | 776 | static const uint32_t LAYER_COUNT = 4; 777 | static const uint32_t LAYER_0_TASK_COUNT = 1; 778 | static const uint32_t LAYER_1_TASK_COUNT = 1024; 779 | static const uint32_t LAYER_2_TASK_COUNT = 796; 780 | static const uint32_t LAYER_3_TASK_COUNT = 640; 781 | static const uint32_t LAYER_TASK_COUNT[LAYER_COUNT] = { LAYER_0_TASK_COUNT, LAYER_1_TASK_COUNT, LAYER_2_TASK_COUNT, LAYER_3_TASK_COUNT }; 782 | static const uint32_t TASK_COUNT = (uint32_t)(LAYER_0_TASK_COUNT + LAYER_1_TASK_COUNT + LAYER_2_TASK_COUNT + LAYER_3_TASK_COUNT); 783 | static const uint32_t DEPENDENCY_COUNT = LAYER_1_TASK_COUNT + LAYER_2_TASK_COUNT + LAYER_3_TASK_COUNT; 784 | 785 | static const uint32_t LAYER_TASK_OFFSET[LAYER_COUNT] = { 786 | 0, 787 | LAYER_0_TASK_COUNT, 788 | (uint32_t)(LAYER_0_TASK_COUNT + LAYER_1_TASK_COUNT), 789 | (uint32_t)(LAYER_0_TASK_COUNT + LAYER_1_TASK_COUNT + LAYER_2_TASK_COUNT) 790 | }; 791 | 792 | nadir::TAtomic32 stop = 0; 793 | nadir::TAtomic32 done = 0; 794 | 795 | Bikeshed_TaskID task_ids[TASK_COUNT]; 796 | TaskDataWorker tasks[TASK_COUNT]; 797 | tasks[0].done = &done; 798 | 799 | BikeShed_TaskFunc funcs[TASK_COUNT]; 800 | void* contexts[TASK_COUNT]; 801 | for (uint32_t task_index = 0; task_index < TASK_COUNT; ++task_index) 802 | { 803 | funcs[task_index] = TaskDataWorker::Compute; 804 | contexts[task_index] = &tasks[task_index]; 805 | } 806 | 807 | Bikeshed shed = Bikeshed_Create(malloc(BIKESHED_SIZE(TASK_COUNT, DEPENDENCY_COUNT, 1)), TASK_COUNT, DEPENDENCY_COUNT, 1, &sync_primitive.m_ReadyCallback); 808 | 809 | for (uint32_t layer_index = 0; layer_index < LAYER_COUNT; ++layer_index) 810 | { 811 | uint32_t task_offset = LAYER_TASK_OFFSET[layer_index]; 812 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, LAYER_TASK_COUNT[layer_index], &funcs[task_offset], &contexts[task_offset], &task_ids[task_offset])); 813 | } 814 | 815 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[0], LAYER_TASK_COUNT[1], &task_ids[LAYER_TASK_OFFSET[1]])); 816 | for (uint32_t i = 0; i < LAYER_TASK_COUNT[2]; ++i) 817 | { 818 | uint32_t parent_index = LAYER_TASK_OFFSET[1] + i; 819 | uint32_t child_index = LAYER_TASK_OFFSET[2] + i; 820 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[parent_index], 1, &task_ids[child_index])); 821 | } 822 | for (uint32_t i = 0; i < LAYER_TASK_COUNT[3]; ++i) 823 | { 824 | uint32_t parent_index = LAYER_TASK_OFFSET[2] + i; 825 | uint32_t child_index = LAYER_TASK_OFFSET[3] + i; 826 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[parent_index], 1, &task_ids[child_index])); 827 | } 828 | 829 | static const uint32_t WORKER_COUNT = 7; 830 | NodeWorker workers[WORKER_COUNT]; 831 | for (uint32_t worker_index = 0; worker_index < WORKER_COUNT; ++worker_index) 832 | { 833 | ASSERT_TRUE(workers[worker_index].CreateThread(shed, sync_primitive.m_ConditionVariable, &stop)); 834 | } 835 | Bikeshed_ReadyTasks(shed, LAYER_TASK_COUNT[3], &task_ids[LAYER_TASK_OFFSET[3]]); 836 | Bikeshed_ReadyTasks(shed, LAYER_TASK_COUNT[2] - LAYER_TASK_COUNT[3], &task_ids[LAYER_TASK_OFFSET[2] + LAYER_TASK_COUNT[3]]); 837 | Bikeshed_ReadyTasks(shed, LAYER_TASK_COUNT[1] - LAYER_TASK_COUNT[2], &task_ids[LAYER_TASK_OFFSET[1] + LAYER_TASK_COUNT[2]]); 838 | 839 | while (!done) 840 | { 841 | if (!Bikeshed_ExecuteOne(shed, 0)) 842 | { 843 | // We can't wait for the signal here since it only signals if there is work to be done 844 | // Ie, if another thread executes the last work item that sets done to true we will 845 | // not get a signal to wake up since no new work will be set to ready. 846 | // So we just go like crazy until top level task sets the 'done' flag 847 | nadir::Sleep(1000); 848 | } 849 | } 850 | ASSERT_EQ((int32_t)TASK_COUNT, sync_primitive.m_ReadyCount); 851 | nadir::AtomicAdd32(&stop, WORKER_COUNT); 852 | nadir::WakeAll(sync_primitive.m_ConditionVariable); 853 | 854 | for (uint32_t worker_index = 0; worker_index < WORKER_COUNT; ++worker_index) 855 | { 856 | while (!nadir::JoinThread(workers[worker_index].thread, 1000)) 857 | { 858 | // Need to look into logic for breaking workers, right now we can get in a state where 859 | // a thread is not woken up 860 | nadir::WakeAll(sync_primitive.m_ConditionVariable); 861 | } 862 | } 863 | 864 | for (uint32_t worker_index = 0; worker_index < WORKER_COUNT; ++worker_index) 865 | { 866 | workers[worker_index].DisposeThread(); 867 | } 868 | 869 | for (uint32_t i = 0; i < TASK_COUNT; ++i) 870 | { 871 | ASSERT_EQ(shed, tasks[i].shed); 872 | ASSERT_EQ(task_ids[i], tasks[i].task_id); 873 | ASSERT_EQ(1, tasks[i].executed); 874 | } 875 | 876 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 877 | 878 | free(shed); 879 | } 880 | 881 | struct MotherData 882 | { 883 | MotherData() 884 | : shed(0) 885 | , task_id(0) 886 | , executed(0) 887 | , sub_task_spawned(0) 888 | , sub_task_executed(0) 889 | { 890 | for (uint32_t i = 0; i < 5; ++i) 891 | { 892 | funcs[i] = 0; 893 | contexts[i] = 0; 894 | task_ids[i] = 0u; 895 | } 896 | } 897 | 898 | struct ChildData 899 | { 900 | ChildData() 901 | : mother_data(0) 902 | { } 903 | 904 | static Bikeshed_TaskResult Compute(Bikeshed shed, Bikeshed_TaskID , uint8_t , void* context_data) 905 | { 906 | ChildData* _this = reinterpret_cast(context_data); 907 | _this->mother_data->sub_task_executed++; 908 | --_this->mother_data->sub_task_spawned; 909 | if (_this->mother_data->sub_task_spawned == 0) 910 | { 911 | Bikeshed_ReadyTasks(shed, 1, &_this->mother_data->task_id); 912 | } 913 | return BIKESHED_TASK_RESULT_COMPLETE; 914 | } 915 | MotherData* mother_data; 916 | }; 917 | 918 | static Bikeshed_TaskResult Compute(Bikeshed shed, Bikeshed_TaskID task_id, uint8_t , void* context_data) 919 | { 920 | MotherData* _this = reinterpret_cast(context_data); 921 | 922 | _this->shed = shed; 923 | _this->task_id = task_id; 924 | 925 | if (_this->sub_task_executed == 0) 926 | { 927 | _this->funcs[0] = ChildData::Compute; 928 | _this->funcs[1] = ChildData::Compute; 929 | _this->funcs[2] = ChildData::Compute; 930 | _this->sub_tasks[0].mother_data = _this; 931 | _this->sub_tasks[1].mother_data = _this; 932 | _this->sub_tasks[2].mother_data = _this; 933 | _this->contexts[0] = &_this->sub_tasks[0]; 934 | _this->contexts[1] = &_this->sub_tasks[1]; 935 | _this->contexts[2] = &_this->sub_tasks[2]; 936 | Bikeshed_CreateTasks(shed, 3, &_this->funcs[0], &_this->contexts[0], &_this->task_ids[0]); 937 | 938 | _this->sub_task_spawned += 3; 939 | Bikeshed_ReadyTasks(shed, 3, &_this->task_ids[0]); 940 | return BIKESHED_TASK_RESULT_BLOCKED; 941 | } 942 | else if (_this->sub_task_executed == 3) 943 | { 944 | _this->funcs[3] = ChildData::Compute; 945 | _this->funcs[4] = ChildData::Compute; 946 | _this->sub_tasks[3].mother_data = _this; 947 | _this->sub_tasks[4].mother_data = _this; 948 | _this->contexts[3] = &_this->sub_tasks[3]; 949 | _this->contexts[4] = &_this->sub_tasks[4]; 950 | Bikeshed_CreateTasks(shed, 2, &_this->funcs[3], &_this->contexts[3], &_this->task_ids[3]); 951 | 952 | _this->sub_task_spawned += 2; 953 | Bikeshed_ReadyTasks(shed, 2, &_this->task_ids[3]); 954 | return BIKESHED_TASK_RESULT_BLOCKED; 955 | } 956 | else if (_this->sub_task_executed == 5) 957 | { 958 | return BIKESHED_TASK_RESULT_COMPLETE; 959 | } 960 | else 961 | { 962 | exit(-1); 963 | } 964 | } 965 | Bikeshed shed; 966 | Bikeshed_TaskID task_id; 967 | nadir::TAtomic32 executed; 968 | uint32_t sub_task_spawned; 969 | uint32_t sub_task_executed; 970 | 971 | ChildData sub_tasks[5]; 972 | BikeShed_TaskFunc funcs[5]; 973 | void* contexts[5]; 974 | Bikeshed_TaskID task_ids[5]; 975 | }; 976 | 977 | TEST(Bikeshed, InExecutionSpawnTasks) 978 | { 979 | AssertAbort fatal; 980 | 981 | NadirLock sync_primitive; 982 | 983 | nadir::TAtomic32 stop = 0; 984 | 985 | Bikeshed_TaskID mother_task_id; 986 | MotherData mother_task; 987 | void* mother_context = &mother_task; 988 | BikeShed_TaskFunc mother_func[1] = { MotherData::Compute }; 989 | 990 | char mem[BIKESHED_SIZE(4, 3, 1)]; 991 | Bikeshed shed = Bikeshed_Create(mem, 4, 3, 1, &sync_primitive.m_ReadyCallback); 992 | 993 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 1, mother_func, &mother_context, &mother_task_id)); 994 | Bikeshed_ReadyTasks(shed, 1, &mother_task_id); 995 | 996 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); // Mother 997 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); // Child[0] 998 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); // Child[1] 999 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); // Child[2] 1000 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); // Mother 1001 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); // Child[3] 1002 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); // Child[4] 1003 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); // Mother 1004 | 1005 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 1006 | 1007 | ASSERT_EQ(0, stop); 1008 | } 1009 | 1010 | TEST(Bikeshed, ShedCopyState) 1011 | { 1012 | AssertAbort fatal; 1013 | 1014 | struct CounterTask 1015 | { 1016 | CounterTask() 1017 | : m_Counter(0) 1018 | { 1019 | } 1020 | 1021 | static Bikeshed_TaskResult Execute(Bikeshed , Bikeshed_TaskID , uint8_t , void* context_data) 1022 | { 1023 | CounterTask* counter_task = reinterpret_cast(context_data); 1024 | nadir::AtomicAdd32(&counter_task->m_Counter, 1); 1025 | return BIKESHED_TASK_RESULT_COMPLETE; 1026 | } 1027 | nadir::TAtomic32 m_Counter; 1028 | }; 1029 | 1030 | // Mother0 1031 | // Mother0Child0 1032 | // Mother0Child0Child0 1033 | // Mother0Child0Child1 1034 | // Mother0Child1 1035 | // Mother0Child1Child0 1036 | // Mother0Child1Child1 1037 | // Mother0Child1Child2 1038 | // Mother1 1039 | // Mother2 1040 | // Mother3 1041 | // 1042 | // Mother123Child0 1043 | // Mother123Child1 1044 | 1045 | // 13 tasks 1046 | // 13 dependencies 1047 | 1048 | uint32_t shed_size = BIKESHED_SIZE(13, 13, 1); 1049 | 1050 | void* shed_master_mem = malloc(shed_size); 1051 | ASSERT_NE(reinterpret_cast(0), shed_master_mem); 1052 | void* shed_execute_mem = malloc(shed_size); 1053 | ASSERT_NE(reinterpret_cast(0), shed_execute_mem); 1054 | 1055 | NadirLock sync_primitive; 1056 | 1057 | Bikeshed shed_master = Bikeshed_Create(shed_master_mem, 13, 13, 1, &sync_primitive.m_ReadyCallback); 1058 | 1059 | // Build graph 1060 | 1061 | CounterTask mother[4]; 1062 | Bikeshed_TaskID motherTaskId[4]; 1063 | BikeShed_TaskFunc motherFunc[4] = {CounterTask::Execute, CounterTask::Execute, CounterTask::Execute, CounterTask::Execute}; 1064 | void* motherContext[4] = {&mother[0], &mother[1], &mother[2], &mother[3]}; 1065 | 1066 | ASSERT_NE(0, Bikeshed_CreateTasks(shed_master, 4, motherFunc, motherContext, motherTaskId)); 1067 | 1068 | CounterTask mother0Child[2]; 1069 | Bikeshed_TaskID mother0ChildTaskId[2]; 1070 | BikeShed_TaskFunc mother0ChildFunc[2] = {CounterTask::Execute, CounterTask::Execute}; 1071 | void* mother0ChildContext[2] = {&mother0Child[0], &mother0Child[1]}; 1072 | ASSERT_NE(0, Bikeshed_CreateTasks(shed_master, 2, mother0ChildFunc, mother0ChildContext, mother0ChildTaskId)); 1073 | ASSERT_NE(0, Bikeshed_AddDependencies(shed_master, 1, &motherTaskId[0], 2, mother0ChildTaskId)); 1074 | 1075 | CounterTask mother0Child0Child[2]; 1076 | Bikeshed_TaskID mother0Child0ChildTaskId[2]; 1077 | BikeShed_TaskFunc mother0Child0ChildFunc[2] = {CounterTask::Execute, CounterTask::Execute}; 1078 | void* mother0Child0ChildContext[2] = {&mother0Child0Child[0], &mother0Child0Child[1]}; 1079 | ASSERT_NE(0, Bikeshed_CreateTasks(shed_master, 2, mother0Child0ChildFunc, mother0Child0ChildContext, mother0Child0ChildTaskId)); 1080 | ASSERT_NE(0, Bikeshed_AddDependencies(shed_master, 1, &mother0ChildTaskId[0], 2, mother0Child0ChildTaskId)); 1081 | 1082 | CounterTask mother0Child1Child[3]; 1083 | Bikeshed_TaskID mother0Child1ChildTaskId[3]; 1084 | BikeShed_TaskFunc mother0Child1ChildFunc[3] = {CounterTask::Execute, CounterTask::Execute, CounterTask::Execute}; 1085 | void* mother0Child1ChildContext[3] = {&mother0Child1Child[0], &mother0Child1Child[1], &mother0Child1Child[2]}; 1086 | ASSERT_NE(0, Bikeshed_CreateTasks(shed_master, 3, mother0Child1ChildFunc, mother0Child1ChildContext, mother0Child1ChildTaskId)); 1087 | ASSERT_NE(0, Bikeshed_AddDependencies(shed_master, 1, &mother0ChildTaskId[1], 3, mother0Child1ChildTaskId)); 1088 | 1089 | CounterTask mother123Child[2]; 1090 | Bikeshed_TaskID mother123ChildTaskId[2]; 1091 | BikeShed_TaskFunc mother123ChildFunc[2] = {CounterTask::Execute, CounterTask::Execute}; 1092 | void* mother123ChildContext[2] = {&mother123Child[0], &mother123Child[1]}; 1093 | ASSERT_NE(0, Bikeshed_CreateTasks(shed_master, 2, mother123ChildFunc, mother123ChildContext, mother123ChildTaskId)); 1094 | 1095 | ASSERT_NE(0, Bikeshed_AddDependencies(shed_master, 1, &motherTaskId[1], 2, mother123ChildTaskId)); 1096 | ASSERT_NE(0, Bikeshed_AddDependencies(shed_master, 1, &motherTaskId[2], 2, mother123ChildTaskId)); 1097 | ASSERT_NE(0, Bikeshed_AddDependencies(shed_master, 1, &motherTaskId[3], 2, mother123ChildTaskId)); 1098 | 1099 | Bikeshed_ReadyTasks(shed_master, 2, mother0Child0ChildTaskId); 1100 | Bikeshed_ReadyTasks(shed_master, 3, mother0Child1ChildTaskId); 1101 | Bikeshed_ReadyTasks(shed_master, 2, mother123ChildTaskId); 1102 | 1103 | // Copy state for execution 1104 | Bikeshed shed_execute = Bikeshed_CloneState(shed_execute_mem, shed_master, shed_size); 1105 | while (Bikeshed_ExecuteOne(shed_execute, 0)); 1106 | 1107 | // Do it twice 1108 | shed_execute = Bikeshed_CloneState(shed_execute_mem, shed_master, shed_size); 1109 | while (Bikeshed_ExecuteOne(shed_execute, 0)); 1110 | 1111 | // Do it three times 1112 | shed_execute = Bikeshed_CloneState(shed_execute_mem, shed_master, shed_size); 1113 | while (Bikeshed_ExecuteOne(shed_execute, 0)); 1114 | 1115 | ASSERT_EQ(3, mother[0].m_Counter); 1116 | ASSERT_EQ(3, mother[1].m_Counter); 1117 | ASSERT_EQ(3, mother[2].m_Counter); 1118 | ASSERT_EQ(3, mother[3].m_Counter); 1119 | 1120 | free(shed_master_mem); 1121 | free(shed_execute_mem); 1122 | } 1123 | 1124 | TEST(Bikeshed, Channels) 1125 | { 1126 | AssertAbort fatal; 1127 | 1128 | char mem[BIKESHED_SIZE(5, 0, 5)]; 1129 | Bikeshed shed = Bikeshed_Create(mem, 5, 0, 5, 0); 1130 | 1131 | TaskData tasks[5]; 1132 | BikeShed_TaskFunc funcs[5] = { 1133 | TaskData::Compute, 1134 | TaskData::Compute, 1135 | TaskData::Compute, 1136 | TaskData::Compute, 1137 | TaskData::Compute 1138 | }; 1139 | void* contexts[5] = { 1140 | &tasks[0], 1141 | &tasks[1], 1142 | &tasks[2], 1143 | &tasks[3], 1144 | &tasks[4] 1145 | }; 1146 | Bikeshed_TaskID task_ids[5]; 1147 | 1148 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 5, funcs, contexts, task_ids)); 1149 | 1150 | Bikeshed_SetTasksChannel(shed, 1, &task_ids[0], 3); 1151 | Bikeshed_SetTasksChannel(shed, 1, &task_ids[1], 1); 1152 | Bikeshed_SetTasksChannel(shed, 1, &task_ids[2], 4); 1153 | Bikeshed_SetTasksChannel(shed, 1, &task_ids[3], 0); 1154 | Bikeshed_SetTasksChannel(shed, 1, &task_ids[4], 2); 1155 | 1156 | Bikeshed_ReadyTasks(shed, 5, &task_ids[0]); 1157 | 1158 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 1159 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 1160 | ASSERT_EQ(task_ids[3], tasks[3].task_id); 1161 | ASSERT_EQ(shed, tasks[3].shed); 1162 | ASSERT_EQ(1u, tasks[3].executed); 1163 | 1164 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 1)); 1165 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 1)); 1166 | ASSERT_EQ(task_ids[1], tasks[1].task_id); 1167 | ASSERT_EQ(shed, tasks[1].shed); 1168 | ASSERT_EQ(1u, tasks[1].executed); 1169 | 1170 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 2)); 1171 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 2)); 1172 | ASSERT_EQ(task_ids[4], tasks[4].task_id); 1173 | ASSERT_EQ(shed, tasks[4].shed); 1174 | ASSERT_EQ(1u, tasks[4].executed); 1175 | 1176 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 3)); 1177 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 3)); 1178 | ASSERT_EQ(task_ids[0], tasks[0].task_id); 1179 | ASSERT_EQ(shed, tasks[0].shed); 1180 | ASSERT_EQ(1u, tasks[0].executed); 1181 | 1182 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 4)); 1183 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 4)); 1184 | ASSERT_EQ(task_ids[2], tasks[2].task_id); 1185 | ASSERT_EQ(shed, tasks[2].shed); 1186 | ASSERT_EQ(1u, tasks[2].executed); 1187 | } 1188 | 1189 | TEST(Bikeshed, ChannelRanges) 1190 | { 1191 | AssertAbort fatal; 1192 | 1193 | char mem[BIKESHED_SIZE(16, 0, 5)]; 1194 | Bikeshed shed = Bikeshed_Create(mem, 16, 0, 5, 0); 1195 | 1196 | TaskData tasks[16]; 1197 | BikeShed_TaskFunc funcs[16] = { 1198 | TaskData::Compute, 1199 | TaskData::Compute, 1200 | TaskData::Compute, 1201 | TaskData::Compute, 1202 | TaskData::Compute, 1203 | TaskData::Compute, 1204 | TaskData::Compute, 1205 | TaskData::Compute, 1206 | TaskData::Compute, 1207 | TaskData::Compute, 1208 | TaskData::Compute, 1209 | TaskData::Compute, 1210 | TaskData::Compute, 1211 | TaskData::Compute, 1212 | TaskData::Compute, 1213 | TaskData::Compute 1214 | }; 1215 | void* contexts[16] = { 1216 | &tasks[0], 1217 | &tasks[1], 1218 | &tasks[2], 1219 | &tasks[3], 1220 | &tasks[4], 1221 | &tasks[5], 1222 | &tasks[6], 1223 | &tasks[7], 1224 | &tasks[8], 1225 | &tasks[9], 1226 | &tasks[10], 1227 | &tasks[11], 1228 | &tasks[12], 1229 | &tasks[13], 1230 | &tasks[14], 1231 | &tasks[15] 1232 | }; 1233 | Bikeshed_TaskID task_ids[16]; 1234 | 1235 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 16, funcs, contexts, task_ids)); 1236 | 1237 | Bikeshed_SetTasksChannel(shed, 5, &task_ids[0], 2); 1238 | Bikeshed_SetTasksChannel(shed, 5, &task_ids[5], 0); 1239 | Bikeshed_SetTasksChannel(shed, 5, &task_ids[10], 1); 1240 | Bikeshed_SetTasksChannel(shed, 1, &task_ids[15], 0); 1241 | 1242 | Bikeshed_ReadyTasks(shed, 16, &task_ids[0]); 1243 | 1244 | for (uint32_t i = 0; i < 6; ++i) 1245 | { 1246 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 1247 | } 1248 | ASSERT_TRUE(!Bikeshed_ExecuteOne(shed, 0)); 1249 | 1250 | for (uint32_t i = 0; i < 5; ++i) 1251 | { 1252 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 1)); 1253 | } 1254 | ASSERT_TRUE(!Bikeshed_ExecuteOne(shed, 1)); 1255 | 1256 | for (uint32_t i = 0; i < 5; ++i) 1257 | { 1258 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 2)); 1259 | } 1260 | ASSERT_TRUE(!Bikeshed_ExecuteOne(shed, 2)); 1261 | 1262 | for (uint32_t i = 0; i < 5; ++i) 1263 | { 1264 | ASSERT_EQ(task_ids[i + 0], tasks[i + 0].task_id); 1265 | ASSERT_EQ(shed, tasks[i + 0].shed); 1266 | ASSERT_EQ(1u, tasks[i + 0].executed); 1267 | ASSERT_EQ(2u, tasks[i + 0].channel); 1268 | } 1269 | 1270 | for (uint32_t i = 0; i < 5; ++i) 1271 | { 1272 | ASSERT_EQ(task_ids[i + 5], tasks[i + 5].task_id); 1273 | ASSERT_EQ(shed, tasks[i + 5].shed); 1274 | ASSERT_EQ(1u, tasks[i + 5].executed); 1275 | ASSERT_EQ(0u, tasks[i + 5].channel); 1276 | } 1277 | ASSERT_EQ(task_ids[15], tasks[15].task_id); 1278 | ASSERT_EQ(shed, tasks[15].shed); 1279 | ASSERT_EQ(1u, tasks[15].executed); 1280 | ASSERT_EQ(0u, tasks[15].channel); 1281 | 1282 | for (uint32_t i = 0; i < 5; ++i) 1283 | { 1284 | ASSERT_EQ(task_ids[i + 10], tasks[i + 10].task_id); 1285 | ASSERT_EQ(shed, tasks[i + 10].shed); 1286 | ASSERT_EQ(1u, tasks[i + 10].executed); 1287 | ASSERT_EQ(1u, tasks[i + 10].channel); 1288 | } 1289 | 1290 | } 1291 | 1292 | struct StealingNodeWorker 1293 | { 1294 | StealingNodeWorker() 1295 | : stop(0) 1296 | , shed(0) 1297 | , condition_variable(0) 1298 | , thread(0) 1299 | , index(0) 1300 | , count(1) 1301 | { 1302 | } 1303 | 1304 | ~StealingNodeWorker() 1305 | { 1306 | } 1307 | 1308 | bool CreateThread(Bikeshed in_shed, nadir::HConditionVariable in_condition_variable, uint32_t worker_index, uint32_t worker_count, nadir::TAtomic32* in_stop) 1309 | { 1310 | shed = in_shed; 1311 | stop = in_stop; 1312 | condition_variable = in_condition_variable; 1313 | index = worker_index; 1314 | count = worker_count; 1315 | thread = nadir::CreateThread(malloc(nadir::GetThreadSize()), StealingNodeWorker::Execute, 0, this); 1316 | return thread != 0; 1317 | } 1318 | 1319 | void DisposeThread() 1320 | { 1321 | nadir::DeleteThread(thread); 1322 | free(thread); 1323 | } 1324 | 1325 | static int32_t ExecuteOne(Bikeshed shed, uint32_t start_channel, uint32_t channel_count) 1326 | { 1327 | uint32_t channel = start_channel; 1328 | for (uint32_t c = 0; c < channel_count; ++c) 1329 | { 1330 | if (Bikeshed_ExecuteOne(shed, (uint8_t)channel)) 1331 | { 1332 | if (start_channel != channel) 1333 | { 1334 | return 2; 1335 | } 1336 | return 1; 1337 | } 1338 | channel = (channel + 1) % channel_count; 1339 | } 1340 | return 0; 1341 | } 1342 | 1343 | static int32_t Execute(void* context) 1344 | { 1345 | StealingNodeWorker* _this = reinterpret_cast(context); 1346 | 1347 | while (*_this->stop == 0) 1348 | { 1349 | if (ExecuteOne(_this->shed, _this->index, _this->count)) 1350 | { 1351 | continue; 1352 | } 1353 | nadir::SleepConditionVariable(_this->condition_variable, nadir::TIMEOUT_INFINITE); 1354 | } 1355 | return 0; 1356 | } 1357 | 1358 | nadir::TAtomic32* stop; 1359 | Bikeshed shed; 1360 | nadir::HConditionVariable condition_variable; 1361 | nadir::HThread thread; 1362 | uint32_t index; 1363 | uint32_t count; 1364 | }; 1365 | 1366 | struct TaskDataStealing 1367 | { 1368 | static const uint32_t WORKER_COUNT = 8; 1369 | static const uint32_t TASK_COUNT = WORKER_COUNT + WORKER_COUNT * WORKER_COUNT; 1370 | TaskDataStealing() 1371 | : executed_count(0) 1372 | , complete_wakeup(0) 1373 | , child_task_data(0) 1374 | , executed_channel(WORKER_COUNT) 1375 | { 1376 | } 1377 | static Bikeshed_TaskResult NoSpawn(Bikeshed , Bikeshed_TaskID , uint8_t channel, void* context) 1378 | { 1379 | TaskDataStealing* _this = reinterpret_cast(context); 1380 | _this->executed_channel = channel; 1381 | NadirLock* complete_wakeup = _this->complete_wakeup; 1382 | if (TASK_COUNT == nadir::AtomicAdd32(_this->executed_count, 1)) 1383 | { 1384 | complete_wakeup->signal(&complete_wakeup->m_ReadyCallback, 0, 1); 1385 | } 1386 | return BIKESHED_TASK_RESULT_COMPLETE; 1387 | } 1388 | static Bikeshed_TaskResult SpawnLocal(Bikeshed shed, Bikeshed_TaskID , uint8_t channel, void* context) 1389 | { 1390 | TaskDataStealing* _this = reinterpret_cast(context); 1391 | _this->executed_channel = channel; 1392 | 1393 | TaskDataStealing* tasks = _this->child_task_data; 1394 | 1395 | BikeShed_TaskFunc funcs[WORKER_COUNT]; 1396 | void* contexts[WORKER_COUNT]; 1397 | for (uint32_t i = 0; i < WORKER_COUNT; ++i) 1398 | { 1399 | tasks[i].executed_count = _this->executed_count; 1400 | tasks[i].complete_wakeup = _this->complete_wakeup; 1401 | tasks[i].child_task_data = 0; 1402 | funcs[i] = TaskDataStealing::NoSpawn; 1403 | contexts[i] = &tasks[i]; 1404 | } 1405 | Bikeshed_TaskID task_ids[WORKER_COUNT]; 1406 | if (Bikeshed_CreateTasks(shed, WORKER_COUNT, funcs, contexts, task_ids)) 1407 | { 1408 | Bikeshed_SetTasksChannel(shed, WORKER_COUNT, &task_ids[0], channel); 1409 | Bikeshed_ReadyTasks(shed, WORKER_COUNT, &task_ids[0]); 1410 | } 1411 | 1412 | nadir::AtomicAdd32(_this->executed_count, 1); 1413 | return BIKESHED_TASK_RESULT_COMPLETE; 1414 | } 1415 | nadir::TAtomic32* executed_count; 1416 | NadirLock* complete_wakeup; 1417 | TaskDataStealing* child_task_data; 1418 | uint8_t executed_channel; 1419 | }; 1420 | 1421 | TEST(Bikeshed, TaskStealing) 1422 | { 1423 | AssertAbort fatal; 1424 | 1425 | static const uint32_t SHED_SIZE BIKESHED_SIZE(TaskDataStealing::TASK_COUNT, 0, TaskDataStealing::WORKER_COUNT); 1426 | char mem[SHED_SIZE]; 1427 | 1428 | NadirLock complete_wakeup; 1429 | NadirLock sync_primitive; 1430 | 1431 | Bikeshed shed = Bikeshed_Create(mem, TaskDataStealing::TASK_COUNT, 0, TaskDataStealing::WORKER_COUNT, &sync_primitive.m_ReadyCallback); 1432 | 1433 | nadir::TAtomic32 stop = 0; 1434 | 1435 | StealingNodeWorker workers[TaskDataStealing::WORKER_COUNT]; 1436 | for (uint32_t worker_index = 0; worker_index < TaskDataStealing::WORKER_COUNT; ++worker_index) 1437 | { 1438 | ASSERT_TRUE(workers[worker_index].CreateThread(shed, sync_primitive.m_ConditionVariable, worker_index, TaskDataStealing::WORKER_COUNT, &stop)); 1439 | } 1440 | 1441 | nadir::TAtomic32 executed_count = 0; 1442 | 1443 | TaskDataStealing tasks[TaskDataStealing::TASK_COUNT]; 1444 | BikeShed_TaskFunc funcs[TaskDataStealing::WORKER_COUNT]; 1445 | void* contexts[TaskDataStealing::WORKER_COUNT]; 1446 | for (uint32_t i = 0; i < TaskDataStealing::WORKER_COUNT; ++i) 1447 | { 1448 | tasks[i].executed_count = &executed_count; 1449 | tasks[i].complete_wakeup = &complete_wakeup; 1450 | tasks[i].child_task_data = &tasks[(i + 1) * TaskDataStealing::WORKER_COUNT]; 1451 | funcs[i] = TaskDataStealing::SpawnLocal; 1452 | contexts[i] = &tasks[i]; 1453 | } 1454 | 1455 | Bikeshed_TaskID task_ids[TaskDataStealing::WORKER_COUNT]; 1456 | 1457 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, TaskDataStealing::WORKER_COUNT, funcs, contexts, task_ids)); 1458 | 1459 | for (uint8_t c = 0; c < (uint8_t)TaskDataStealing::WORKER_COUNT; ++c) 1460 | { 1461 | Bikeshed_SetTasksChannel(shed, 1, &task_ids[c], c); 1462 | } 1463 | 1464 | Bikeshed_ReadyTasks(shed, TaskDataStealing::WORKER_COUNT, &task_ids[0]); 1465 | 1466 | while(executed_count != TaskDataStealing::TASK_COUNT) 1467 | { 1468 | nadir::SleepConditionVariable(complete_wakeup.m_ConditionVariable, 1000); 1469 | } 1470 | 1471 | nadir::AtomicAdd32(&stop, TaskDataStealing::WORKER_COUNT); 1472 | nadir::WakeAll(sync_primitive.m_ConditionVariable); 1473 | 1474 | for (uint32_t worker_index = 0; worker_index < TaskDataStealing::WORKER_COUNT; ++worker_index) 1475 | { 1476 | while (!nadir::JoinThread(workers[worker_index].thread, 1000)) 1477 | { 1478 | // Need to look into logic for breaking workers, right now we can get in a state where 1479 | // a thread is not woken up 1480 | nadir::WakeAll(sync_primitive.m_ConditionVariable); 1481 | } 1482 | } 1483 | 1484 | for (uint32_t worker_index = 0; worker_index < TaskDataStealing::WORKER_COUNT; ++worker_index) 1485 | { 1486 | workers[worker_index].DisposeThread(); 1487 | } 1488 | } 1489 | 1490 | TEST(Bikeshed, TaskCreateHalfwayOverflow) 1491 | { 1492 | AssertAbort fatal; 1493 | 1494 | static const uint32_t MAX_TASK_COUNT = 4; 1495 | static const uint32_t SHED_SIZE BIKESHED_SIZE(MAX_TASK_COUNT, 0, 1); 1496 | char mem[SHED_SIZE]; 1497 | 1498 | Bikeshed shed = Bikeshed_Create(mem, MAX_TASK_COUNT, 0, 1, 0); 1499 | ASSERT_NE((Bikeshed)0, shed); 1500 | 1501 | TaskData tasks[5]; 1502 | BikeShed_TaskFunc funcs[5] = { 1503 | TaskData::Compute, 1504 | TaskData::Compute, 1505 | TaskData::Compute, 1506 | TaskData::Compute, 1507 | TaskData::Compute 1508 | }; 1509 | void* contexts[5] = { 1510 | &tasks[0], 1511 | &tasks[1], 1512 | &tasks[2], 1513 | &tasks[3], 1514 | &tasks[4] 1515 | }; 1516 | Bikeshed_TaskID task_ids[5]; 1517 | 1518 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 2, funcs, contexts, task_ids)); 1519 | 1520 | ASSERT_FALSE(Bikeshed_CreateTasks(shed, 3, &funcs[2], &contexts[2], &task_ids[2])); 1521 | 1522 | Bikeshed_ReadyTasks(shed, 2, &task_ids[0]); 1523 | 1524 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 1525 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 1526 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 1527 | 1528 | ASSERT_EQ(task_ids[0], tasks[0].task_id); 1529 | ASSERT_EQ(shed, tasks[0].shed); 1530 | ASSERT_EQ(1u, tasks[0].executed); 1531 | 1532 | ASSERT_EQ(task_ids[1], tasks[1].task_id); 1533 | ASSERT_EQ(shed, tasks[1].shed); 1534 | ASSERT_EQ(1u, tasks[1].executed); 1535 | 1536 | ASSERT_EQ(0u, tasks[2].task_id); 1537 | ASSERT_EQ(0, tasks[2].shed); 1538 | ASSERT_EQ(0u, tasks[2].executed); 1539 | 1540 | ASSERT_EQ(0u, tasks[3].task_id); 1541 | ASSERT_EQ(0, tasks[3].shed); 1542 | ASSERT_EQ(0u, tasks[3].executed); 1543 | 1544 | ASSERT_EQ(0u, tasks[4].task_id); 1545 | ASSERT_EQ(0, tasks[4].shed); 1546 | ASSERT_EQ(0u, tasks[4].executed); 1547 | } 1548 | 1549 | TEST(Bikeshed, DependencyCreateHalfwayOverflow) 1550 | { 1551 | AssertAbort fatal; 1552 | 1553 | static const uint32_t MAX_TASK_COUNT = 6; 1554 | static const uint32_t MAX_DEPENDENCY_COUNT = 4; 1555 | static const uint32_t SHED_SIZE BIKESHED_SIZE(MAX_TASK_COUNT, MAX_DEPENDENCY_COUNT, 1); 1556 | char mem[SHED_SIZE]; 1557 | 1558 | Bikeshed shed = Bikeshed_Create(mem, MAX_TASK_COUNT, MAX_DEPENDENCY_COUNT, 1, 0); 1559 | ASSERT_NE((Bikeshed)0, shed); 1560 | 1561 | TaskData tasks[6]; 1562 | BikeShed_TaskFunc funcs[6] = { 1563 | TaskData::Compute, 1564 | TaskData::Compute, 1565 | TaskData::Compute, 1566 | TaskData::Compute, 1567 | TaskData::Compute, 1568 | TaskData::Compute 1569 | }; 1570 | void* contexts[6] = { 1571 | &tasks[0], 1572 | &tasks[1], 1573 | &tasks[2], 1574 | &tasks[3], 1575 | &tasks[4], 1576 | &tasks[5] 1577 | }; 1578 | Bikeshed_TaskID task_ids[6]; 1579 | 1580 | ASSERT_TRUE(Bikeshed_CreateTasks(shed, 6, funcs, contexts, task_ids)); 1581 | 1582 | Bikeshed_TaskID dependency_ids[5] = { 1583 | task_ids[1], 1584 | task_ids[2], 1585 | task_ids[3], 1586 | task_ids[4], 1587 | task_ids[5] 1588 | }; 1589 | 1590 | ASSERT_FALSE(Bikeshed_AddDependencies(shed, 1, &task_ids[0], 5, dependency_ids)); 1591 | ASSERT_TRUE(Bikeshed_AddDependencies(shed, 1, &task_ids[0], 4, dependency_ids)); 1592 | 1593 | Bikeshed_ReadyTasks(shed, 4, &task_ids[1]); 1594 | 1595 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 1596 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 1597 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 1598 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 1599 | ASSERT_TRUE(Bikeshed_ExecuteOne(shed, 0)); 1600 | ASSERT_FALSE(Bikeshed_ExecuteOne(shed, 0)); 1601 | 1602 | ASSERT_EQ(task_ids[0], tasks[0].task_id); 1603 | ASSERT_EQ(shed, tasks[0].shed); 1604 | ASSERT_EQ(1u, tasks[0].executed); 1605 | 1606 | ASSERT_EQ(task_ids[1], tasks[1].task_id); 1607 | ASSERT_EQ(shed, tasks[1].shed); 1608 | ASSERT_EQ(1u, tasks[1].executed); 1609 | 1610 | ASSERT_EQ(task_ids[2], tasks[2].task_id); 1611 | ASSERT_EQ(shed, tasks[1].shed); 1612 | ASSERT_EQ(1u, tasks[2].executed); 1613 | 1614 | ASSERT_EQ(task_ids[3], tasks[3].task_id); 1615 | ASSERT_EQ(shed, tasks[3].shed); 1616 | ASSERT_EQ(1u, tasks[3].executed); 1617 | 1618 | ASSERT_EQ(task_ids[4], tasks[4].task_id); 1619 | ASSERT_EQ(shed, tasks[4].shed); 1620 | ASSERT_EQ(1u, tasks[4].executed); 1621 | 1622 | ASSERT_EQ(0u, tasks[5].task_id); 1623 | ASSERT_EQ(0, tasks[5].shed); 1624 | ASSERT_EQ(0u, tasks[5].executed); 1625 | } 1626 | 1627 | TEST(Bikeshed, MaxTaskCount) 1628 | { 1629 | AssertAbort fatal; 1630 | 1631 | static const uint32_t MAX_TASK_COUNT = 8388607; 1632 | static const uint32_t SHED_SIZE BIKESHED_SIZE(MAX_TASK_COUNT, 0, 1); 1633 | void* mem = malloc(SHED_SIZE); 1634 | ASSERT_NE(reinterpret_cast(0), mem); 1635 | 1636 | Bikeshed shed = Bikeshed_Create(mem, MAX_TASK_COUNT, 0, 1, 0); 1637 | ASSERT_NE((Bikeshed)0, shed); 1638 | free(mem); 1639 | } 1640 | 1641 | TEST(Bikeshed, MaxOverdraftTaskCount) 1642 | { 1643 | AssertExpect expect; 1644 | 1645 | static const uint32_t MAX_TASK_COUNT = 8388608; 1646 | static const uint32_t SHED_SIZE BIKESHED_SIZE(MAX_TASK_COUNT, 0, 1); 1647 | void* mem = malloc(SHED_SIZE); 1648 | ASSERT_NE(reinterpret_cast(0), mem); 1649 | 1650 | Bikeshed_Create(mem, MAX_TASK_COUNT, 0, 1, 0); 1651 | #if defined(BIKESHED_ASSERTS) 1652 | ASSERT_EQ(1u, AssertExpect::gAssertCount); 1653 | #endif 1654 | free(mem); 1655 | } 1656 | 1657 | TEST(Bikeshed, MaxDependencyCount) 1658 | { 1659 | AssertAbort fatal; 1660 | 1661 | static const uint32_t MAX_DEPENDENCY_COUNT = 8388607; 1662 | static const uint32_t SHED_SIZE BIKESHED_SIZE(2, MAX_DEPENDENCY_COUNT, 1); 1663 | void* mem = malloc(SHED_SIZE); 1664 | ASSERT_NE(reinterpret_cast(0), mem); 1665 | 1666 | Bikeshed_Create(mem, 2, MAX_DEPENDENCY_COUNT, 1, 0); 1667 | free(mem); 1668 | } 1669 | 1670 | TEST(Bikeshed, MaxOverdraftDependencyCount) 1671 | { 1672 | AssertExpect expect; 1673 | 1674 | static const uint32_t MAX_DEPENDENCY_COUNT = 8388608; 1675 | static const uint32_t SHED_SIZE BIKESHED_SIZE(2, MAX_DEPENDENCY_COUNT, 1); 1676 | void* mem = malloc(SHED_SIZE); 1677 | 1678 | Bikeshed_Create(mem, 2, MAX_DEPENDENCY_COUNT, 1, 0); 1679 | #if defined(BIKESHED_ASSERTS) 1680 | ASSERT_EQ(1u, AssertExpect::gAssertCount); 1681 | #endif 1682 | free(mem); 1683 | } 1684 | -------------------------------------------------------------------------------- /test/test_c99.c: -------------------------------------------------------------------------------- 1 | #define BIKESHED_IMPLEMENTATION 2 | #include "../src/bikeshed.h" 3 | -------------------------------------------------------------------------------- /third-party/jctest/src/jc_test.h: -------------------------------------------------------------------------------- 1 | /* test.h Copyright 2018 Mathias Westerdahl 2 | * 3 | * https://github.com/JCash/jctest 4 | * https://jcash.github.io/jctest 5 | * 6 | * BRIEF: 7 | * 8 | * A single header only C/C++ test framework in <1kloc 9 | * Made sure to compile with highest warning/error levels possible 10 | * 11 | * HISTORY: 12 | * 13 | * 0.3 2019-04-25 Ansi colors for Win32 14 | * Msys2 + Cygwin support 15 | * setjmp fix for Emscripten 16 | * Removed limit on number of tests 17 | * 0.2 2019-04-14 Fixed ASSERT_EQ for single precision floats 18 | * 0.1 2019-01-19 Added GTEST-like C++ interface 19 | * 20 | * LICENSE: 21 | * 22 | * The MIT License (MIT) 23 | * 24 | * Copyright (c) 2018-2019 Mathias Westerdahl 25 | * 26 | * Permission is hereby granted, free of charge, to any person obtaining a copy 27 | * of this software and associated documentation files (the "Software"), to deal 28 | * in the Software without restriction, including without limitation the rights 29 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 30 | * copies of the Software, and to permit persons to whom the Software is 31 | * furnished to do so, subject to the following conditions: 32 | * 33 | * The above copyright notice and this permission notice shall be included in all 34 | * copies or substantial portions of the Software. 35 | * 36 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 37 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 38 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 39 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 40 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 41 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 42 | * SOFTWARE. 43 | * 44 | * DISCLAIMER: 45 | * 46 | * This software is supplied "AS IS" without any warranties and support 47 | * 48 | * This software was designed to be a (non complete) replacement for GTEST, 49 | * with the intent to replace the library in an existing code base. 50 | * Although the supported features were implemented in the same spirit as the GTEST 51 | * fixtures/functions, there will be discprepancies. However, those differences have 52 | * been chosen from a pragmatic standpoint, in favor of making porting of the existing 53 | * tests feasible with minimal changes, as well as keeping this library 54 | * as light weight as possible. 55 | * 56 | * 57 | * USAGE: 58 | * For more use cases, see end of document 59 | 60 | // Smallest test case 61 | #define JC_TEST_IMPLEMENTATION 62 | #include 63 | 64 | TEST(FixtureName, TestName) { 65 | ASSERT_EQ(4, 2*2); 66 | } 67 | 68 | int main(int argc, char** argv) { 69 | jc_test_init(&argc, argv); 70 | return jc_test_run_all(); 71 | } 72 | */ 73 | 74 | #ifndef JC_TEST_H 75 | #define JC_TEST_H 76 | 77 | // *************************************************************************************** 78 | // PUBLIC API 79 | 80 | // May modify the argument list, to remove the test specific arguments 81 | extern void jc_test_init(int* argc, char** argv); 82 | 83 | // Runs all registered tests 84 | extern int jc_test_run_all(); 85 | 86 | // The standard test class 87 | struct jc_test_base_class { 88 | virtual ~jc_test_base_class(); 89 | static void SetUpTestCase() {} // The UserClass::SetUpTestCase is called before each test case runs 90 | static void TearDownTestCase() {} // The UserClass::TearDownTestCase is called after all tests have run 91 | virtual void SetUp(); // Called before each test 92 | virtual void TearDown(); // Called after each test 93 | virtual void TestBody() = 0; // Implemented by TEST_F and TEST_P 94 | private: 95 | struct Setup_should_be_spelled_SetUp {}; 96 | virtual Setup_should_be_spelled_SetUp* Setup() { return 0; } // Trick from GTEST to make sure users don't accidentally misspell the function 97 | }; 98 | 99 | // A parameterized test class, to use with TEST_P and INSTANTIATE_TEST_CASE_P 100 | template 101 | struct jc_test_params_class : public jc_test_base_class { 102 | typedef ParamType param_t; 103 | jc_test_params_class() {} 104 | static const ParamType& GetParam() { return *param; } 105 | static void SetParam(const ParamType* _param) { param = _param; } 106 | static const ParamType* param; 107 | }; 108 | 109 | // *************************************************************************************** 110 | // TEST API 111 | 112 | // Basic test 113 | // #define TEST(testfixture,testfn) 114 | // 115 | // Basic tests using a user defined fixture class (jc_test_base_class) 116 | // #define TEST_F(testfixture,testfn) 117 | // 118 | // Parameterized tests using a user defined fixture class (jc_test_params_class) 119 | // #define TEST_P(testfixture,testfn) 120 | // 121 | // Instantiation of parameterisaed test 122 | // #define INSTANTIATE_TEST_CASE_P(prefix,testfixture,testvalues) 123 | 124 | // *************************************************************************************** 125 | // ASSERTION API 126 | 127 | // #define SKIP() Skips the test 128 | 129 | // Fatal failures 130 | // #define ASSERT_TRUE( VALUE ) value 131 | // #define ASSERT_FALSE( VALUE ) !value 132 | // #define ASSERT_EQ( A, B ) A == B 133 | // #define ASSERT_NE( A, B ) A != B 134 | // #define ASSERT_LT( A, B ) A < B 135 | // #define ASSERT_GT( A, B ) A > B 136 | // #define ASSERT_LE( A, B ) A <= B 137 | // #define ASSERT_GE( A, B ) A >= B 138 | // #define ASSERT_STREQ( A, B ) strcmp(A,B) == 0 139 | // #define ASSERT_STRNE( A, B ) strcmp(A,B) != 0 140 | // #define ASSERT_NEAR( A, B, EPSILON ) abs(a - b) < epsilon 141 | // #define ASSERT_DEATH(S, RE) 142 | 143 | // #define EXPECT_TRUE( VALUE ) value 144 | // #define EXPECT_FALSE( VALUE ) !value 145 | // #define EXPECT_EQ( A, B ) A == B 146 | // #define EXPECT_NE( A, B ) A != B 147 | // #define EXPECT_LT( A, B ) A < B 148 | // #define EXPECT_GT( A, B ) A > B 149 | // #define EXPECT_LE( A, B ) A <= B 150 | // #define EXPECT_GE( A, B ) A >= B 151 | // #define EXPECT_STREQ( A, B ) strcmp(A,B) == 0 152 | // #define EXPECT_STRNE( A, B ) strcmp(A,B) != 0 153 | // #define EXPECT_NEAR( A, B, EPS ) abs(a - b) < epsilon 154 | // #define EXPECT_DEATH(S, RE) 155 | 156 | // #define SCOPED_TRACE(_MSG) // nop 157 | 158 | 159 | // *************************************************************************************** 160 | // Possible modifications for the included files 161 | 162 | // Can be used to override the logging entirely (e.g. for writing html output) 163 | #ifndef JC_TEST_LOGF 164 | #include //va_list 165 | // Can be overridden to log in a different way 166 | #define JC_TEST_LOGF jc_test_logf 167 | #endif 168 | 169 | #ifndef JC_TEST_SNPRINTF 170 | #include //snprintf 171 | #if defined(_MSC_VER) 172 | #define JC_TEST_SNPRINTF _snprintf 173 | #else 174 | #define JC_TEST_SNPRINTF snprintf 175 | #endif 176 | #endif 177 | 178 | #ifndef JC_TEST_ASSERT_FN 179 | #include 180 | #define JC_TEST_ASSERT_FN assert 181 | #endif 182 | 183 | #ifndef JC_TEST_EXIT 184 | #include 185 | #define JC_TEST_EXIT exit 186 | #endif 187 | 188 | #ifndef JC_TEST_NO_DEATH_TEST 189 | #include 190 | #include // setjmp+longjmp 191 | #if defined(__EMSCRIPTEN__) || defined(__MINGW32__) 192 | #define JC_TEST_SETJMP setjmp 193 | #else 194 | #define JC_TEST_SETJMP _setjmp 195 | #endif 196 | #endif 197 | 198 | // C++0x and above 199 | #if !defined(_MSC_VER) 200 | #pragma GCC diagnostic push 201 | #if !defined(__GNUC__) 202 | #if __cplusplus >= 199711L 203 | // Silencing them made the code unreadable, so I opted to disable them instead 204 | #pragma GCC diagnostic ignored "-Wc++98-compat" 205 | #endif 206 | #endif 207 | #if __cplusplus >= 201103L 208 | #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" 209 | #endif 210 | #endif 211 | 212 | #if defined(__GNUC__) || defined(__clang__) 213 | #define JC_TEST_UNUSED __attribute__ ((unused)) 214 | #else 215 | #define JC_TEST_UNUSED 216 | #endif 217 | 218 | #if !defined(JC_TEST_UINT64) 219 | #include 220 | #define JC_TEST_UINT64 uint64_t 221 | #define JC_TEST_UINT32 uint32_t 222 | #endif 223 | 224 | #define JC_TEST_EVENT_FIXTURE_SETUP 0 225 | #define JC_TEST_EVENT_FIXTURE_TEARDOWN 1 226 | #define JC_TEST_EVENT_TEST_SETUP 2 227 | #define JC_TEST_EVENT_TEST_TEARDOWN 3 228 | #define JC_TEST_EVENT_ASSERT_FAILED 4 229 | #define JC_TEST_EVENT_SUMMARY 5 230 | #define JC_TEST_EVENT_GENERIC 6 231 | 232 | #ifndef JC_TEST_TIMING_FUNC 233 | #define JC_TEST_TIMING_FUNC jc_test_get_time // returns micro seconds 234 | 235 | typedef unsigned long jc_test_time_t; 236 | extern jc_test_time_t jc_test_get_time(void); 237 | #endif 238 | 239 | // Returns the user defined context for the fixture 240 | typedef void* (*jc_test_fixture_setup_func)(); 241 | 242 | struct jc_test_factory_base_interface; 243 | typedef void (*jc_test_void_staticfunc)(); 244 | typedef void (jc_test_base_class::*jc_test_void_memberfunc)(); 245 | 246 | typedef struct jc_test_entry { 247 | jc_test_entry* next; // linked list 248 | const char* name; 249 | jc_test_base_class* instance; 250 | jc_test_factory_base_interface* factory; // Factory for parameterized tests 251 | JC_TEST_UINT32 fail:1; 252 | JC_TEST_UINT32 skipped:1; 253 | JC_TEST_UINT32 :30; 254 | #if defined(__x86_64__) || defined(__ppc64__) || defined(_WIN64) 255 | JC_TEST_UINT32 :32; 256 | #endif 257 | } jc_test_entry; 258 | 259 | typedef struct jc_test_stats { 260 | int num_pass; 261 | int num_fail:16; 262 | int num_skipped:16; 263 | int num_assertions; 264 | int num_tests; 265 | jc_test_time_t totaltime; 266 | } jc_test_stats; 267 | 268 | typedef struct jc_test_fixture { 269 | virtual ~jc_test_fixture(); 270 | virtual void SetParam(); 271 | jc_test_fixture* next; // linked list 272 | jc_test_entry* tests; // linked list 273 | const char* name; // The name of the fixture 274 | const char* filename; // The filename of the current ASSERT/EXPECT 275 | struct jc_test_fixture* parent; // In case of parameterized tests, this points to the first test 276 | jc_test_void_staticfunc fixture_setup; 277 | jc_test_void_staticfunc fixture_teardown; 278 | jc_test_stats stats; 279 | unsigned int fail:27; 280 | unsigned int type:2; // 0: function, 1: class, 2: params class, 3: params instance 281 | unsigned int first:1; // If it's the first in a range of fixtures 282 | unsigned int last:1; // If it's the last in a range of fixtures 283 | unsigned int fatal:1; // If set, it aborts the test 284 | unsigned int index; // the index of the param in the original params array 285 | int signum:8; // If we're checking for a signal 286 | int line:16; // The line of the current ASSERT/EXPECT 287 | int _pad:8; 288 | int num_tests; 289 | } jc_test_fixture; 290 | 291 | typedef struct jc_test_state { 292 | #if !defined(JC_TEST_NO_DEATH_TEST) 293 | jmp_buf jumpenv; // Set before trying to catch exceptions 294 | // remove padding warning due to jmp_buf struct alignment 295 | unsigned char _pad[sizeof(jmp_buf)+sizeof(void*) - (sizeof(jmp_buf)/sizeof(void*))*sizeof(void*)]; 296 | #endif 297 | 298 | jc_test_entry* current_test; 299 | jc_test_fixture* current_fixture; 300 | jc_test_fixture* fixtures; 301 | jc_test_stats stats; 302 | int num_fixtures:31; 303 | unsigned int is_a_tty:1; 304 | unsigned int :32; 305 | } jc_test_state; 306 | 307 | // *************************************************************************************** 308 | // Private functions 309 | 310 | extern jc_test_state* jc_test_get_state(); 311 | extern void jc_test_exit(); // called by jc_test_run_all 312 | extern void jc_test_set_test_fail(int fatal); 313 | extern void jc_test_set_test_skipped(); 314 | extern void jc_test_increment_assertions(); 315 | extern void jc_test_set_signal_handler(); 316 | extern void jc_test_unset_signal_handler(); 317 | extern int jc_test_streq(const char* a, const char* b); 318 | extern void jc_test_logf(const jc_test_fixture* fixture, const jc_test_entry* test, const jc_test_stats* stats, int event, const char* format, ...); 319 | extern int jc_test_cmp_double_eq(double, double); 320 | extern int jc_test_cmp_float_eq(float, float); 321 | extern int jc_test_cmp_STREQ(const char* a, const char* b, const char* exprA, const char* exprB); 322 | extern int jc_test_cmp_STRNE(const char* a, const char* b, const char* exprA, const char* exprB); 323 | extern int jc_test_cmp_NEAR(double a, double b, double epsilon, const char* exprA, const char* exprB, const char* exprC); 324 | 325 | #define JC_TEST_CAST(_TYPE_, _EXPR_) reinterpret_cast< _TYPE_ >( _EXPR_ ) 326 | #define JC_TEST_STATIC_CAST(_TYPE_, _EXPR_) static_cast< _TYPE_ >( _EXPR_ ) 327 | 328 | static inline jc_test_fixture* jc_test_get_fixture() { 329 | return jc_test_get_state()->current_fixture; 330 | } 331 | static inline jc_test_entry* jc_test_get_test() { 332 | return jc_test_get_state()->current_test; 333 | } 334 | template char* jc_test_print_value(char* buffer, size_t, const T) { 335 | buffer[0] = '?'; buffer[1] = 0; 336 | return buffer+2; 337 | } 338 | template <> char* jc_test_print_value(char* buffer, size_t buffer_len, const double value); 339 | template <> char* jc_test_print_value(char* buffer, size_t buffer_len, const float value); 340 | template <> char* jc_test_print_value(char* buffer, size_t buffer_len, const int value); 341 | template <> char* jc_test_print_value(char* buffer, size_t buffer_len, const unsigned int value); 342 | template <> char* jc_test_print_value(char* buffer, size_t buffer_len, const char* value); 343 | 344 | template 345 | static inline void jc_test_log_failure(T1 a, T2 b, const char* exprA, const char* exprB, const char* op) { 346 | char bufferA[64]; jc_test_print_value(bufferA, sizeof(bufferA), a); 347 | char bufferB[64]; jc_test_print_value(bufferB, sizeof(bufferB), b); 348 | JC_TEST_LOGF(jc_test_get_fixture(), jc_test_get_test(), 0, JC_TEST_EVENT_ASSERT_FAILED, "\nExpected: (%s) %s (%s), actual: %s vs %s\n", exprA, op, exprB, bufferA, bufferB); 349 | } 350 | 351 | template 352 | static inline void jc_test_log_failure_boolean(T v, const char* expr) { 353 | JC_TEST_LOGF(jc_test_get_fixture(), jc_test_get_test(), 0, JC_TEST_EVENT_ASSERT_FAILED, "\nValue of: %s\nExpected: %s\n Actual: %s\n", expr, (!v)?"true":"false", v?"true":"false"); 354 | } 355 | 356 | template 357 | static inline void jc_test_log_failure_str(const T* a, const T* b, const char* exprA, const char* exprB, const char* op) { 358 | JC_TEST_LOGF(jc_test_get_fixture(), jc_test_get_test(), 0, JC_TEST_EVENT_ASSERT_FAILED, "\nValue of: %s %s %s\nExpected: %s\n Actual: %s\n", exprA, op, exprB, a, b); 359 | } 360 | 361 | template 362 | int jc_test_cmp_TRUE(T v, const char* expr) { 363 | if (v) return 1; 364 | jc_test_log_failure_boolean(v, expr); 365 | return 0; 366 | } 367 | template 368 | int jc_test_cmp_FALSE(T v, const char* expr) { 369 | if (!v) return 1; 370 | jc_test_log_failure_boolean(v, expr); 371 | return 0; 372 | } 373 | 374 | #define JC_TEST_COMPARE_FUNC(OP_NAME, OP) \ 375 | template \ 376 | int jc_test_cmp_ ## OP_NAME(T1 a, T2 b, const char* exprA, const char* exprB) { \ 377 | if (a OP b) return 1; \ 378 | jc_test_log_failure(a, b, exprA, exprB, #OP); \ 379 | return 0; \ 380 | } 381 | 382 | JC_TEST_COMPARE_FUNC(EQ, ==) 383 | JC_TEST_COMPARE_FUNC(NE, !=) 384 | JC_TEST_COMPARE_FUNC(LE, <=) 385 | JC_TEST_COMPARE_FUNC(LT, <) 386 | JC_TEST_COMPARE_FUNC(GE, >=) 387 | JC_TEST_COMPARE_FUNC(GT, >) 388 | 389 | template int jc_test_cmp_EQ(double a, T b, const char* exprA, const char* exprB) { 390 | if (jc_test_cmp_double_eq(a, JC_TEST_STATIC_CAST(double, b))) return 1; 391 | jc_test_log_failure(a, b, exprA, exprB, "=="); 392 | return 0; 393 | } 394 | template int jc_test_cmp_EQ(float a, T b, const char* exprA, const char* exprB) { 395 | if (jc_test_cmp_float_eq(a, JC_TEST_STATIC_CAST(float, b))) return 1; 396 | jc_test_log_failure(a, b, exprA, exprB, "=="); 397 | return 0; 398 | } 399 | 400 | template int jc_test_cmp_NE(double a, T b, const char* exprA, const char* exprB) { 401 | if (!jc_test_cmp_double_eq(a, JC_TEST_STATIC_CAST(double, b))) return 1; 402 | jc_test_log_failure(a, b, exprA, exprB, "!="); 403 | return 0; 404 | } 405 | template int jc_test_cmp_NE(float a, T b, const char* exprA, const char* exprB) { 406 | if (!jc_test_cmp_float_eq(a, JC_TEST_STATIC_CAST(float, b))) return 1; 407 | jc_test_log_failure(a, b, exprA, exprB, "!="); 408 | return 0; 409 | } 410 | 411 | // The only way to match this function, is with a null literal (since the type is hidden) 412 | char _jc_test_is_null_literal(struct _jc_test_null_literal* p); 413 | char (&_jc_test_is_null_literal(...))[2]; 414 | #define JC_TEST_IS_NULL_LITERAL(VALUE) (sizeof(_jc_test_is_null_literal(VALUE)) == 1) 415 | 416 | template struct jc_test_is_pointer { 417 | static const bool value = false; 418 | }; 419 | template struct jc_test_is_pointer { 420 | static const bool value = true; 421 | }; 422 | template struct jc_test_enable_argument; 423 | template <> struct jc_test_enable_argument { typedef void type; }; 424 | 425 | 426 | template 427 | struct jc_test_cmp_eq_helper { 428 | template 429 | static int compare(const T1& a, const T2& b, const char* exprA, const char* exprB) { 430 | return jc_test_cmp_EQ(a, b, exprA, exprB); 431 | } 432 | }; 433 | 434 | template<> 435 | struct jc_test_cmp_eq_helper { 436 | template 437 | static int compare(const T1& a, const T2& b, const char* exprA, const char* exprB, 438 | typename jc_test_enable_argument::value>::type* = 0) { 439 | return jc_test_cmp_EQ(a, b, exprA, exprB); 440 | } 441 | template 442 | static int compare(_jc_test_null_literal*, T* b, const char* exprA, const char* exprB) { 443 | return jc_test_cmp_EQ(JC_TEST_STATIC_CAST(T*, 0), b, exprA, exprB); 444 | } 445 | }; 446 | 447 | #define JC_TEST_ASSERT_SETUP \ 448 | jc_test_get_fixture()->line = __LINE__; \ 449 | jc_test_get_fixture()->filename = __FILE__; \ 450 | jc_test_increment_assertions() 451 | 452 | #define JC_TEST_FATAL_FAILURE jc_test_set_test_fail(1); return 453 | #define JC_TEST_NON_FATAL_FAILURE jc_test_set_test_fail(0); 454 | 455 | #define JC_ASSERT_TEST_BOOLEAN(OP, VALUE, FAIL_FUNC) \ 456 | do { \ 457 | JC_TEST_ASSERT_SETUP; \ 458 | if ( jc_test_cmp_##OP (VALUE, #VALUE) == 0 ) { \ 459 | FAIL_FUNC; \ 460 | } \ 461 | } while(0) 462 | #define JC_ASSERT_TEST_EQ(A, B, FAIL_FUNC) \ 463 | do { \ 464 | JC_TEST_ASSERT_SETUP; \ 465 | if ( jc_test_cmp_eq_helper::compare(A, B, #A, #B) == 0 ) { \ 466 | FAIL_FUNC; \ 467 | } \ 468 | } while(0) 469 | #define JC_ASSERT_TEST_OP(OP, A, B, FAIL_FUNC) \ 470 | do { \ 471 | JC_TEST_ASSERT_SETUP; \ 472 | if ( jc_test_cmp_##OP (A, B, #A, #B) == 0 ) { \ 473 | FAIL_FUNC; \ 474 | } \ 475 | } while(0) 476 | #define JC_ASSERT_TEST_3OP(OP, A, B, C, FAIL_FUNC) \ 477 | do { \ 478 | JC_TEST_ASSERT_SETUP; \ 479 | if ( jc_test_cmp_##OP (A, B, C, #A, #B, #C) == 0 ) { \ 480 | FAIL_FUNC; \ 481 | } \ 482 | } while(0) 483 | #if !defined(JC_TEST_NO_DEATH_TEST) 484 | #define JC_ASSERT_TEST_DEATH_OP(STATEMENT, RE, FAIL_FUNC) \ 485 | do { \ 486 | JC_TEST_ASSERT_SETUP; \ 487 | if (JC_TEST_SETJMP(jc_test_get_state()->jumpenv) == 0) { \ 488 | jc_test_set_signal_handler(); \ 489 | JC_TEST_LOGF(jc_test_get_fixture(), jc_test_get_test(), 0, JC_TEST_EVENT_GENERIC, "\njc_test: Death test begin ->\n"); \ 490 | STATEMENT; \ 491 | JC_TEST_LOGF(jc_test_get_fixture(), jc_test_get_test(), 0, JC_TEST_EVENT_ASSERT_FAILED, "\nExpected this to fail: %s", #STATEMENT ); \ 492 | jc_test_unset_signal_handler(); \ 493 | FAIL_FUNC; \ 494 | } \ 495 | jc_test_unset_signal_handler(); \ 496 | JC_TEST_LOGF(jc_test_get_fixture(), jc_test_get_test(), 0, JC_TEST_EVENT_GENERIC, "jc_test: <- Death test end\n"); \ 497 | } while(0) 498 | #else 499 | #define JC_ASSERT_TEST_DEATH_OP(STATEMENT, RE, FAIL_FUNC) 500 | #endif 501 | 502 | // TEST API Begin --> 503 | 504 | #define SKIP() { jc_test_set_test_skipped(); return; } 505 | 506 | #define ASSERT_TRUE( VALUE ) JC_ASSERT_TEST_BOOLEAN( TRUE, VALUE, JC_TEST_FATAL_FAILURE ) 507 | #define ASSERT_FALSE( VALUE ) JC_ASSERT_TEST_BOOLEAN( FALSE, VALUE, JC_TEST_FATAL_FAILURE ) 508 | #define ASSERT_EQ( A, B ) JC_ASSERT_TEST_EQ( A, B, JC_TEST_FATAL_FAILURE ) 509 | #define ASSERT_NE( A, B ) JC_ASSERT_TEST_OP( NE, A, B, JC_TEST_FATAL_FAILURE ) 510 | #define ASSERT_LT( A, B ) JC_ASSERT_TEST_OP( LT, A, B, JC_TEST_FATAL_FAILURE ) 511 | #define ASSERT_GT( A, B ) JC_ASSERT_TEST_OP( GT, A, B, JC_TEST_FATAL_FAILURE ) 512 | #define ASSERT_LE( A, B ) JC_ASSERT_TEST_OP( LE, A, B, JC_TEST_FATAL_FAILURE ) 513 | #define ASSERT_GE( A, B ) JC_ASSERT_TEST_OP( GE, A, B, JC_TEST_FATAL_FAILURE ) 514 | #define ASSERT_STREQ( A, B ) JC_ASSERT_TEST_OP( STREQ, A, B, JC_TEST_FATAL_FAILURE ) 515 | #define ASSERT_STRNE( A, B ) JC_ASSERT_TEST_OP( STRNE, A, B, JC_TEST_FATAL_FAILURE ) 516 | #define ASSERT_NEAR( A, B, EPS ) JC_ASSERT_TEST_3OP( NEAR, A, B, EPS, JC_TEST_FATAL_FAILURE ) 517 | #define ASSERT_DEATH(S, RE) JC_ASSERT_TEST_DEATH_OP( S, RE, JC_TEST_FATAL_FAILURE ) 518 | 519 | #define EXPECT_TRUE( VALUE ) JC_ASSERT_TEST_BOOLEAN( TRUE, VALUE, JC_TEST_NON_FATAL_FAILURE ) 520 | #define EXPECT_FALSE( VALUE ) JC_ASSERT_TEST_BOOLEAN( FALSE, VALUE, JC_TEST_NON_FATAL_FAILURE ) 521 | #define EXPECT_EQ( A, B ) JC_ASSERT_TEST_EQ( A, B, JC_TEST_NON_FATAL_FAILURE ) 522 | #define EXPECT_NE( A, B ) JC_ASSERT_TEST_OP( NE, A, B, JC_TEST_NON_FATAL_FAILURE ) 523 | #define EXPECT_LT( A, B ) JC_ASSERT_TEST_OP( LT, A, B, JC_TEST_NON_FATAL_FAILURE ) 524 | #define EXPECT_GT( A, B ) JC_ASSERT_TEST_OP( GT, A, B, JC_TEST_NON_FATAL_FAILURE ) 525 | #define EXPECT_LE( A, B ) JC_ASSERT_TEST_OP( LE, A, B, JC_TEST_NON_FATAL_FAILURE ) 526 | #define EXPECT_GE( A, B ) JC_ASSERT_TEST_OP( GE, A, B, JC_TEST_NON_FATAL_FAILURE ) 527 | #define EXPECT_STREQ( A, B ) JC_ASSERT_TEST_OP( STREQ, A, B, JC_TEST_NON_FATAL_FAILURE ) 528 | #define EXPECT_STRNE( A, B ) JC_ASSERT_TEST_OP( STRNE, A, B, JC_TEST_NON_FATAL_FAILURE ) 529 | #define EXPECT_NEAR( A, B, EPS ) JC_ASSERT_TEST_3OP( NEAR, A, B, EPS, JC_TEST_NON_FATAL_FAILURE ) 530 | #define EXPECT_DEATH(S, RE) JC_ASSERT_TEST_DEATH_OP(S, RE, JC_TEST_NON_FATAL_FAILURE ) 531 | 532 | #define SCOPED_TRACE(_MSG) // nop 533 | 534 | template 535 | struct jc_test_value_iterator { 536 | virtual ~jc_test_value_iterator(); 537 | virtual const T* Get() const = 0; 538 | virtual void Advance() = 0; 539 | virtual bool Empty() const = 0; // return false when out of values 540 | virtual void Rewind() = 0; 541 | }; 542 | template jc_test_value_iterator::~jc_test_value_iterator() {} // separate line to silence warning 543 | 544 | template 545 | struct jc_test_array_iterator : public jc_test_value_iterator { 546 | const T *begin, *cursor, *end; 547 | jc_test_array_iterator(const T* _begin, const T* _end) : begin(_begin), cursor(_begin), end(_end) {} 548 | const T* Get() const{ return cursor; } 549 | void Advance() { ++cursor; } 550 | bool Empty() const { return cursor == end; } 551 | void Rewind() { cursor = begin; } 552 | }; 553 | 554 | template jc_test_array_iterator* jc_test_values_in(const T* begin, const T* end) { 555 | return new jc_test_array_iterator(begin, end); 556 | } 557 | template jc_test_array_iterator* jc_test_values_in(const T (&arr)[N] ) { 558 | return jc_test_values_in(arr, arr+N); 559 | } 560 | 561 | template const ParamType* jc_test_params_class::param = 0; 562 | 563 | template 564 | struct jc_test_fixture_with_param : public jc_test_fixture { 565 | void SetParam() { JC_TEST_CAST(jc_test_params_class*, jc_test_get_state()->current_test)->SetParam(param); } 566 | const ParamType* param; 567 | }; 568 | 569 | struct jc_test_factory_base_interface { 570 | virtual ~jc_test_factory_base_interface(); 571 | }; 572 | 573 | template 574 | struct jc_test_factory_interface : public jc_test_factory_base_interface { 575 | virtual jc_test_params_class* New() = 0; 576 | virtual void SetParam(const ParamType* param) = 0; 577 | }; 578 | 579 | template 580 | struct jc_test_factory : public jc_test_factory_interface { 581 | jc_test_params_class* New() { return new T(); } 582 | void SetParam(const typename T::param_t* param) { T::SetParam(param); } 583 | }; 584 | 585 | #define JC_TEST_FIXTURE_TYPE_CLASS 0 586 | #define JC_TEST_FIXTURE_TYPE_PARAMS_CLASS 1 587 | #define JC_TEST_FIXTURE_TYPE_TYPED_CLASS 2 588 | 589 | extern jc_test_fixture* jc_test_find_fixture(const char* name, unsigned int fixture_type); 590 | extern jc_test_fixture* jc_test_alloc_fixture(const char* name, unsigned int fixture_type); 591 | extern jc_test_fixture* jc_test_create_fixture(jc_test_fixture* fixture, const char* name, unsigned int fixture_type); 592 | extern jc_test_entry* jc_test_add_test_to_fixture(jc_test_fixture* fixture, const char* test_name, jc_test_base_class* instance, jc_test_factory_base_interface* factory); 593 | extern void jc_test_memcpy(void* dst, void* src, size_t size); 594 | 595 | extern int jc_test_register_class_test(const char* fixture_name, const char* test_name, 596 | jc_test_void_staticfunc class_setup, jc_test_void_staticfunc class_teardown, 597 | jc_test_base_class* instance, unsigned int fixture_type); 598 | template 599 | int jc_test_register_param_class_test(const char* fixture_name, const char* test_name, 600 | jc_test_void_staticfunc class_setup, jc_test_void_staticfunc class_teardown, 601 | jc_test_factory_interface* factory) { 602 | jc_test_fixture* fixture = jc_test_find_fixture(fixture_name, JC_TEST_FIXTURE_TYPE_PARAMS_CLASS); 603 | if (!fixture) { 604 | fixture = jc_test_alloc_fixture(fixture_name, JC_TEST_FIXTURE_TYPE_PARAMS_CLASS); 605 | fixture->fixture_setup = class_setup; 606 | fixture->fixture_teardown = class_teardown; 607 | } 608 | jc_test_add_test_to_fixture(fixture, test_name, 0, factory); 609 | return 0; 610 | } 611 | 612 | struct jc_test_type0 {}; 613 | 614 | template 615 | struct jc_test_type1 { 616 | typedef T1 head; typedef jc_test_type0 tail; 617 | }; 618 | template 619 | struct jc_test_type2 { 620 | typedef T2 head; typedef jc_test_type1 tail; 621 | }; 622 | template 623 | struct jc_test_type3 { 624 | typedef T3 head; typedef jc_test_type2 tail; 625 | }; 626 | template 627 | struct jc_test_type4 { 628 | typedef T4 head; typedef jc_test_type3 tail; 629 | }; 630 | 631 | template 632 | struct jc_test_register_typed_class_test { 633 | static int register_test(const char* fixture_name, const char* test_name, unsigned int index) { 634 | typedef typename TypeList::head TypeParam; 635 | typedef typename BaseClassSelector::template bind::type TestClass; 636 | 637 | jc_test_fixture* fixture = jc_test_find_fixture(fixture_name, JC_TEST_FIXTURE_TYPE_TYPED_CLASS); 638 | if (!fixture) { 639 | fixture = jc_test_alloc_fixture(fixture_name, JC_TEST_FIXTURE_TYPE_CLASS); 640 | fixture->fixture_setup = TestClass::SetUpTestCase; 641 | fixture->fixture_teardown = TestClass::TearDownTestCase; 642 | } 643 | fixture->index = index; 644 | jc_test_add_test_to_fixture(fixture, test_name, new TestClass, 0); 645 | return jc_test_register_typed_class_test:: 646 | register_test(fixture_name, test_name, index+1); 647 | } 648 | }; 649 | 650 | template 651 | struct jc_test_register_typed_class_test { 652 | static int register_test(const char*, const char*, unsigned int) { 653 | return 0; 654 | } 655 | }; 656 | 657 | template 658 | jc_test_fixture* jc_test_alloc_fixture_with_param(const char* name, unsigned int type) { 659 | return jc_test_create_fixture(new jc_test_fixture_with_param, name, type); 660 | } 661 | 662 | template 663 | int jc_test_register_param_tests(const char* prototype_fixture_name, const char* fixture_name, jc_test_value_iterator* values) 664 | { 665 | unsigned int index = 0; 666 | jc_test_fixture* first_fixture = 0; 667 | while (!values->Empty()) { 668 | jc_test_fixture* prototype_fixture = jc_test_find_fixture(prototype_fixture_name, JC_TEST_FIXTURE_TYPE_PARAMS_CLASS); 669 | if (!prototype_fixture) { 670 | JC_TEST_LOGF(0, 0, 0, JC_TEST_EVENT_GENERIC, "Couldn't find fixture of name %s\n", prototype_fixture_name); 671 | JC_TEST_ASSERT_FN(prototype_fixture != 0); 672 | return 0; 673 | } 674 | JC_TEST_ASSERT_FN(prototype_fixture->type == JC_TEST_FIXTURE_TYPE_PARAMS_CLASS); 675 | 676 | // Allocate a new fixture, and create the test class 677 | jc_test_fixture_with_param* fixture = JC_TEST_CAST(jc_test_fixture_with_param*, 678 | jc_test_alloc_fixture_with_param(fixture_name, JC_TEST_FIXTURE_TYPE_CLASS) ); 679 | 680 | fixture->first = first_fixture == 0 ? 1 : 0; 681 | if (!first_fixture) { 682 | first_fixture = fixture; // A silly trick to make the first fixture accumulate all the timings from this batch 683 | } 684 | fixture->parent = first_fixture; 685 | fixture->index = index++; 686 | fixture->param = values->Get(); 687 | fixture->fixture_setup = prototype_fixture->fixture_setup; 688 | fixture->fixture_teardown = prototype_fixture->fixture_teardown; 689 | 690 | fixture->num_tests = prototype_fixture->num_tests; 691 | jc_test_entry* prototype_test = prototype_fixture->tests; 692 | jc_test_entry* first = 0; 693 | jc_test_entry* prev = 0; 694 | while (prototype_test) { 695 | jc_test_entry* test = new jc_test_entry; 696 | test->next = 0; 697 | test->name = prototype_test->name; 698 | test->factory = 0; 699 | test->fail = 0; 700 | test->skipped = 0; 701 | 702 | jc_test_factory_interface* factory = JC_TEST_CAST(jc_test_factory_interface*, prototype_test->factory); 703 | factory->SetParam(fixture->param); 704 | test->instance = factory->New(); 705 | 706 | if (!first) { 707 | first = test; 708 | prev = test; 709 | } else { 710 | prev->next = test; 711 | prev = test; 712 | } 713 | prototype_test = prototype_test->next; 714 | } 715 | fixture->tests = first; 716 | 717 | values->Advance(); 718 | 719 | fixture->last = values->Empty() ? 1 : 0; 720 | } 721 | 722 | delete values; 723 | return 0; 724 | } 725 | 726 | #define JC_TEST_MAKE_NAME2(X,Y) X ## _ ## Y 727 | #define JC_TEST_MAKE_NAME3(X,Y,Z) X ## _ ## Y ## _ ## Z 728 | #define JC_TEST_MAKE_CLASS_NAME(X, Y) JC_TEST_MAKE_NAME3(X, Y, _TestCase) 729 | #define JC_TEST_MAKE_FUNCTION_NAME(X, Y) JC_TEST_MAKE_NAME2(X, Y) 730 | #define JC_TEST_MAKE_UNIQUE_NAME(X, Y, LINE) JC_TEST_MAKE_NAME3(X, Y, LINE) 731 | 732 | #define TEST(testfixture,testfn) \ 733 | class JC_TEST_MAKE_CLASS_NAME(testfixture,testfn) : public jc_test_base_class { \ 734 | virtual void TestBody(); \ 735 | }; \ 736 | static int JC_TEST_MAKE_UNIQUE_NAME(testfixture,testfn,__LINE__) JC_TEST_UNUSED = jc_test_register_class_test( \ 737 | #testfixture, #testfn, jc_test_base_class::SetUpTestCase, jc_test_base_class::TearDownTestCase, \ 738 | new JC_TEST_MAKE_CLASS_NAME(testfixture,testfn), JC_TEST_FIXTURE_TYPE_CLASS); \ 739 | void JC_TEST_MAKE_CLASS_NAME(testfixture,testfn)::TestBody() 740 | 741 | #define TEST_F(testfixture,testfn) \ 742 | class JC_TEST_MAKE_CLASS_NAME(testfixture,testfn) : public testfixture { \ 743 | virtual void TestBody(); \ 744 | }; \ 745 | static int JC_TEST_MAKE_UNIQUE_NAME(testfixture,testfn,__LINE__) JC_TEST_UNUSED = jc_test_register_class_test( \ 746 | #testfixture, #testfn, testfixture::SetUpTestCase, testfixture::TearDownTestCase, \ 747 | new JC_TEST_MAKE_CLASS_NAME(testfixture,testfn), JC_TEST_FIXTURE_TYPE_CLASS); \ 748 | void JC_TEST_MAKE_CLASS_NAME(testfixture,testfn)::TestBody() 749 | 750 | #define TEST_P(testfixture,testfn) \ 751 | class JC_TEST_MAKE_CLASS_NAME(testfixture,testfn) : public testfixture { \ 752 | virtual void TestBody(); \ 753 | }; \ 754 | static int JC_TEST_MAKE_UNIQUE_NAME(testfixture,testfn,__LINE__) JC_TEST_UNUSED = jc_test_register_param_class_test( \ 755 | #testfixture, #testfn, testfixture::SetUpTestCase, testfixture::TearDownTestCase, \ 756 | new jc_test_factory()); \ 757 | void JC_TEST_MAKE_CLASS_NAME(testfixture,testfn)::TestBody() 758 | 759 | #define INSTANTIATE_TEST_CASE_P(prefix,testfixture,testvalues) \ 760 | static int JC_TEST_MAKE_UNIQUE_NAME(prefix,testfixture,__LINE__) JC_TEST_UNUSED = \ 761 | jc_test_register_param_tests(#testfixture, #prefix "/" #testfixture, testvalues) 762 | 763 | 764 | template struct jc_test_typed_list { 765 | typedef T type; 766 | }; 767 | 768 | template