diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..16796b6 --- /dev/null +++ b/.clang-format @@ -0,0 +1,225 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: true +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: NextLine +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 3 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: true +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Left +PPIndentWidth: -1 +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + - ParseTestProto + - ParsePartialTestProto + CanonicalDelimiter: pb + BasedOnStyle: google +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..30485f6 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,32 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*' +WarningsAsErrors: '' +HeaderFileExtensions: + - '' + - h + - hh + - hpp + - hxx +ImplementationFileExtensions: + - c + - cc + - cpp + - cxx +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +FormatStyle: none +CheckOptions: + cert-dcl16-c.NewSuffixes: 'L;LL;LU;LLU' + google-readability-namespace-comments.ShortNamespaceLines: '10' + cert-err33-c.CheckedFunctions: '::aligned_alloc;::asctime_s;::at_quick_exit;::atexit;::bsearch;::bsearch_s;::btowc;::c16rtomb;::c32rtomb;::calloc;::clock;::cnd_broadcast;::cnd_init;::cnd_signal;::cnd_timedwait;::cnd_wait;::ctime_s;::fclose;::fflush;::fgetc;::fgetpos;::fgets;::fgetwc;::fopen;::fopen_s;::fprintf;::fprintf_s;::fputc;::fputs;::fputwc;::fputws;::fread;::freopen;::freopen_s;::fscanf;::fscanf_s;::fseek;::fsetpos;::ftell;::fwprintf;::fwprintf_s;::fwrite;::fwscanf;::fwscanf_s;::getc;::getchar;::getenv;::getenv_s;::gets_s;::getwc;::getwchar;::gmtime;::gmtime_s;::localtime;::localtime_s;::malloc;::mbrtoc16;::mbrtoc32;::mbsrtowcs;::mbsrtowcs_s;::mbstowcs;::mbstowcs_s;::memchr;::mktime;::mtx_init;::mtx_lock;::mtx_timedlock;::mtx_trylock;::mtx_unlock;::printf_s;::putc;::putwc;::raise;::realloc;::remove;::rename;::scanf;::scanf_s;::setlocale;::setvbuf;::signal;::snprintf;::snprintf_s;::sprintf;::sprintf_s;::sscanf;::sscanf_s;::strchr;::strerror_s;::strftime;::strpbrk;::strrchr;::strstr;::strtod;::strtof;::strtoimax;::strtok;::strtok_s;::strtol;::strtold;::strtoll;::strtoul;::strtoull;::strtoumax;::strxfrm;::swprintf;::swprintf_s;::swscanf;::swscanf_s;::thrd_create;::thrd_detach;::thrd_join;::thrd_sleep;::time;::timespec_get;::tmpfile;::tmpfile_s;::tmpnam;::tmpnam_s;::tss_create;::tss_get;::tss_set;::ungetc;::ungetwc;::vfprintf;::vfprintf_s;::vfscanf;::vfscanf_s;::vfwprintf;::vfwprintf_s;::vfwscanf;::vfwscanf_s;::vprintf_s;::vscanf;::vscanf_s;::vsnprintf;::vsnprintf_s;::vsprintf;::vsprintf_s;::vsscanf;::vsscanf_s;::vswprintf;::vswprintf_s;::vswscanf;::vswscanf_s;::vwprintf_s;::vwscanf;::vwscanf_s;::wcrtomb;::wcschr;::wcsftime;::wcspbrk;::wcsrchr;::wcsrtombs;::wcsrtombs_s;::wcsstr;::wcstod;::wcstof;::wcstoimax;::wcstok;::wcstok_s;::wcstol;::wcstold;::wcstoll;::wcstombs;::wcstombs_s;::wcstoul;::wcstoull;::wcstoumax;::wcsxfrm;::wctob;::wctrans;::wctype;::wmemchr;::wprintf_s;::wscanf;::wscanf_s;' + llvm-else-after-return.WarnOnUnfixable: 'false' + cert-str34-c.DiagnoseSignedUnsignedCharComparisons: 'false' + google-readability-namespace-comments.SpacesBeforeComments: '2' + cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: 'true' + google-readability-braces-around-statements.ShortStatementLines: '1' + google-readability-function-size.StatementThreshold: '800' + llvm-qualified-auto.AddConstToQualified: 'false' + llvm-else-after-return.WarnOnConditionVariables: 'false' + cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField: 'false' +SystemHeaders: false +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd2b9ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,640 @@ +# Built products +build* +cmake-build-debug* +out/** + +# Compiled PyTorch models +*.pt + +## C/C++ + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +## macOS + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +## Linux + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +## Windows + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +## VSCode IDE + +.vscode/* + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +## CMake + +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + + +# Byte-compiled / optimized / DLL files +*__pycache__/* +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + + +# CMake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +CMakeUserPresets.json + +############################################################ +# Visual Studio - Start +############################################################ + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# fuzzing +sync_dir* + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +# Ignore the executable +/vcpkg.exe + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +############################################################ +# Visual Studio - End +############################################################ + + +############################################################ +# vcpkg - Start +############################################################ + +.vscode/ +*.code-workspace +/buildtrees/ +/build*/ +/downloads/ +/installed*/ +/vcpkg_installed*/ +/packages/ +/scripts/buildsystems/tmp/ +#ignore custom triplets +/triplets/* +#add vcpkg-designed triplets back in +!/triplets/arm-uwp.cmake +!/triplets/arm64-windows.cmake +!/triplets/x64-linux.cmake +!/triplets/x64-osx.cmake +!/triplets/x64-uwp.cmake +!/triplets/x64-windows-static.cmake +!/triplets/x64-windows.cmake +!/triplets/x86-windows.cmake + +!/triplets/community +!/triplets/community/** + +*.exe +*.zip + +############################################################ +# vcpkg - End +############################################################ +vcpkg.disable-metrics +archives +.DS_Store +prefab/ +*.swp + +################### +# Codespaces +################### +pythonenv3.8/ +.venv/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..cff2267 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +fail_fast: true +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-symlinks + - id: end-of-file-fixer + - id: trailing-whitespace diff --git a/CMakeLists.txt b/CMakeLists.txt index c2fa5fc..3df3929 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,11 +14,16 @@ set(CMAKE_CXX_STANDARD_REQUIRED TRUE) # Project configuration option(BUILD_TESTS OFF) option(VERBOSE_TESTING OFF) +option(MCMINI_USE_SCIP OFF) set(MCMINI_DIR "${CMAKE_SOURCE_DIR}") set(MCMINI_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include") set(MCMINI_CMAKE_MODULE_DIR "${CMAKE_SOURCE_DIR}/cmake") set(MCMINI_WITH_DMTCP OFF) +if(MCMINI_USE_SCIP) +find_package(scip) +endif() + # Project source files set(MCMINI_C_SRC src/common/exit.c @@ -49,6 +54,7 @@ set(MCMINI_CPP_SRC src/mcmini/model/transitions/process.cpp src/mcmini/model/transitions/thread.cpp src/mcmini/model/transitions/semaphore.cpp + src/mcmini/model_checking/reporter.cpp src/mcmini/model_checking/algorithms/classic_dpor.cpp src/mcmini/model_checking/algorithms/clock_vector.cpp src/mcmini/real_world/dmtcp_process_source.cpp @@ -117,6 +123,10 @@ target_compile_options(mcmini target_link_libraries(mcmini PUBLIC "${MCMINI_EXTRA_LINK_FLAGS}") +if (MCMINI_USE_SCIP) +target_link_libraries(mcmini PUBLIC scip) +target_compile_definitions(mcmini PUBLIC MCMINI_USE_SCIP) +endif() add_subdirectory(src/examples) add_subdirectory(test) diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/include/mcmini/coordinator/coordinator.hpp b/include/mcmini/coordinator/coordinator.hpp index e8a7f33..e5b2619 100644 --- a/include/mcmini/coordinator/coordinator.hpp +++ b/include/mcmini/coordinator/coordinator.hpp @@ -77,7 +77,7 @@ * aforementioned synchronization. */ class coordinator { - public: +public: /** * @brief Constructs a new coordinator which synchronizes processes. * @@ -90,6 +90,12 @@ class coordinator { * @param process_source a process source which can repeatedly produce * processes starting at state `initial_state`. The coordinator will * repeatedly create new processes from this source part of its exploration. + * @param assumes_linear_program_flow whether each thread executes a sequence + * of transitions that does not depend on the ordering of the execution of + * other thread operations. In other words, each thread can only become + * disabled, but will execute a fixed sequence of operations. This is the case + * in codes without control flow dependent on the interaction of other + * threads. * @invariant: A _critical_ invariant is that _process_source_ create new * processed that are modeled by _initial_state_. McMini model-checking * algorithms rely _solely_ on the model to make determinations about how to @@ -101,7 +107,8 @@ class coordinator { */ coordinator(model::program &&initial_state, model::transition_registry runtime_transition_mapping, - std::unique_ptr &&process_source); + std::unique_ptr &&process_source, + bool assumes_linear_program_flow = false); ~coordinator() = default; const model::program &get_current_program_model() const { @@ -163,7 +170,7 @@ class coordinator { */ void execute_runner(real_world::process::runner_id_t id); - private: +private: logging::logger logger = logging::logger("coord"); model::program current_program_model; model::transition_registry runtime_transition_mapping; @@ -171,7 +178,10 @@ class coordinator { std::unique_ptr process_source; std::unordered_map, model::state::objid_t> system_address_mapping; + std::vector> transition_cache; + bool assumes_linear_program_flow = false; +private: void assign_new_process_handle() { // NOTE: This is INTENTIONALLY split into two separate statements, i.e. // assiging `nullptr` and THEN calling `force_new_process()`. We want to diff --git a/include/mcmini/lib/log.h b/include/mcmini/lib/log.h index 9d1d570..4ca6933 100644 --- a/include/mcmini/lib/log.h +++ b/include/mcmini/lib/log.h @@ -4,7 +4,7 @@ #include #include -#define MCMINI_LOG_MINIMUM_LEVEL (MCMINI_LOG_DEBUG) +#define MCMINI_LOG_MINIMUM_LEVEL (MCMINI_LOG_DISABLE) enum log_level { MCMINI_LOG_VERBOSE, diff --git a/include/mcmini/log/logger.hpp b/include/mcmini/log/logger.hpp index 09f94e0..dc4d2ad 100644 --- a/include/mcmini/log/logger.hpp +++ b/include/mcmini/log/logger.hpp @@ -11,49 +11,47 @@ #include "mcmini/log/severity_level.hpp" #include "mcmini/model/program.hpp" -#define log_severity(logger, severity) \ +#define log_severity(logger, severity) \ logger.make_stream(__FILE__, __LINE__) << severity -#define log_very_verbose(logger) \ +#define log_very_verbose(logger) \ log_severity(logger, logging::severity_level::very_verbose) -#define log_verbose(logger) \ +#define log_verbose(logger) \ log_severity(logger, logging::severity_level::verbose) #define log_debug(logger) log_severity(logger, logging::severity_level::debug) #define log_info(logger) log_severity(logger, logging::severity_level::info) -#define log_unexpected(logger) \ +#define log_unexpected(logger) \ log_severity(logger, logging::severity_level::unexpected) #define log_error(logger) log_severity(logger, logging::severity_level::error) -#define log_critical(logger) \ +#define log_critical(logger) \ log_severity(logger, logging::severity_level::critical) #define log_abort(logger) log_severity(logger, logging::severity_level::abort) namespace logging { class logger { - public: +public: logger() = default; logger(const std::string &subsystem) : subsystem(subsystem) {} - public: - template - void set_instance(T *instance) { +public: + template void set_instance(T *instance) { std::stringstream strm; strm << "0x" << std::hex << reinterpret_cast(instance); this->instance = strm.str(); } - public: +public: struct stream { - public: + public: ~stream() { flush(); } - template - stream &operator<<(const T &value) { + template stream &operator<<(const T &value) { ostream << value; return *this; } - template - stream &operator<<(const std::unordered_set &set) { + template stream &operator<<(const std::unordered_set &set) { ostream << "["; - for (const T &t : set) ostream << t << ", "; + for (const T &t : set) + ostream << t << ", "; ostream << "]"; return *this; } @@ -72,7 +70,7 @@ class logger { return *this; } - private: + private: stream &operator=(stream &&) = default; stream(stream &&) = default; explicit stream(logger *log, const char *file = __FILE__, @@ -85,7 +83,7 @@ class logger { }; } - private: + private: logger *log; const char *file; int line; @@ -93,13 +91,12 @@ class logger { severity_level current_severity = severity_level::info; std::stringstream ostream; - private: + private: friend class logger; }; - public: - template - stream operator<<(const T &item) { +public: + template stream operator<<(const T &item) { return make_stream(__FILE__, __LINE__); } @@ -107,18 +104,18 @@ class logger { return logging::logger::stream(this, file, line); } - public: +public: inline void log_raw(const std::string &message, severity_level severity, const char *file = __FILE__, int line = __LINE__) { log_control::instance().log_raw(instance, subsystem, message, severity, file, line); } - private: +private: std::string instance; std::string subsystem; - private: +private: friend struct stream; }; -} // namespace logging +} // namespace logging diff --git a/include/mcmini/misc/ddt.hpp b/include/mcmini/misc/ddt.hpp index f8db347..e7500bb 100644 --- a/include/mcmini/misc/ddt.hpp +++ b/include/mcmini/misc/ddt.hpp @@ -155,20 +155,21 @@ struct double_dispatch_member_function_table 0) { if (internal_table.at(t1_type).count(t2_type) > 0) { - const auto& pair = internal_table.at(t1_type).at(t2_type); + const auto &pair = internal_table.at(t1_type).at(t2_type); return pair.first(t1, t2, pair.second, std::forward(args)...); } - } else if (interface_member_function_table.count(t1_type) > 0) { - const auto& pair = interface_member_function_table.at(t1_type); + } + if (interface_member_function_table.count(t1_type) > 0) { + const auto &pair = interface_member_function_table.at(t1_type); return pair.first(t1, t2, pair.second, std::forward(args)...); } else if (interface_member_function_table.count(t2_type) > 0) { - const auto& pair = interface_member_function_table.at(t2_type); + const auto &pair = interface_member_function_table.at(t2_type); // NOTE: t2 should come before t1 here since // `casting_function_interface_table` always casts its first argument return pair.first(t2, t1, pair.second, std::forward(args)...); @@ -176,12 +177,12 @@ struct double_dispatch_member_function_table 0) { if (internal_table.at(t1_type).count(t2_type) > 0) { - const auto& pair = internal_table.at(t1_type).at(t2_type); + const auto &pair = internal_table.at(t1_type).at(t2_type); return pair.first(t1, t2, pair.second, std::forward(args)...); } } diff --git a/include/mcmini/model/config.hpp b/include/mcmini/model/config.hpp index f492590..f581776 100644 --- a/include/mcmini/model/config.hpp +++ b/include/mcmini/model/config.hpp @@ -13,21 +13,94 @@ namespace model { struct config { /** * The maximum number of transitions that can be run - * by any single thread while running the model checker + * by any _single thread_ while running the model checker */ uint64_t max_thread_execution_depth; + /** + * The maximum number of transitions that can be contained in any given trace. + */ + uint64_t maximum_total_execution_depth = 1500; + /** * The trace id to stop the model checker at * to print the contents of the transition stack */ trid_t target_trace_id; + /** + * Whether or not exploration occurs using round robin scheduling (default is + * smallest tid first) + */ + bool use_round_robin_scheduling = false; + /** * Whether model checking should halt at the first encountered deadlock */ bool stop_at_first_deadlock = false; + /** + * Whether relinearization should occur before displying buggy traces. + * + * A relinearization `T'` of a trace `T` is a permutation `sigma_n` such that + * `happens_before_T(i, j) <--> happens_before_T'(sigma(i), sigma(j))`. + * Informally, it is a reordering of the transitions in the trace that + * produces the same final state (and hence the same bugs, crashes, etc). + * + * The interest in relinearization is that although round-robin exploration in + * theory should enable McMini to capture shallower bugs more readily, the + * tradeoff is less readable traces when they're produced because the user + * will frequently have to switch between different threads to analyze the + * trace. Frequent switching makes it challenging to have a global idea of the + * issue causing a bug, even for small traces. + * + * With this enabled, McMini will first reorder the transitions using a greedy + * approach as follows: + * + * 1. When given the choice, McMini will first choose transitions of the same + * thread ID as the thread ID of the last chosen transition. + * 2. When McMini is forced by the happens-before relation to order a + * transition of a different thread ID, among the possible choices it chooses + * the smallest. + */ + bool relinearize_traces = false; + + /** + * Whether or not traces are relinearized optimally. Implies + * `relinearize_traces`. + * + * By default, relinearizations are created using a greedy algorithm by + * grouping together, as long as possible, transitions of the same thread ID + * subject to the happens-before relation over them. However, a + * relinearization produced using the greedy method is not guaranteed to + * minimize the number of "context switches" a user has to make. That is, + * there may exist a better linearization obeying the same happens-before + * relation but with a fewer number of changes between thread IDs. + * + * The optimization problem can be formulated as follows: + * + * Consider a colored DAG, G, with vertices V and edges E and a coloring + * function f: V —> C (where C is a finite set of colors). A linearization L + * of the vertices of G is a sequence of vertices v_I such + * that the order of v_I obeys the dag: a node ‘u’ appears before any node + * with which a directed edge from that node to any other node (formally u < v + * in L iff (u,v) in E). + * + * A colored linearization is the sequence of colors c_1, c_2, … of a + * linearization v_1, v_2, …, where c_i = f(v_i). Let h: L —> N + * denote the number of “inversions” in a linearization L. + * A linearization L has an inversion (v_i, v_i+1) iff f(v_i) != f(v_i+1). Let + * Inv: L -> Nat denote the number of inversions in a linearization L. Then + * MinInversions is the problem + * + * min (Inv L) where L is a linearization of G. + * + * The problem can be modeled as an instance of the Sequential Ordering + * Problem. Since in general this problem is challenging to solve, linearizing + * large traces may become impractical. + */ + bool use_optimal_linearization = false; + /** * Informs McMini that the target executable should be run under DMTCP with * `libmcmini.so` configured in record mode. @@ -41,6 +114,17 @@ struct config { */ bool use_multithreaded_fork = false; + /** + * Whether to print every trace, or one those that cause a bug + */ + bool verbose = false; + + /** + * Whether the STDOUT/STDERR of the program is sent to /dev/null during + * execution + */ + bool quiet_program_output = false; + /** * The time between consecutive checkpoint images when `libmcmini.so` is * running in record mode. @@ -63,4 +147,4 @@ struct config { // file path is provided (TODO). logging::severity_level global_severity_level = logging::severity_level::info; }; -} // namespace model +} // namespace model diff --git a/include/mcmini/model/pending_transitions.hpp b/include/mcmini/model/pending_transitions.hpp index 668db87..ec65268 100644 --- a/include/mcmini/model/pending_transitions.hpp +++ b/include/mcmini/model/pending_transitions.hpp @@ -43,6 +43,7 @@ struct pending_transitions final { } auto cend() -> decltype(_contents.cend()) const { return _contents.cend(); } size_t size() const { return this->_contents.size(); } + bool empty() const { return this->_contents.empty(); } /** * @brief Returns the transition mapped to id `id`, or `nullptr` if no such * runner has been mapped to an id. diff --git a/include/mcmini/model/transitions/thread/thread_exit.hpp b/include/mcmini/model/transitions/thread/thread_exit.hpp index 7509ff2..daf8576 100644 --- a/include/mcmini/model/transitions/thread/thread_exit.hpp +++ b/include/mcmini/model/transitions/thread/thread_exit.hpp @@ -8,22 +8,23 @@ namespace model { namespace transitions { struct thread_exit : public model::transition { - public: +public: thread_exit(state::runner_id_t executor) : transition(executor) {} ~thread_exit() = default; - status modify(model::mutable_state& s) const override { + status modify(model::mutable_state &s) const override { using namespace model::objects; - auto* thread_state = s.get_state_of_runner(executor); - if (!thread_state->is_running() || executor == RID_MAIN_THREAD) { + auto *thread_state = s.get_state_of_runner(executor); + if (!thread_state->is_running()) { return status::disabled; } s.add_state_for_runner(executor, new thread(thread::exited)); return status::exists; } + bool depends(const model::transition *t) const { return false; } std::string to_string() const override { return "exits"; } }; -} // namespace transitions -} // namespace model +} // namespace transitions +} // namespace model diff --git a/include/mcmini/model/transitions/thread/thread_start.hpp b/include/mcmini/model/transitions/thread/thread_start.hpp index db9821e..ed4f1fa 100644 --- a/include/mcmini/model/transitions/thread/thread_start.hpp +++ b/include/mcmini/model/transitions/thread/thread_start.hpp @@ -7,19 +7,20 @@ namespace model { namespace transitions { struct thread_start : public model::transition { - public: +public: thread_start(state::runner_id_t executor) : transition(executor) {} ~thread_start() = default; - status modify(model::mutable_state& s) const override { + status modify(model::mutable_state &s) const override { // No modification necessary: we simply move into the next state using namespace model::objects; - auto* thread_state = s.get_state_of_runner(executor); + auto *thread_state = s.get_state_of_runner(executor); return thread_state->is_embryo() ? status::disabled : status::exists; } + bool depends(const model::transition *t) const { return false; } std::string to_string() const override { return "starts"; } }; -} // namespace transitions -} // namespace model +} // namespace transitions +} // namespace model diff --git a/include/mcmini/model/transitions/transition_sequence.hpp b/include/mcmini/model/transitions/transition_sequence.hpp index ad2c522..158ee2e 100644 --- a/include/mcmini/model/transitions/transition_sequence.hpp +++ b/include/mcmini/model/transitions/transition_sequence.hpp @@ -20,10 +20,11 @@ namespace model { * the enabled-ness of transitions in the sequence. */ class transition_sequence final { - private: - std::vector contents; +private: + std::vector contents; + std::unordered_map per_runner_depth; - public: +public: using index = size_t; transition_sequence() = default; ~transition_sequence(); @@ -35,11 +36,21 @@ class transition_sequence final { bool empty() const { return contents.empty(); } size_t count() const { return contents.size(); } - const transition* at(size_t i) const { return contents.at(i); } - const transition* back() const { return contents.back(); } + size_t count(runner_id_t tid) const { + if (per_runner_depth.find(tid) != per_runner_depth.end()) { + return per_runner_depth.at(tid); + } else { + return 0; + } + } + const transition *at(size_t i) const { return contents.at(i); } + const transition *back() const { return contents.back(); } std::unique_ptr extract_at(size_t i); - void push(const transition* t) { contents.push_back(t); } + void push(const transition *t) { + contents.push_back(t); + per_runner_depth[t->get_executor()]++; + } void consume_into_subsequence(uint32_t depth); }; -} // namespace model +} // namespace model diff --git a/include/mcmini/model_checking/algorithm.hpp b/include/mcmini/model_checking/algorithm.hpp index 5fce879..df5bc65 100644 --- a/include/mcmini/model_checking/algorithm.hpp +++ b/include/mcmini/model_checking/algorithm.hpp @@ -1,7 +1,5 @@ #pragma once -#include - #include "mcmini/coordinator/coordinator.hpp" #include "mcmini/model/exception.hpp" #include "mcmini/model/program.hpp" @@ -15,24 +13,37 @@ namespace model_checking { * the correctness of a program modeled under McMini. */ class algorithm { - public: +public: + enum class exploration_policy : uint { round_robin, smallest_first }; + + struct context { + virtual const model::program &get_model() const = 0; + virtual std::vector + linearize_lowest_first() const = 0; + virtual std::vector + linearize_optimal() const = 0; + std::vector linearize_trace(bool optimal) const { + return optimal ? linearize_optimal() : linearize_lowest_first(); + } + }; + struct callbacks { - public: - callbacks() = default; - std::function crash; - std::function deadlock; - std::function data_race; - std::function unknown_error; - std::function trace_completed; - std::function - abnormal_termination; - std::function - nonzero_exit_code; - std::function - undefined_behavior; + virtual void crash(const context &, const stats &) const {} + virtual void deadlock(const context &, const stats &) const {} + virtual void data_race(const context &, const stats &) const {} + virtual void trace_completed(const context &, const stats &) const {} + + virtual void + abnormal_termination(const context &, const stats &, + const real_world::process::termination_error &) const { + } + virtual void nonzero_exit_code( + const context &, const stats &, + const real_world::process::nonzero_exit_code_error &) const {} + + virtual void + undefined_behavior(const context &, const stats &, + const model::undefined_behavior_exception &) const {} }; /** @@ -72,4 +83,4 @@ class algorithm { } }; -}; // namespace model_checking +}; // namespace model_checking diff --git a/include/mcmini/model_checking/algorithms/classic_dpor.hpp b/include/mcmini/model_checking/algorithms/classic_dpor.hpp index 931e001..7eba8e7 100644 --- a/include/mcmini/model_checking/algorithms/classic_dpor.hpp +++ b/include/mcmini/model_checking/algorithms/classic_dpor.hpp @@ -12,7 +12,7 @@ namespace model_checking { * algorithm of Flanagan and Godefroid (2005). */ class classic_dpor final : public algorithm { - public: +public: using dependency_relation_type = double_dispatch_member_function_table; @@ -29,20 +29,33 @@ class classic_dpor final : public algorithm { static dependency_relation_type default_dependencies(); static coenabled_relation_type default_coenabledness(); - struct configuration { + struct config { + public: + config() = default; + config(const model::config &c) + : maximum_total_execution_depth(c.maximum_total_execution_depth), + stop_at_first_deadlock(c.stop_at_first_deadlock), + policy(c.use_round_robin_scheduling + ? exploration_policy::round_robin + : exploration_policy::smallest_first) {} + + public: dependency_relation_type dependency_relation = classic_dpor::default_dependencies(); coenabled_relation_type coenabled_relation = classic_dpor::default_coenabledness(); uint32_t maximum_total_execution_depth = 1500; + bool stop_at_first_deadlock = false; bool assumes_linear_program_flow = false; + exploration_policy policy = exploration_policy::smallest_first; }; classic_dpor() = default; - classic_dpor(configuration config) : config(std::move(config)) {} + classic_dpor(config config) : config(std::move(config)) {} + classic_dpor(const model::config &config) : config(config) {} - private: - configuration config; +private: + config config; bool are_dependent(const model::transition &t1, const model::transition &t2) const; @@ -59,13 +72,6 @@ class classic_dpor final : public algorithm { // the DPOR algorithm and are called at specific points in time! struct dpor_context; - - bool happens_before(const dpor_context &, size_t i, size_t j) const; - bool happens_before_thread(const dpor_context &, size_t i, - runner_id_t p) const; - bool threads_race_after(const dpor_context &context, size_t i, runner_id_t q, - runner_id_t p) const; - clock_vector accumulate_max_clock_vector_against(const model::transition &, const dpor_context &) const; @@ -79,4 +85,4 @@ class classic_dpor final : public algorithm { runner_id_t p); }; -} // namespace model_checking +} // namespace model_checking diff --git a/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp b/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp index 8e41fb5..ec3e3d0 100644 --- a/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp +++ b/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp @@ -219,9 +219,9 @@ struct stack_item final { return backtrack_thread; } - clock_vector get_clock_vector() const { return this->cv; } - const std::array - &get_per_runner_clocks() const { + const clock_vector &get_clock_vector() const { return this->cv; } + const std::array & + get_per_runner_clocks() const { return this->per_runner_clocks; } runner_id_t get_first_enabled_runner() const { @@ -231,6 +231,9 @@ struct stack_item final { } return r; } + const bool is_enabled(runner_id_t rid) { + return this->enabled_runners.count(rid) > 0; + } const std::unordered_set &get_enabled_runners() const { return this->enabled_runners; } diff --git a/include/mcmini/model_checking/reporter.hpp b/include/mcmini/model_checking/reporter.hpp new file mode 100644 index 0000000..954d850 --- /dev/null +++ b/include/mcmini/model_checking/reporter.hpp @@ -0,0 +1,40 @@ +#pragma once +#include "mcmini/model/config.hpp" +#include "mcmini/model/exception.hpp" +#include "mcmini/model_checking/algorithm.hpp" + +namespace model_checking { +class reporter : public algorithm::callbacks { +private: + const bool verbose; + const bool relinearize_traces; + const bool use_optimal_linearization; + +private: + void dump_relinearized_trace(std::ostream &, const algorithm::context &, + const stats &) const; + +public: + reporter() = default; + reporter(const model::config &c) + : verbose(c.verbose), + relinearize_traces(c.relinearize_traces || c.use_optimal_linearization), + use_optimal_linearization(c.use_optimal_linearization) {} + +public: + // void crash(const algorithm::context &, const stats &) const override; + void deadlock(const algorithm::context &, const stats &) const override; + // void data_race(const algorithm::context &, const stats &) const override; + void trace_completed(const algorithm::context &, + const stats &) const override; + void abnormal_termination( + const algorithm::context &, const stats &, + const real_world::process::termination_error &) const override; + // void nonzero_exit_code( + // const algorithm::context &, const stats &, + // const real_world::process::nonzero_exit_code_error &) const override; + void undefined_behavior( + const algorithm::context &, const stats &, + const model::undefined_behavior_exception &) const override; +}; +} // namespace model_checking \ No newline at end of file diff --git a/include/mcmini/signal.hpp b/include/mcmini/signal.hpp index 27f3491..5f77c66 100644 --- a/include/mcmini/signal.hpp +++ b/include/mcmini/signal.hpp @@ -1,22 +1,24 @@ #pragma once -#include - #include #include #include #include +#include #include +#include "mcmini/log/logger.hpp" + extern "C" { #include "mcmini/lib/sig.h" } using signo_t = int; extern const std::unordered_map sig_to_str; +extern logging::logger signal_logger; struct signal_tracker { - private: +private: static signal_tracker _instance; constexpr signal_tracker() = default; constexpr static size_t MAX_SIGNAL_TYPES = NSIG - 1; @@ -25,7 +27,7 @@ struct signal_tracker { #endif std::atomic_uint32_t flags[MAX_SIGNAL_TYPES] = {}; - public: +public: static sem_t *current_sem; struct interrupted_error; static signal_tracker &instance(); diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt index 5c056b1..ab761d9 100644 --- a/src/examples/CMakeLists.txt +++ b/src/examples/CMakeLists.txt @@ -4,8 +4,9 @@ add_executable(cv-test cv-test.c) add_executable(deadly-embrace deadly-embrace.c) add_executable(fifo-example fifo.cpp) add_executable(producer-consumer producer-consumer.c) +add_executable(test test.cpp) target_link_libraries(hello-world PUBLIC -pthread) target_link_libraries(cv-hello-world PUBLIC -pthread) target_link_libraries(cv-test PUBLIC -pthread) target_link_libraries(deadly-embrace PUBLIC -pthread) -target_link_libraries(producer-consumer PUBLIC -pthread) +target_link_libraries(producer-consumer PUBLIC -pthread) \ No newline at end of file diff --git a/src/examples/test.cpp b/src/examples/test.cpp new file mode 100644 index 0000000..76c0ae8 --- /dev/null +++ b/src/examples/test.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include + +bool DEBUG_MODE = true; +struct PhilosopherForks { + int id; + std::mutex &left_fork; + std::mutex &right_fork; +}; + +void philosopher_doit(int id, std::mutex &left_fork, std::mutex &right_fork) { + // Simulate thinking/arrival + // std::this_thread::sleep_for(std::chrono::seconds(1)); + + // Naive acquisition: pick up left then right (leads to deadlock) + left_fork.lock(); + if (DEBUG_MODE) + std::cout << "Philosopher " << id << " picked up left fork.\n"; + + // std::this_thread::sleep_for(std::chrono::seconds(1)); + + right_fork.lock(); + if (DEBUG_MODE) + std::cout << "Philosopher " << id + << " picked up right fork and is eating.\n"; + + // Simulate eating + // std::this_thread::sleep_for(std::chrono::seconds(2)); + + // Release forks + right_fork.unlock(); + left_fork.unlock(); + + if (DEBUG_MODE) + std::cout << "Philosopher " << id + << " finished eating and put down forks.\n"; +} + +int main() { + const int NUM_PHILOSOPHERS = 3; + + // std::mutex is not copyable, so we use a vector of unique_ptrs + // or a fixed-size array to keep them stable in memory. + std::vector forks(NUM_PHILOSOPHERS); + std::vector threads; + + for (int i = 0; i < NUM_PHILOSOPHERS; ++i) { + // We pass references to the mutexes in the vector + threads.emplace_back(philosopher_doit, i, std::ref(forks[i]), + std::ref(forks[(i + 1) % NUM_PHILOSOPHERS])); + } + + // Wait for all threads to complete + for (auto &t : threads) { + if (t.joinable()) { + t.join(); + } + } + + return 0; +} diff --git a/src/lib/template/loop.c b/src/lib/template/loop.c index c37dd55..b574322 100644 --- a/src/lib/template/loop.c +++ b/src/lib/template/loop.c @@ -75,7 +75,6 @@ int rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t *info) { void mc_template_receive_sigchld(int sig, siginfo_t *info, void *) { assert(global_model_checker_pid != NO_DEFINED_MCMINI_PID); - printf("signalling!!\n"); fsync(STDOUT_FILENO); int status; bool signal_mcmini = false; @@ -92,12 +91,11 @@ void mc_template_receive_sigchld(int sig, siginfo_t *info, void *) { } else if (WIFSIGNALED(status)) { int signo = WTERMSIG(status); - printf("signaled %d", status); + // printf("signaled %d", status); fsync(STDOUT_FILENO); signal_mcmini = is_bad_signal(signo); } if (signal_mcmini) { - printf("signalling McMini!!\n"); fsync(STDOUT_FILENO); // TODO: We can use `sigqueue(3)` to pass the exit status of // the child to the McMini process diff --git a/src/mcmini/coordinator/coordinator.cpp b/src/mcmini/coordinator/coordinator.cpp index 2b8146d..4b0f003 100644 --- a/src/mcmini/coordinator/coordinator.cpp +++ b/src/mcmini/coordinator/coordinator.cpp @@ -25,14 +25,28 @@ using namespace real_world; coordinator::coordinator( model::program &&initial_state, model::transition_registry runtime_transition_mapping, - std::unique_ptr &&process_source) + std::unique_ptr &&process_source, + bool assumes_linear_program_flow) : current_program_model(std::move(initial_state)), runtime_transition_mapping(std::move(runtime_transition_mapping)), - process_source(std::move(process_source)) { - this->assign_new_process_handle(); + process_source(std::move(process_source)), + assumes_linear_program_flow(assumes_linear_program_flow) { + if (!assumes_linear_program_flow) this->assign_new_process_handle(); } void coordinator::execute_runner(process::runner_id_t runner_id) { + if (assumes_linear_program_flow) { + // this->current_program_model.get_trace().count(runner_id); + + // TODO: Check if in transition cache --> otherwise fallback + // and create a new process before + + // else { + // // If not in the cache, create a new process and restart + // this->return_to_depth(this->current_program_model.get_trace().count()); + // } + } + if (!current_process_handle) { throw real_world::process::execution_error( "Failed to execute runner with id \"" + std::to_string(runner_id) + diff --git a/src/mcmini/mcmini.cpp b/src/mcmini/mcmini.cpp index 025c28e..2740f3e 100644 --- a/src/mcmini/mcmini.cpp +++ b/src/mcmini/mcmini.cpp @@ -11,6 +11,7 @@ #include "mcmini/model/transitions/thread/thread_exit.hpp" #include "mcmini/model_checking/algorithm.hpp" #include "mcmini/model_checking/algorithms/classic_dpor.hpp" +#include "mcmini/model_checking/reporter.hpp" #include "mcmini/real_world/fifo.hpp" #include "mcmini/real_world/process/dmtcp_process_source.hpp" #include "mcmini/real_world/process/multithreaded_fork_process_source.hpp" @@ -43,10 +44,12 @@ using namespace model_checking; using namespace objects; using namespace real_world; -visible_object_state* translate_recorded_object_to_model( - const ::visible_object& recorded_object, +logging::logger setup_logger("mcmini"); + +visible_object_state *translate_recorded_object_to_model( + const ::visible_object &recorded_object, const std::unordered_map< - void*, std::vector>> + void *, std::vector>> cv_waiting_threads) { // TODO: A function table would be slightly better, but this works perfectly // fine too. @@ -54,8 +57,8 @@ visible_object_state* translate_recorded_object_to_model( case MUTEX: { auto mutex_state = static_cast(recorded_object.mut_state); - pthread_mutex_t* mutex_location = - (pthread_mutex_t*)recorded_object.location; + pthread_mutex_t *mutex_location = + (pthread_mutex_t *)recorded_object.location; return new objects::mutex(mutex_state, mutex_location); } case CONDITION_VARIABLE: { @@ -65,7 +68,7 @@ visible_object_state* translate_recorded_object_to_model( runner_id_t interacting_thread = recorded_object.cond_state.interacting_thread; - pthread_mutex_t* associated_mutex = + pthread_mutex_t *associated_mutex = recorded_object.cond_state.associated_mutex; int count = recorded_object.cond_state.count; // get waiting threads from the map @@ -96,8 +99,8 @@ visible_object_state* translate_recorded_object_to_model( } } -runner_state* translate_recorded_runner_to_model( - const ::visible_object& recorded_object) { +runner_state *translate_recorded_runner_to_model( + const ::visible_object &recorded_object) { switch (recorded_object.type) { case THREAD: { return new objects::thread(recorded_object.thrd_state.status); @@ -108,99 +111,22 @@ runner_state* translate_recorded_runner_to_model( } } -void finished_trace_classic_dpor(const coordinator& c, const stats& stats) { - std::stringstream ss; - const auto& program_model = c.get_current_program_model(); - ss << "TRACE " << stats.trace_id << "\n"; - for (const auto& t : program_model.get_trace()) { - ss << "thread " << t->get_executor() << ": " << t->to_string() << "\n"; - } - ss << "\nNEXT THREAD OPERATIONS\n"; - for (const auto& tpair : program_model.get_pending_transitions()) { - ss << "thread " << tpair.first << ": " << tpair.second->to_string() << "\n"; - } - std::cout << ss.str(); - std::cout.flush(); -} - -void found_undefined_behavior(const coordinator& c, const stats& stats, - const undefined_behavior_exception& ub) { - std::cerr << "UNDEFINED BEHAVIOR:\n" << ub.what() << std::endl; - finished_trace_classic_dpor(c, stats); -} - -void found_abnormal_termination( - const coordinator& c, const stats& stats, - const real_world::process::termination_error& ub) { - std::cerr << "Abnormally Termination (signo: " << ub.signo - << ", signal: " << sig_to_str.at(ub.signo) << "):\n" - << ub.what() << std::endl; - - std::stringstream ss; - const auto& program_model = c.get_current_program_model(); - ss << "TRACE " << stats.trace_id << "\n"; - for (const auto& t : program_model.get_trace()) { - ss << "thread " << t->get_executor() << ": " << t->to_string() << "\n"; - } - const transition* terminator = - program_model.get_pending_transition_for(ub.culprit); - ss << "thread " << terminator->get_executor() << ": " - << terminator->to_string() << "\n"; - - ss << "\nNEXT THREAD OPERATIONS\n"; - for (const auto& tpair : program_model.get_pending_transitions()) { - if (tpair.first == terminator->get_executor()) { - ss << "thread " << tpair.first << ": executing" - << "\n"; - } else { - ss << "thread " << tpair.first << ": " << tpair.second->to_string() - << "\n"; - } - } - ss << stats.total_transitions + 1 << " total transitions executed" - << "\n"; - std::cout << ss.str(); - std::cout.flush(); -} - -void found_deadlock(const coordinator& c, const stats& stats) { - std::cerr << "DEADLOCK" << std::endl; - std::stringstream ss; - const auto& program_model = c.get_current_program_model(); - for (const auto& t : program_model.get_trace()) { - ss << "thread " << t->get_executor() << ": " << t->to_string() << "\n"; - } - ss << "\nNEXT THREAD OPERATIONS\n"; - for (const auto& tpair : program_model.get_pending_transitions()) { - ss << "thread " << tpair.first << ": " << tpair.second->to_string() << "\n"; - } - std::cout << ss.str(); - std::cout.flush(); -} - -void do_model_checking(const config& config) { +void do_model_checking(const config &config) { algorithm::callbacks c; target target_program(config.target_executable, config.target_executable_args); + target_program.set_quiet(config.quiet_program_output); coordinator coordinator(program::starting_from_main(), transition_registry::default_registry(), make_unique(target_program)); - std::cerr << "\n\n**************** INTIAL STATE *********************\n\n"; - coordinator.get_current_program_model().dump_state(std::cerr); - std::cerr << "\n\n**************** INTIAL STATE *********************\n\n"; - std::cerr.flush(); - - model_checking::classic_dpor classic_dpor_checker; - c.trace_completed = &finished_trace_classic_dpor; - c.deadlock = &found_deadlock; - c.undefined_behavior = &found_undefined_behavior; - c.abnormal_termination = &found_abnormal_termination; - classic_dpor_checker.verify_using(coordinator, c); + model_checking::reporter reporter(config); + model_checking::classic_dpor classic_dpor_checker(config); + classic_dpor_checker.verify_using(coordinator, reporter); std::cout << "Model checking completed!" << std::endl; } -void do_model_checking_from_dmtcp_ckpt_file(const config& config) { - volatile mcmini_shm_file* rw_region = +void do_model_checking_from_dmtcp_ckpt_file(const config &config) { + volatile mcmini_shm_file *rw_region = xpc_resources::get_instance().get_rw_region()->as(); std::unique_ptr dmtcp_template_handle; @@ -217,7 +143,6 @@ void do_model_checking_from_dmtcp_ckpt_file(const config& config) { // Make sure that `dmtcp_restart` has executed and that the template // process is ready for execution; otherwise, the state restoration will not // work as expected. - algorithm::callbacks c; transition_registry tr = transition_registry::default_registry(); coordinator coordinator(program(), tr, std::move(dmtcp_template_handle)); { @@ -227,7 +152,7 @@ void do_model_checking_from_dmtcp_ckpt_file(const config& config) { ::visible_object current_obj; std::vector<::visible_object> recorded_threads; std::unordered_map< - void*, std::vector>> + void *, std::vector>> cv_waiting_threads; while (fifo.read(¤t_obj) && current_obj.type != UNKNOWN) { if (current_obj.type == THREAD) { @@ -246,17 +171,17 @@ void do_model_checking_from_dmtcp_ckpt_file(const config& config) { } std::sort(recorded_threads.begin(), recorded_threads.end(), - [](const ::visible_object& lhs, const ::visible_object& rhs) { + [](const ::visible_object &lhs, const ::visible_object &rhs) { return lhs.thrd_state.id < rhs.thrd_state.id; }); - for (const ::visible_object& recorded_thread : recorded_threads) { + for (const ::visible_object &recorded_thread : recorded_threads) { recorder.observe_runner( - (void*)recorded_thread.thrd_state.pthread_desc, + (void *)recorded_thread.thrd_state.pthread_desc, translate_recorded_runner_to_model(recorded_thread)); } - for (const ::visible_object& recorded_thread : recorded_threads) { + for (const ::visible_object &recorded_thread : recorded_threads) { // Translates from what each user space thread recorded as its next // transition. This happens _after_ DMTCP has restarted the checkpoint // image but _before_ the template thread told the McMini process (i.e. @@ -265,11 +190,11 @@ void do_model_checking_from_dmtcp_ckpt_file(const config& config) { // of "during the RECORD phase of `libmcmini.so`") the next transition // it would have run had McMini not just now intervened. runner_id_t recorded_id = recorded_thread.thrd_state.id; - const transition* next_transition = nullptr; + const transition *next_transition = nullptr; switch (recorded_thread.thrd_state.status) { case ALIVE: { - volatile runner_mailbox* mb = &rw_region->mailboxes[recorded_id]; + volatile runner_mailbox *mb = &rw_region->mailboxes[recorded_id]; transition_registry::transition_discovery_callback callback = tr.get_callback_for(mb->type); if (!callback) { @@ -314,18 +239,15 @@ void do_model_checking_from_dmtcp_ckpt_file(const config& config) { std::cerr << "\n\n**************** INTIAL STATE *********************\n\n"; std::cerr.flush(); - model_checking::classic_dpor classic_dpor_checker; - c.trace_completed = &finished_trace_classic_dpor; - c.undefined_behavior = &found_undefined_behavior; - c.deadlock = &found_deadlock; - c.abnormal_termination = &found_abnormal_termination; - classic_dpor_checker.verify_using(coordinator, c); + model_checking::reporter reporter(config); + model_checking::classic_dpor classic_dpor_checker(config); + classic_dpor_checker.verify_using(coordinator, reporter); std::cerr << "Deep debugging completed!" << std::endl; } #include "mcmini/log/logger.hpp" -void do_recording(const config& config) { +void do_recording(const config &config) { char dir[PATH_MAX]; // FIXME: This depends on mcmini starting in root dir of git repo. std::string libmcini_dir = getcwd(dir, sizeof(dir)) ? dir : "PATH_TOO_LONG"; @@ -338,7 +260,7 @@ void do_recording(const config& config) { dmtcp_launch_args.push_back(libmcmini_path); dmtcp_launch_args.push_back("--modify-env"); dmtcp_launch_args.push_back(config.target_executable); - for (const std::string& target_arg : config.target_executable_args) + for (const std::string &target_arg : config.target_executable_args) dmtcp_launch_args.push_back(target_arg); real_world::target target_program("dmtcp_launch", dmtcp_launch_args); std::cout << "Recording: " << target_program << std::endl; @@ -348,13 +270,13 @@ void do_recording(const config& config) { std::string find_first_ckpt_file_in_cwd() { try { // Open the current directory - DIR* dir = opendir("."); + DIR *dir = opendir("."); if (dir == nullptr) { perror("opendir"); return ""; } - struct dirent* entry; + struct dirent *entry; while ((entry = readdir(dir)) != nullptr) { // Check if the entry is a regular file and has the .foo extension if (entry->d_type == DT_REG) { // DT_REG indicates a regular file @@ -372,19 +294,19 @@ std::string find_first_ckpt_file_in_cwd() { << std::endl; closedir(dir); return ""; - } catch (const std::exception& e) { + } catch (const std::exception &e) { std::cerr << "Error: " << e.what() << std::endl; return ""; } } -int main_cpp(int argc, const char** argv) { +int main_cpp(int argc, const char **argv) { model::config mcmini_config; - if (const char* env_p = std::getenv("MCMINI_LOG_LEVEL")) + if (const char *env_p = std::getenv("MCMINI_LOG_LEVEL")) mcmini_config.global_severity_level = logging::parse_severity(env_p); - const char** cur_arg = &argv[1]; + const char **cur_arg = &argv[1]; if (argc == 1) { cur_arg[0] = "--help"; cur_arg[1] = NULL; @@ -397,18 +319,41 @@ int main_cpp(int argc, const char** argv) { mcmini_config.max_thread_execution_depth = strtoul(cur_arg[1], nullptr, 10); - char* endptr; + char *endptr; if (strtol(cur_arg[1], &endptr, 10) == 0 || endptr[0] != '\0') { fprintf(stderr, "%s: illegal value\n", "--max-depth-per-thread"); exit(1); } cur_arg += 2; + } else if (strcmp(cur_arg[0], "--max-depth-per-trace") == 0 || + strcmp(cur_arg[0], "-M") == 0) { + mcmini_config.maximum_total_execution_depth = + strtoul(cur_arg[1], nullptr, 10); + + char *endptr; + if (strtol(cur_arg[1], &endptr, 10) == 0 || endptr[0] != '\0') { + fprintf(stderr, "%s: illegal value\n", "--max-depth-per-trace"); + exit(1); + } + cur_arg += 2; } else if (strcmp(cur_arg[0], "--interval") == 0 || strcmp(cur_arg[0], "-i") == 0) { mcmini_config.record_target_executable_only = true; mcmini_config.checkpoint_period = std::chrono::seconds(strtoul(cur_arg[1], nullptr, 10)); cur_arg += 2; + } else if ((strcmp(cur_arg[0], "--round-robin") == 0) || + strcmp(cur_arg[0], "-rr") == 0) { + mcmini_config.use_round_robin_scheduling = true; + cur_arg += 1; + } else if ((strcmp(cur_arg[0], "--optimal-relinearization") == 0) || + strcmp(cur_arg[0], "-orelin") == 0) { + mcmini_config.use_optimal_linearization = true; + cur_arg += 1; + } else if ((strcmp(cur_arg[0], "--relinearize") == 0) || + strcmp(cur_arg[0], "-relin") == 0) { + mcmini_config.relinearize_traces = true; + cur_arg += 1; } else if (strcmp(cur_arg[0], "--from-checkpoint") == 0 || strcmp(cur_arg[0], "-ckpt") == 0) { mcmini_config.checkpoint_file = cur_arg[1]; @@ -424,6 +369,14 @@ int main_cpp(int argc, const char** argv) { strcmp(cur_arg[0], "-mtf") == 0) { mcmini_config.use_multithreaded_fork = true; cur_arg++; + } else if (strcmp(cur_arg[0], "--verbose") == 0 || + strcmp(cur_arg[0], "-v") == 0) { + mcmini_config.verbose = true; + cur_arg++; + } else if (strcmp(cur_arg[0], "--quiet-program-output") == 0 || + strcmp(cur_arg[0], "-q") == 0) { + mcmini_config.quiet_program_output = true; + cur_arg++; } else if (cur_arg[0][1] == 'm' && isdigit(cur_arg[0][2])) { mcmini_config.max_thread_execution_depth = strtoul(cur_arg[1], nullptr, 10); @@ -436,7 +389,7 @@ int main_cpp(int argc, const char** argv) { } else if (strcmp(cur_arg[0], "--print-at-traceId") == 0 || strcmp(cur_arg[0], "-p") == 0) { mcmini_config.target_trace_id = strtoul(cur_arg[1], nullptr, 10); - char* endptr; + char *endptr; if (strtol(cur_arg[1], &endptr, 10) == 0 || endptr[0] != '\0') { fprintf(stderr, "%s: illegal value\n", "--print-at-traceId"); exit(1); @@ -453,8 +406,12 @@ int main_cpp(int argc, const char** argv) { " [--record|-r ] \n" " [--from-checkpoint ] [--multithreaded-fork] \n" " [--max-depth-per-thread|-m ]\n" + " [--max-depth-per-trace|-M ]\n" " [--first-deadlock|--first|-f]\n" + " [--round-robin|-rr]\n" " [--log-level|-log ]\n" + " [--verbose|-v]\n" + " [--quiet-program-output|-q]\n" " [--help|-h]\n" " target_executable\n"); exit(1); @@ -503,10 +460,10 @@ int main_cpp(int argc, const char** argv) { return EXIT_SUCCESS; } -int main(int argc, const char** argv) { +int main(int argc, const char **argv) { try { return main_cpp(argc, argv); - } catch (const std::exception& e) { + } catch (const std::exception &e) { std::cerr << "ERROR: " << e.what() << std::endl; return EXIT_FAILURE; } catch (...) { diff --git a/src/mcmini/model/cond_var_arbitrary_policy.cpp b/src/mcmini/model/cond_var_arbitrary_policy.cpp index f4f99f3..e73cfb3 100644 --- a/src/mcmini/model/cond_var_arbitrary_policy.cpp +++ b/src/mcmini/model/cond_var_arbitrary_policy.cpp @@ -1,56 +1,56 @@ #include "mcmini/misc/cond/cond_var_arbitrary_policy.hpp" + #include #include -ConditionVariablePolicy* -ConditionVariableArbitraryPolicy::clone() const -{ +ConditionVariablePolicy* ConditionVariableArbitraryPolicy::clone() const { return new ConditionVariableArbitraryPolicy(*this); } -void -ConditionVariableArbitraryPolicy::receive_signal_message() -{ +void ConditionVariableArbitraryPolicy::receive_signal_message() { if (!this->wait_queue.empty()) { this->wake_groups.push_back( - WakeGroup(this->wait_queue.begin(), this->wait_queue.end())); + WakeGroup(this->wait_queue.begin(), this->wait_queue.end())); } } -bool -ConditionVariableArbitraryPolicy::has_waiters() const -{ - return ! this->wait_queue.empty(); +bool ConditionVariableArbitraryPolicy::has_waiters() const { + return !this->wait_queue.empty(); } -std::deque ConditionVariableArbitraryPolicy::return_wait_queue() const { +std::deque ConditionVariableArbitraryPolicy::return_wait_queue() + const { return this->wait_queue; } -std::vector ConditionVariableArbitraryPolicy::return_wake_groups() const { +std::vector ConditionVariableArbitraryPolicy::return_wake_groups() + const { return this->wake_groups; } -void ConditionVariableArbitraryPolicy::add_to_wake_groups(const std::vector& threads) { +void ConditionVariableArbitraryPolicy::add_to_wake_groups( + const std::vector& threads) { if (!threads.empty()) { this->wake_groups.push_back(WakeGroup(threads.begin(), threads.end())); } } // Add thread with specific state -void ConditionVariableArbitraryPolicy::add_waiter_with_state(runner_id_t tid, condition_variable_status state) { +void ConditionVariableArbitraryPolicy::add_waiter_with_state( + runner_id_t tid, condition_variable_status state) { add_waiter(tid); this->threads_with_states[tid] = state; } // Get thread's current state -condition_variable_status ConditionVariableArbitraryPolicy::get_thread_cv_state(runner_id_t tid) { +condition_variable_status ConditionVariableArbitraryPolicy::get_thread_cv_state( + runner_id_t tid) { auto it = this->threads_with_states.find(tid); - return (it != this->threads_with_states.end()) ? it->second : CV_UNINITIALIZED; + return (it != this->threads_with_states.end()) ? it->second + : CV_UNINITIALIZED; } -void ConditionVariableArbitraryPolicy::update_thread_cv_state(runner_id_t tid, condition_variable_status state) { +void ConditionVariableArbitraryPolicy::update_thread_cv_state( + runner_id_t tid, condition_variable_status state) { this->threads_with_states[tid] = state; } - - diff --git a/src/mcmini/model/cond_var_default_policy.cpp b/src/mcmini/model/cond_var_default_policy.cpp index fb06472..d07d5ed 100644 --- a/src/mcmini/model/cond_var_default_policy.cpp +++ b/src/mcmini/model/cond_var_default_policy.cpp @@ -1,35 +1,30 @@ /* File inspired by classic mcmini*/ #include "mcmini/misc/cond/cond_var_default_policy.hpp" + #include -#include #include +#include -void -ConditionVariableDefaultPolicy::receive_broadcast_message() -{ +void ConditionVariableDefaultPolicy::receive_broadcast_message() { // Move everyone into the get-out-of-jail free place // from the wake group list. for (const WakeGroup &wg : this->wake_groups) { - for (const runner_id_t signaled_thread : wg) { + for (const runner_id_t signaled_thread : wg) { this->broadcast_eligible_threads.insert(signaled_thread); - } + } } this->wake_groups.clear(); } -bool -ConditionVariableDefaultPolicy::has_waiters() const -{ +bool ConditionVariableDefaultPolicy::has_waiters() const { // broadcast_eligiblle_threads are those threads that were // blocked, but were around during the last broadcast. // wake_groups are the groups that are available to wake. - return ! this->broadcast_eligible_threads.empty() || - ! this->wake_groups.empty(); + return !this->broadcast_eligible_threads.empty() || + !this->wake_groups.empty(); } -bool -ConditionVariableDefaultPolicy::thread_can_exit(runner_id_t tid) const -{ +bool ConditionVariableDefaultPolicy::thread_can_exit(runner_id_t tid) const { // Either you're eligible to wake up because: // 1. You were around during a broadcast message @@ -38,15 +33,11 @@ ConditionVariableDefaultPolicy::thread_can_exit(runner_id_t tid) const } // 2. OR you can now consume a signal - return std::any_of( - wake_groups.begin(), wake_groups.end(), - [=](const WakeGroup &wg) { return wg.contains(tid); }); - + return std::any_of(wake_groups.begin(), wake_groups.end(), + [=](const WakeGroup &wg) { return wg.contains(tid); }); } -void -ConditionVariableDefaultPolicy::wake_thread(runner_id_t tid) -{ +void ConditionVariableDefaultPolicy::wake_thread(runner_id_t tid) { // To correctly match the semantics of condition variables, // if a thread was present in the condition variable during a // broadcast, we DO NOT want it to consume signals which arrive @@ -58,10 +49,10 @@ ConditionVariableDefaultPolicy::wake_thread(runner_id_t tid) // Otherwise, we should consume the FIRST signal // we're located in: we want to ensure we allow // the most possible threads to - const auto signal_to_consume = std::find_if( - wake_groups.begin(), wake_groups.end(), - [=](const WakeGroup &wg) { return wg.contains(tid); }); - + const auto signal_to_consume = + std::find_if(wake_groups.begin(), wake_groups.end(), + [=](const WakeGroup &wg) { return wg.contains(tid); }); + // If 'signal_to_consume == wake_groups.end()', we are attempting // to wake a thread which can neither consume a signal nor has // been woken from a prior broadcast. @@ -69,9 +60,9 @@ ConditionVariableDefaultPolicy::wake_thread(runner_id_t tid) wake_groups.erase(signal_to_consume); } - //Additionally, remove this thread from any other - // wake groups it may be in, so that it does'nt attempt - // to consume a signal in the future + // Additionally, remove this thread from any other + // wake groups it may be in, so that it does'nt attempt + // to consume a signal in the future for (WakeGroup &wg : this->wake_groups) { wg.remove_candidate_thread(tid); } diff --git a/src/mcmini/model/cond_var_single_grp_policy.cpp b/src/mcmini/model/cond_var_single_grp_policy.cpp index c25cac7..6e3577b 100644 --- a/src/mcmini/model/cond_var_single_grp_policy.cpp +++ b/src/mcmini/model/cond_var_single_grp_policy.cpp @@ -3,9 +3,7 @@ #include -void -ConditionVariableSingleGroupPolicy::receive_broadcast_message() -{ +void ConditionVariableSingleGroupPolicy::receive_broadcast_message() { // Empty BOTH the sleep queue and the wake group list: // at this point everyone is allowed to wake up ConditionVariableDefaultPolicy::receive_broadcast_message(); @@ -16,9 +14,7 @@ ConditionVariableSingleGroupPolicy::receive_broadcast_message() this->wait_queue.clear(); } -void -ConditionVariableSingleGroupPolicy::wake_thread(runner_id_t tid) -{ +void ConditionVariableSingleGroupPolicy::wake_thread(runner_id_t tid) { ConditionVariableDefaultPolicy::wake_thread(tid); // Remove the thread from the sleep queue if it is still @@ -26,21 +22,16 @@ ConditionVariableSingleGroupPolicy::wake_thread(runner_id_t tid) // the thread in the queue as part of their implementation, // but they should always be removed when a thread is woken const auto waiting_thread = - std::find(wait_queue.begin(), wait_queue.end(), tid); + std::find(wait_queue.begin(), wait_queue.end(), tid); if (waiting_thread != wait_queue.end()) { wait_queue.erase(waiting_thread); } } -void -ConditionVariableSingleGroupPolicy::add_waiter(runner_id_t tid) -{ +void ConditionVariableSingleGroupPolicy::add_waiter(runner_id_t tid) { this->wait_queue.push_back(tid); - } -bool -ConditionVariableSingleGroupPolicy::has_waiters() const -{ - return ! this->wait_queue.empty(); +bool ConditionVariableSingleGroupPolicy::has_waiters() const { + return !this->wait_queue.empty(); } diff --git a/src/mcmini/model/cond_var_wakegroup.cpp b/src/mcmini/model/cond_var_wakegroup.cpp index d1adea0..dfe58d7 100644 --- a/src/mcmini/model/cond_var_wakegroup.cpp +++ b/src/mcmini/model/cond_var_wakegroup.cpp @@ -1,11 +1,11 @@ #include "mcmini/misc/cond/cond_var_wakegroup.hpp" -#include +#include - bool WakeGroup::contains(runner_id_t tid) const { - return std::find(begin(), end(), tid) != end(); - } +bool WakeGroup::contains(runner_id_t tid) const { + return std::find(begin(), end(), tid) != end(); +} - void WakeGroup::remove_candidate_thread(runner_id_t tid) { - erase(std::remove(begin(), end(), tid), end()); - } +void WakeGroup::remove_candidate_thread(runner_id_t tid) { + erase(std::remove(begin(), end(), tid), end()); +} diff --git a/src/mcmini/model/program.cpp b/src/mcmini/model/program.cpp index f29563c..210f20e 100644 --- a/src/mcmini/model/program.cpp +++ b/src/mcmini/model/program.cpp @@ -151,12 +151,10 @@ bool program::is_in_deadlock() const { // probably still want to show a bug if other userspace threads manage to get // into a deadlock even while the main thread could exit, so we don't treat // that case specially. - bool all_exited = true; - for (const auto &pair : this->get_pending_transitions()) { + for (const auto &pair : this->get_pending_transitions()) if (!this->state_seq.get_state_of_runner(pair.first)->has_exited()) - all_exited = false; - } - return !all_exited; + return true; // All blocked and at least one hasn't exited + return false; } std::ostream &program::dump_state(std::ostream &os) const { diff --git a/src/mcmini/model/transition_sequence.cpp b/src/mcmini/model/transition_sequence.cpp index 72ccbbf..994651e 100644 --- a/src/mcmini/model/transition_sequence.cpp +++ b/src/mcmini/model/transition_sequence.cpp @@ -14,7 +14,9 @@ void transition_sequence::consume_into_subsequence(uint32_t depth) { // effect. if (depth <= contents.size()) { extensions::delete_all(contents.begin() + depth, contents.end()); - contents.erase(contents.begin() + depth, contents.end()); + contents.resize(depth); + // Per-thread depths change + per_runner_depth.clear(); } } diff --git a/src/mcmini/model/transitions/condition_variables.cpp b/src/mcmini/model/transitions/condition_variables.cpp index c5af6d7..c5b2649 100644 --- a/src/mcmini/model/transitions/condition_variables.cpp +++ b/src/mcmini/model/transitions/condition_variables.cpp @@ -1,117 +1,125 @@ +#include "../include/mcmini/misc/cond/cond_var_arbitrary_policy.hpp" #include "mcmini/mem.h" #include "mcmini/model/exception.hpp" #include "mcmini/model/transitions/condition_variables/callbacks.hpp" -#include "../include/mcmini/misc/cond/cond_var_arbitrary_policy.hpp" using namespace model; using namespace objects; -model::transition* cond_init_callback(runner_id_t p, - const volatile runner_mailbox& rmb, - model_to_system_map& m) { +model::transition *cond_init_callback(runner_id_t p, + const volatile runner_mailbox &rmb, + model_to_system_map &m) { // Fetch the remote object - pthread_cond_t* remote_cond; - memcpy_v(&remote_cond, (volatile void*)rmb.cnts, sizeof(pthread_cond_t*)); + pthread_cond_t *remote_cond; + memcpy_v(&remote_cond, (volatile void *)rmb.cnts, sizeof(pthread_cond_t *)); // Locate the corresponding model of this object if (!m.contains(remote_cond)) { // FIXME: Allow dynamic selection of wakeup policies. // For now, we hard-code it here. Not great, but at least // we can change it relatively easily still - ConditionVariablePolicy* policy = new ConditionVariableArbitraryPolicy(); - m.observe_object(remote_cond, - new condition_variable( - condition_variable::state::cv_initialized, policy)); + ConditionVariablePolicy *policy = new ConditionVariableArbitraryPolicy(); + m.observe_object(remote_cond, + new condition_variable( + condition_variable::state::cv_initialized, policy)); } - + state::objid_t const cond = m.get_model_of_object(remote_cond); return new transitions::condition_variable_init(p, cond); } -model::transition* cond_waiting_thread_enqueue_callback(runner_id_t p, - const volatile runner_mailbox& rmb, - model_to_system_map& m){ - pthread_cond_t* remote_cond; - pthread_mutex_t* remote_mut; - memcpy_v(&remote_cond, (volatile void*)rmb.cnts, sizeof(pthread_cond_t*)); - memcpy_v(&remote_mut, (volatile void*)(rmb.cnts + sizeof(pthread_cond_t*)), sizeof(pthread_mutex_t*)); +model::transition *cond_waiting_thread_enqueue_callback( + runner_id_t p, const volatile runner_mailbox &rmb, model_to_system_map &m) { + pthread_cond_t *remote_cond; + pthread_mutex_t *remote_mut; + memcpy_v(&remote_cond, (volatile void *)rmb.cnts, sizeof(pthread_cond_t *)); + memcpy_v(&remote_mut, (volatile void *)(rmb.cnts + sizeof(pthread_cond_t *)), + sizeof(pthread_mutex_t *)); if (!m.contains(remote_cond)) - throw undefined_behavior_exception( - "Attempting to wait on an uninitialized condition variable"); + m.observe_object(remote_cond, new condition_variable( + condition_variable::cv_initialized)); if (!m.contains(remote_mut)) throw undefined_behavior_exception( - "Attempting to wait on a condition variable with an uninitialized mutex"); - + "Attempting to wait on a condition " + "variable with an uninitialized mutex"); + state::objid_t const cond = m.get_model_of_object(remote_cond); state::objid_t const mut = m.get_model_of_object(remote_mut); return new transitions::condition_variable_enqueue_thread(p, cond, mut); } -model::transition* cond_wait_callback(runner_id_t p, - const volatile runner_mailbox& rmb, - model_to_system_map& m) { - pthread_cond_t* remote_cond; - pthread_mutex_t* remote_mut; - memcpy_v(&remote_cond, (volatile void*)rmb.cnts, sizeof(pthread_cond_t*)); - memcpy_v(&remote_mut, (volatile void*)(rmb.cnts + sizeof(pthread_cond_t*)), sizeof(pthread_mutex_t*)); +model::transition *cond_wait_callback(runner_id_t p, + const volatile runner_mailbox &rmb, + model_to_system_map &m) { + pthread_cond_t *remote_cond; + pthread_mutex_t *remote_mut; + memcpy_v(&remote_cond, (volatile void *)rmb.cnts, sizeof(pthread_cond_t *)); + memcpy_v(&remote_mut, (volatile void *)(rmb.cnts + sizeof(pthread_cond_t *)), + sizeof(pthread_mutex_t *)); // Locate the corresponding model of this object if (!m.contains(remote_cond)) - throw undefined_behavior_exception( - "Attempting to wait on an uninitialized condition variable"); + m.observe_object(remote_cond, new condition_variable( + condition_variable::cv_initialized)); if (!m.contains(remote_mut)) throw undefined_behavior_exception( - "Attempting to wait on a condition variable with an uninitialized mutex"); - + "Attempting to wait on a condition " + "variable with an uninitialized mutex"); state::objid_t const cond = m.get_model_of_object(remote_cond); state::objid_t const mut = m.get_model_of_object(remote_mut); return new transitions::condition_variable_wait(p, cond, mut); } -model::transition* cond_signal_callback(runner_id_t p, - const volatile runner_mailbox& rmb, - model_to_system_map& m) { - pthread_cond_t* remote_cond; - memcpy_v(&remote_cond, (volatile void*)rmb.cnts, sizeof(pthread_cond_t*)); +model::transition *cond_signal_callback(runner_id_t p, + const volatile runner_mailbox &rmb, + model_to_system_map &m) { + pthread_cond_t *remote_cond; + memcpy_v(&remote_cond, (volatile void *)rmb.cnts, sizeof(pthread_cond_t *)); // Locate the corresponding model of this object if (!m.contains(remote_cond)) - throw undefined_behavior_exception( - "Attempting to signal an uninitialized condition variable"); + m.observe_object(remote_cond, new condition_variable( + condition_variable::cv_initialized)); + // throw undefined_behavior_exception( + // "Attempting to signal an uninitialized condition variable"); state::objid_t const cond = m.get_model_of_object(remote_cond); return new transitions::condition_variable_signal(p, cond); } -model::transition* cond_broadcast_callback(runner_id_t p, - const volatile runner_mailbox& rmb, - model_to_system_map& m) { - pthread_cond_t* remote_cond; - memcpy_v(&remote_cond, (volatile void*)rmb.cnts, sizeof(pthread_cond_t*)); +model::transition *cond_broadcast_callback(runner_id_t p, + const volatile runner_mailbox &rmb, + model_to_system_map &m) { + pthread_cond_t *remote_cond; + memcpy_v(&remote_cond, (volatile void *)rmb.cnts, sizeof(pthread_cond_t *)); // Locate the corresponding model of this object if (!m.contains(remote_cond)) - throw undefined_behavior_exception( - "Attempting to broadcast on an uninitialized condition variable"); + m.observe_object(remote_cond, new condition_variable( + condition_variable::cv_initialized)); + // throw undefined_behavior_exception( + // "Attempting to broadcast on an uninitialized condition variable"); state::objid_t const cond = m.get_model_of_object(remote_cond); return new transitions::condition_variable_broadcast(p, cond); } -model::transition* cond_destroy_callback(runner_id_t p, - const volatile runner_mailbox& rmb, - model_to_system_map& m) { - pthread_cond_t* remote_cond; - memcpy_v(&remote_cond, (volatile void*)rmb.cnts, sizeof(pthread_cond_t*)); +model::transition *cond_destroy_callback(runner_id_t p, + const volatile runner_mailbox &rmb, + model_to_system_map &m) { + pthread_cond_t *remote_cond; + memcpy_v(&remote_cond, (volatile void *)rmb.cnts, sizeof(pthread_cond_t *)); // Locate the corresponding model of this object if (!m.contains(remote_cond)) - throw undefined_behavior_exception( - "Attempting to destroy an uninitialized condition variable"); + m.observe_object(remote_cond, new condition_variable( + condition_variable::cv_initialized)); + // throw undefined_behavior_exception( + // "Attempting to destroy an uninitialized condition variable"); state::objid_t const cond = m.get_model_of_object(remote_cond); return new transitions::condition_variable_destroy(p, cond); diff --git a/src/mcmini/model/transitions/mutex.cpp b/src/mcmini/model/transitions/mutex.cpp index 1757a7e..8d187e1 100644 --- a/src/mcmini/model/transitions/mutex.cpp +++ b/src/mcmini/model/transitions/mutex.cpp @@ -5,12 +5,12 @@ using namespace model; using namespace objects; -model::transition* mutex_init_callback(runner_id_t p, - const volatile runner_mailbox& rmb, - model_to_system_map& m) { +model::transition *mutex_init_callback(runner_id_t p, + const volatile runner_mailbox &rmb, + model_to_system_map &m) { // Fetch the remote object - pthread_mutex_t* remote_mut; - memcpy_v(&remote_mut, (volatile void*)rmb.cnts, sizeof(pthread_mutex_t*)); + pthread_mutex_t *remote_mut; + memcpy_v(&remote_mut, (volatile void *)rmb.cnts, sizeof(pthread_mutex_t *)); // Locate the corresponding model of this object if (!m.contains(remote_mut)) @@ -20,26 +20,27 @@ model::transition* mutex_init_callback(runner_id_t p, return new transitions::mutex_init(p, mut); } -model::transition* mutex_lock_callback(runner_id_t p, - const volatile runner_mailbox& rmb, - model_to_system_map& m) { - pthread_mutex_t* remote_mut; - memcpy_v(&remote_mut, (volatile void*)rmb.cnts, sizeof(pthread_mutex_t*)); +model::transition *mutex_lock_callback(runner_id_t p, + const volatile runner_mailbox &rmb, + model_to_system_map &m) { + pthread_mutex_t *remote_mut; + memcpy_v(&remote_mut, (volatile void *)rmb.cnts, sizeof(pthread_mutex_t *)); // TODO: add code from Gene's PR here if (!m.contains(remote_mut)) - throw undefined_behavior_exception( - "Attempting to lock an uninitialized mutex"); + m.observe_object(remote_mut, new mutex(mutex::unlocked)); + // throw undefined_behavior_exception( + // "Attempting to lock an uninitialized mutex"); state::objid_t const mut = m.get_model_of_object(remote_mut); return new transitions::mutex_lock(p, mut); } -model::transition* mutex_unlock_callback(runner_id_t p, - const volatile runner_mailbox& rmb, - model_to_system_map& m) { - pthread_mutex_t* remote_mut; - memcpy_v(&remote_mut, (volatile void*)rmb.cnts, sizeof(pthread_mutex_t*)); +model::transition *mutex_unlock_callback(runner_id_t p, + const volatile runner_mailbox &rmb, + model_to_system_map &m) { + pthread_mutex_t *remote_mut; + memcpy_v(&remote_mut, (volatile void *)rmb.cnts, sizeof(pthread_mutex_t *)); // TODO: add code from Gene's PR here if (!m.contains(remote_mut)) diff --git a/src/mcmini/model_checking/algorithms/classic_dpor.cpp b/src/mcmini/model_checking/algorithms/classic_dpor.cpp index 2504f21..55950ad 100644 --- a/src/mcmini/model_checking/algorithms/classic_dpor.cpp +++ b/src/mcmini/model_checking/algorithms/classic_dpor.cpp @@ -1,21 +1,29 @@ -#include "mcmini/model_checking/algorithms/classic_dpor.hpp" - #include +#include #include #include #include #include #include +#include #include #include #include #include #include +#ifdef MCMINI_USE_SCIP +#include + +#include "scip/scip.h" +#include "scip/scipdefplugins.h" +#endif + #include "mcmini/coordinator/coordinator.hpp" #include "mcmini/defines.h" #include "mcmini/log/logger.hpp" +#include "mcmini/model/config.hpp" #include "mcmini/model/exception.hpp" #include "mcmini/model/program.hpp" #include "mcmini/model/transition.hpp" @@ -24,6 +32,7 @@ #include "mcmini/model/transitions/mutex/mutex_init.hpp" #include "mcmini/model/transitions/semaphore/callbacks.hpp" #include "mcmini/model/transitions/thread/callbacks.hpp" +#include "mcmini/model_checking/algorithms/classic_dpor.hpp" #include "mcmini/model_checking/algorithms/classic_dpor/clock_vector.hpp" #include "mcmini/model_checking/algorithms/classic_dpor/runner_item.hpp" #include "mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp" @@ -35,15 +44,67 @@ using namespace model_checking; logger dpor_logger("dpor"); -struct classic_dpor::dpor_context { +struct classic_dpor::dpor_context : public algorithm::context { + public: ::coordinator &coordinator; std::vector stack; dpor_context(::coordinator &c) : coordinator(c) {} + public: + const size_t state_stack_size() const { return stack.size(); } + const size_t transition_stack_size() const { + const size_t ss = state_stack_size(); + return ss > 0 ? (ss - 1) : 0; + } + const transition *get_transition(int i) const { return stack.at(i).get_out_transition(); } + + const transition *get_transition(size_t i) const { + return stack.at(i).get_out_transition(); + } + + const runner_id_t tid(size_t i) const { + return get_transition(i)->get_executor(); + } + + const clock_vector &clock(size_t j) const { + // C(j) in the DPOR paper + // NOTE: The clock vector for the `i`th transition is stored in the state + // into which the transition points, NOT the state from which the transition + // starts (hence the j + 1) + return stack.at(j + 1).get_clock_vector(); + } + + bool happens_before(size_t i, size_t j) const { + const runner_id_t procSi = get_transition(i)->get_executor(); + return i <= clock(j).value_for(procSi); + } + + bool happens_before_thread(size_t i, runner_id_t p) const { + const runner_id_t rid = get_transition(i)->get_executor(); + const clock_vector &cv = + stack.back().get_per_runner_clocks()[p].get_clock_vector(); + return i <= cv.value_for(rid); + } + + bool threads_race_after(size_t i, runner_id_t q, runner_id_t p) const { + const size_t transition_stack_height = stack.size() - 1; + for (size_t j = i + 1; j < transition_stack_height; j++) { + if (q == get_transition(j)->get_executor() && + this->happens_before_thread(j, p)) + return true; + } + return false; + } + std::vector> transitive_reduction() const; + std::vector linearize_lowest_first() const; + std::vector linearize_optimal() const; + const program &get_model() const { + return this->coordinator.get_current_program_model(); + } }; clock_vector classic_dpor::accumulate_max_clock_vector_against( @@ -53,11 +114,10 @@ clock_vector classic_dpor::accumulate_max_clock_vector_against( // (case-sensitive) in the paper, viz. the transition between states `s_i` and // `s_{i+1}`. clock_vector result; - for (const stack_item &s_i : context.stack) { - if (s_i.get_out_transition() != nullptr && - this->are_dependent(*s_i.get_out_transition(), t)) { - result = clock_vector::max(result, s_i.get_clock_vector()); - } + for (size_t i = 0; i < context.transition_stack_size(); i++) { + const model::transition *S_i = context.get_transition(i); + if (this->are_dependent(*S_i, t)) + result = clock_vector::max(result, context.clock(i)); } return result; } @@ -74,37 +134,10 @@ bool classic_dpor::are_dependent(const model::transition &t1, this->config.dependency_relation.call_or(true, &t1, &t2); } -bool classic_dpor::happens_before(const dpor_context &context, size_t i, - size_t j) const { - const runner_id_t rid = - context.stack.at(i).get_out_transition()->get_executor(); - const clock_vector &cv = context.stack.at(j).get_clock_vector(); - return i <= cv.value_for(rid); -} - -bool classic_dpor::happens_before_thread(const dpor_context &context, size_t i, - runner_id_t p) const { - const runner_id_t rid = context.get_transition(i)->get_executor(); - const clock_vector &cv = - context.stack.back().get_per_runner_clocks()[p].get_clock_vector(); - return i <= cv.value_for(rid); -} - -bool classic_dpor::threads_race_after(const dpor_context &context, size_t i, - runner_id_t q, runner_id_t p) const { - const size_t transition_stack_height = context.stack.size() - 1; - for (size_t j = i + 1; j < transition_stack_height; j++) { - if (q == context.get_transition(j)->get_executor() && - this->happens_before_thread(context, j, p)) - return true; - } - return false; -} - void classic_dpor::verify_using(coordinator &coordinator, const callbacks &callbacks) { // The code below is an implementation of the model-checking algorithm of - // Flanagan and Godefroid from 2015. + // Flanagan and Godefroid from 2005. // 1. Data structure set up @@ -114,6 +147,9 @@ void classic_dpor::verify_using(coordinator &coordinator, /// The initial entry into the stack represents the information DPOR tracks /// for state `s_0`. log_debug(dpor_logger) << "Initializing the DPOR stack"; + bool reached_max_depth = false; + std::list round_robin_sched; + stats model_checking_stats; dpor_context context(coordinator); auto &dpor_stack = context.stack; @@ -128,21 +164,65 @@ void classic_dpor::verify_using(coordinator &coordinator, // 2. Exploration phase while (dpor_stack.back().has_enabled_runners()) { if (dpor_stack.size() >= this->config.maximum_total_execution_depth) { - throw std::runtime_error( - "*** Execution Limit Reached! ***\n\n" - "McMini ran a trace with" + - std::to_string(dpor_stack.size()) + - " transitions which is\n" - "the more than McMini was configured to handle in any one trace (" + - std::to_string(this->config.maximum_total_execution_depth) + - "). Try running mcmini with the \"--max-depth-per-thread\" flag\n" - "to limit how far into a trace McMini can go\n"); + if (!reached_max_depth) { + log_unexpected(dpor_logger) + << "*** Execution Limit Reached! ***\n\n" + << "McMini encountered a trace with " << dpor_stack.size() + << " transitions which is the most that McMini was configured" + << " to handle in any given trace (" + << this->config.maximum_total_execution_depth + << "). McMini will continue its search, but it may be " + "incomplete. Rerun McMini with the " + "\"--max-depth-per-thread\" " + << "flag for correct results."; + reached_max_depth = true; + } + break; } - // NOTE: For deterministic results, always choose the "first" enabled - // runner. A runner precedes another runner in being enabled iff it has a - // smaller id. try { - const runner_id_t rid = dpor_stack.back().get_first_enabled_runner(); + runner_id_t rid; + switch (config.policy) { + case algorithm::exploration_policy::smallest_first: { + rid = dpor_stack.back().get_first_enabled_runner(); + break; + } + case algorithm::exploration_policy::round_robin: { + auto unscheduled_runners = dpor_stack.back().get_enabled_runners(); + + // For round robin scheduling, always prefer to schedule + // threads that don't appear in `round_robin_sched` because + // these threads are necessarily unscheduled wrt the previous + // starting point. + // + // 1. Among those threads not appearing in the round robin + // schedule, always pick the smallest. + // 2. If every enabled thread has already been scheduled, then pick + // the first based on the round robin schedule and move it to the + // back of the list. NOTE: Every enabled runner is in + // `round_robin_sched`, but the converse is not necessarily true + // (i.e. there may be threads in `round_robin_sched` that aren't + // enabled) + for (const runner_id_t id : round_robin_sched) { + unscheduled_runners.erase(id); + } + if (!unscheduled_runners.empty()) { + rid = *std::min_element(unscheduled_runners.begin(), + unscheduled_runners.end()); + round_robin_sched.push_back(rid); + } else { + auto it = std::find_if(round_robin_sched.begin(), + round_robin_sched.end(), + [&](const runner_id_t id) { + return dpor_stack.back().is_enabled(id); + }); + assert(it != round_robin_sched.end()); + round_robin_sched.splice(round_robin_sched.end(), + round_robin_sched, it); + rid = *it; + } + } + } + this->continue_dpor_by_expanding_trace_with(rid, context); model_checking_stats.total_transitions++; @@ -165,26 +245,29 @@ void classic_dpor::verify_using(coordinator &coordinator, "The program exited abnormally"); } } catch (const model::undefined_behavior_exception &ube) { - if (callbacks.undefined_behavior) - callbacks.undefined_behavior(coordinator, model_checking_stats, ube); + callbacks.undefined_behavior(context, model_checking_stats, ube); return; } catch (const real_world::process::termination_error &te) { - if (callbacks.abnormal_termination) - callbacks.abnormal_termination(coordinator, model_checking_stats, te); + callbacks.abnormal_termination(context, model_checking_stats, te); return; } catch (const real_world::process::nonzero_exit_code_error &nzec) { - if (callbacks.nonzero_exit_code) - callbacks.nonzero_exit_code(coordinator, model_checking_stats, nzec); + callbacks.nonzero_exit_code(context, model_checking_stats, nzec); return; } } - if (callbacks.trace_completed) - callbacks.trace_completed(coordinator, model_checking_stats); + callbacks.trace_completed(context, model_checking_stats); - if (callbacks.deadlock && - coordinator.get_current_program_model().is_in_deadlock()) - callbacks.deadlock(coordinator, model_checking_stats); + if (coordinator.get_current_program_model().is_in_deadlock()) { + if (config.stop_at_first_deadlock) { + log_info(dpor_logger) + << "First deadlock found. Reporting and stopping model checking."; + callbacks.deadlock(context, model_checking_stats); + return; + } else { + callbacks.deadlock(context, model_checking_stats); + } + } // 3. Backtrack phase while (!dpor_stack.empty() && dpor_stack.back().backtrack_set_empty()) @@ -208,9 +291,25 @@ void classic_dpor::verify_using(coordinator &coordinator, try { this->continue_dpor_by_expanding_trace_with( dpor_stack.back().backtrack_set_pop_first(), context); + + // If we're doing round robin scheduling for expanding the trace, + // backtracking forces a restart of the round robin process. + // Intuitively, this makes sense: round robin only applies when + // searching _new_ traces. However, we could be a little more + // intelligent here and resume the round robin from where it _should_ + // continue, but that's a little more complicated. + // + // That is, if the trace after backtracking is e.g. + // + // 1, 2, 3, 1, 2, _2_ + // + // where _2_ was inserted as a backtrack point, then we could notice + // that _3_ was technically supposed to be next in the round robin + // scheduling. The current method would schedule 1, then 2, then 3, ... + // since we only account for scheduling that occurs during _expansion_. + round_robin_sched.clear(); } catch (const model::undefined_behavior_exception &ube) { - if (callbacks.undefined_behavior) - callbacks.undefined_behavior(coordinator, model_checking_stats, ube); + callbacks.undefined_behavior(context, model_checking_stats, ube); return; } } @@ -393,14 +492,14 @@ bool classic_dpor::dynamically_update_backtrack_sets_at_index( // TODO: add in co-enabled conditions const bool has_reversible_race = this->are_dependent(next_sp, S_i) && this->are_coenabled(next_sp, S_i) && - !this->happens_before_thread(context, i, p); + !context.happens_before_thread(i, p); // If there exists i such that ... if (has_reversible_race) { std::set e; for (runner_id_t const q : pre_si.get_enabled_runners()) { - const bool in_e = q == p || this->threads_race_after(context, i, q, p); + const bool in_e = q == p || context.threads_race_after(i, q, p); // If E != empty set if (in_e && !pre_si.sleep_set_contains(q)) e.insert(q); @@ -430,57 +529,672 @@ bool classic_dpor::dynamically_update_backtrack_sets_at_index( classic_dpor::dependency_relation_type classic_dpor::default_dependencies() { classic_dpor::dependency_relation_type dr; - dr.register_dd_entry( - &transitions::thread_create::depends); - dr.register_dd_entry( - &transitions::thread_join::depends); - dr.register_dd_entry( - &transitions::mutex_lock::depends); - dr.register_dd_entry( - &transitions::mutex_lock::depends); - dr.register_dd_entry( - &transitions::condition_variable_wait::depends); - dr.register_dd_entry( - &transitions::condition_variable_wait::depends); - dr.register_dd_entry( - &transitions::condition_variable_signal::depends); - dr.register_dd_entry( - &transitions::condition_variable_signal::depends); + using namespace transitions; + dr.register_dd_entry(&thread_create::depends); + dr.register_dd_entry(&thread_join::depends); + dr.register_dd_entry(&thread_start::depends); + dr.register_dd_entry(&thread_exit::depends); + dr.register_dd_entry( + &mutex_lock::depends); + dr.register_dd_entry( + &mutex_lock::depends); + dr.register_dd_entry( + &condition_variable_wait::depends); + dr.register_dd_entry( + &condition_variable_wait::depends); + dr.register_dd_entry( + &condition_variable_signal::depends); + dr.register_dd_entry( + &condition_variable_signal::depends); return dr; } classic_dpor::coenabled_relation_type classic_dpor::default_coenabledness() { + using namespace transitions; classic_dpor::dependency_relation_type cr; - cr.register_dd_entry( - &transitions::thread_create::coenabled_with); - cr.register_dd_entry( - &transitions::thread_join::coenabled_with); - cr.register_dd_entry( - &transitions::mutex_lock::coenabled_with); - cr.register_dd_entry( - &transitions::condition_variable_signal::coenabled_with); - cr.register_dd_entry( - &transitions::condition_variable_signal::coenabled_with); - cr.register_dd_entry( - &transitions::condition_variable_broadcast::coenabled_with); - cr.register_dd_entry( - &transitions::condition_variable_broadcast::coenabled_with); - cr.register_dd_entry( - &transitions::condition_variable_destroy::coenabled_with); - cr.register_dd_entry( - &transitions::condition_variable_destroy::coenabled_with); + cr.register_dd_entry(&thread_create::coenabled_with); + cr.register_dd_entry(&thread_join::coenabled_with); + cr.register_dd_entry( + &mutex_lock::coenabled_with); + cr.register_dd_entry( + &condition_variable_signal::coenabled_with); + cr.register_dd_entry( + &condition_variable_signal::coenabled_with); + cr.register_dd_entry( + &condition_variable_broadcast::coenabled_with); + cr.register_dd_entry( + &condition_variable_broadcast::coenabled_with); + cr.register_dd_entry( + &condition_variable_destroy::coenabled_with); + cr.register_dd_entry( + &condition_variable_destroy::coenabled_with); return cr; } + +std::vector> +classic_dpor::dpor_context::transitive_reduction() const { + // This code is heavily inspired by the code in the LEDA library + // (https://leda.uni-trier.de), specifically the + // `LEDA-7.2.2/src/graph/graph_alg/_transclosure.cpp` + // + // The transitive reduction R of a digraph G is the graph of the fewest arcs + // such that a path exists between nodes u and v in G iff a path exists + // between u and v in R. Essentially, it's the smallest graph with the same + // dependency relation of G. + // + // In our case, we're considering a trace T := t1, t2, ..., tN of `N` + // total transitions. Each transition can be thought of as a node in a digraph + // with the edges representing "happens-before" dependencies between them. The + // transitive reduction of this digraph then represents those transitions in + // an "immediate" race: ti --> tj and for all transitions tk in-between, + // either ti --/--> tk or tk --/-->tj. + if (this->stack.empty()) return {}; + const size_t N = this->transition_stack_size(); + const size_t INF = N + 1; + + // `adj_list` holds the dependencies between every point in the trace. Nodes + // are representing using integers and correspond to the index in the + // transition stack. + // + // `e in adj_list[i] <--> happens_before(i, e)`. + // + // `redundant[i][j] <---> adj_list[i][j] can be removed in the transition + // reduction` + // + // NOTE: For each `i`, the nodes are ordered in topological order. This is + // because the transitions in the stack are already topologically ordered by + // construction, and we process nodes in order. + std::vector> adj_list(N); + std::vector> redundant(N); + + for (size_t i = 0; i < N; i++) { + for (size_t j = i + 1; j < N; j++) { + if (happens_before(i, j)) { + adj_list[i].push_back(j); + redundant[i].push_back(true); + } + } + } + + // Compute Chain Decomposition + // + // Decomposes the DAG into a minimum number of disjoint paths (chains). + // + // chain_id[v]: index of the chain containing v + // C[h]: list of nodes in chain h + // + // See Dilworth's theorem. + std::vector unmarked(N, true); + std::vector chain_id(N); + std::vector> C; + + // Iterating in increasing topological order (see note above) + for (size_t v = 0; v < N; ++v) { + if (unmarked[v]) { + C.emplace_back(); // Start a new chain + size_t current_chain_id = C.size() - 1; + size_t current_node = v; + + while (current_node != INF) { + C.back().push_back(current_node); + chain_id[current_node] = current_chain_id; + unmarked[current_node] = false; + + // Find the first unmarked neighbor 'w' (smallest topological rank) + size_t next_node = INF; + for (size_t w : adj_list[current_node]) { + if (unmarked[w]) { + next_node = w; + break; + } + } + current_node = next_node; + } + } + } + + // Compute Reachability and Redundancy + // + // reach[i][c] = min{j ; node j in C[c] and path i -> j exists} + // + // where "<" refers to topological order + // + // Intuitively, `reach[i][c]` denotes the "smallest" (topologically-speaking) + // node in chain `c` to which `i` has a path. + const int num_chains = C.size(); + std::vector> reach(N, std::vector(num_chains, INF)); + + // The key loop: iterates in DECREASING topological order + for (size_t k = 0; k <= N - 1; k++) { + size_t v = N - k - 1; + + // Process outgoing edges of v (target 'w' is in INCREASING order) + for (size_t i = 0; i < adj_list[v].size(); i++) { + const int w = adj_list[v][i]; + if (w < reach[v][chain_id[w]]) { + redundant[v][i /* INDEX of w */] = false; + + // If v can reach w via a non-redundant edge, v can reach everything j + // reaches. + for (int c = 0; c < num_chains; ++c) + reach[v][c] = std::min(reach[v][c], reach[w][c]); + } + } + reach[v][chain_id[v]] = v; + } + + // Remove redundant edges from the adjacency list + for (size_t k = 0; k < N; k++) { + auto &out_edges = adj_list[k]; + auto &redundant_edges = redundant[k]; + assert(out_edges.size() == redundant_edges.size()); + size_t j = 0; + for (size_t i = 0; i < out_edges.size(); ++i) + if (!redundant_edges[i]) out_edges[j++] = out_edges[i]; + out_edges.resize(j); + } + +#if DEBUG + // Validation: every point in the adjacency list happens before + for (size_t i = 0; i < N; i++) + for (size_t j = i + 1; j < N; j++) + if (std::find(adj_list[i].begin(), adj_list[i].end(), j) != + adj_list[i].end()) { + assert(happens_before(i, j)); + } else { + if (happens_before(i, j)) { + auto s = std::find_if(adj_list[i].begin(), adj_list[i].end(), + [&](int w) { return happens_before(w, j); }); + assert(s != adj_list[i].end()); + } + // Not in adj so ok in `false` case + } +#endif + return adj_list; +} + +#ifdef MCMINI_USE_SCIP +void SCIP_dump(SCIP *scip) { + SCIP_CALL_ABORT(SCIPprintOrigProblem(scip, nullptr, "lp", false)); +} +#endif + +std::vector classic_dpor::dpor_context::linearize_optimal() + const { +#ifdef MCMINI_USE_SCIP + // Given a fixed trace `T` of `N` transitions and a happens-before relation + // `happens-before_T: [1, N] x [1, N] -> {0, 1}` over the indices of `T`, the + // goal is to produce a new trace `T'` such that `happens-before_T(i, j) = + // happens-before_T'(i, j)` and such that the number of inversions is + // minimized. An inversion is a point in the trace where the thread ID of the + // `i`th transition doesn't match the thread ID of the `(i + 1)`th transition. + // + // The intuition in producing such a trace `T'` is that reasoning about this + // trace is "easier" than any other because the number of context switches + // (inversions) a user must perform to analyze a new trace is minimized. + // Moreover, since `T'` obeys the same "happens-before" relation as the + // original trace `T`, it produces the exact same bug. + // + // We can map relinearization into a graph problem. Let `Tids := {tid : exists + // i, T[i]->get_executor() = tid }`. We construct an unweighted, labeled + // digraph `K` where + // + // 1. V(K) := {1, 2, ..., N} + // 2. E(K) := {(i, j) in [1, N] x [1, N] : happens-before_T(i, j)} + // 3. tid : V -> Tids, tid(v) := T[v].get_executor() + // + // Since `happens-before_T` is transitive, we note that `K = + // Transitive-Closure(K)` since `happens-before_T` is transitive. Moreover, + // the array `[1, 2, ..., N]` is a valid topological sorting of `K` since `i > + // j --> happens-before_T(i, j) = 0`. + // + // An equivalent formulation of the problem is to find a topological sorting + // of K of minimal inversions. Computing such a minimal topological sort can + // be reduced to the sequential ordering problem (SOP) (see + // https://people.idsia.ch/~roberto/SOP.pdf for a reference). The SOP problem + // is related to the Asymmetric Traveling Salesperson Problem (ATSP). In the + // ATSP, the goal is to find a Hamiltonian Cycle in a weighted directed graph + // of minimal weight. SOP adds one more constraint and requires that certain + // nodes are visited before others based on a relation `R` over the vertices. + // + // We construct a new graph `G` from `K`. Here, we + // note that although the SOP problem and ATSP problems work with cycles, we + // can add a source and sink node to our DAG with zero weight edges between + // each other and the respective start and end nodes of the digraph (those + // with in-degree and out-degree equalt to 0 respectively) to map exactly to + // the SOP problem. + // + // Denote `H := Transitive-Reduction(K)`. Recall each node `v` in `K` and by + // proxy `H` corresponds to a transition in the transition stack `T`. Let + // `tid(v)` denote the labeling function of `K`. Then let + // + // 1. V(G) := V(H) + {source, sink} + // 2. E(G) := V(H) x V(H) + {(u, sink) : u in V(H), out-degree[u] = 0} + + // {(source, u) : u in V(H), in-degree[u] = 0}} + // 3. For all e = (u, v) in E(G), + // + // w(e) := if tid(u) == tid(v) || (u == source) || (v == sink) then 0 + // else 1 + // + // Let `C := SOP(G, E(H))` denote the Hamiltonian Cycle of minimum weight + // subjet to `E(H)`. Then `C := m_1, m_2, ..., m_N` corresponds to the minimal + // topological sorting. Clearly, `C` is a valid topological sorting since it + // contains all nodes of `H` and in an order obeying `E(H)`. By construction, + // the total weight of `C` equals the number of inversions in `C`, since an + // edge is weighted iff they are inverted and don't connect either the sink or + // source. Since `C` is of minimal weight,. it is also of minimal inversions. + // + // The SOP instance can be solved by reduction using integer constraints. + // Here, we use the Miller-Tucker-Zemlin (MTZ) formulation of the TSP problem. + // In MTZ, a variable `x_ij in {0, 1}` is added to represent whether the edge + // `(i, j)` is taken. For eache node `v_i`, a variable `u_i` is added to + // indicate the order the node is visited in the cycle. Constraints are added + // to ensure that exactly one `x_ij` goes into and exits each node and such + // that if (i, j) is taken then `u_i < u_j` We can adjust the MTZ to work on + // an SOP instance by adding precendence constraints on the variables `u_i` by + // always forcing `u_i <= u_j + 1` iff `i` happens-before `j`. + // + // Reducing the number of variables is key to making the solver efficient. + // There are two optimizations we apply in the context of our specific + // optimization problem instance: + // + // A) For each edge (u, v) in E(H), only (u, v) needs to be added to G + // since a path that includes (v, u) would not satisfy the ordering + // constraint. Moreover, any edge (u', v') in E(K) such that there exists + // a node k such that (u', k) and (k, v') in E(H) can be eliminated from + // G. This is equivalent to eliminating any edges in G that are contained + // in the `Transitive-Closure(H) = K`. In general, + // + // 1. V(G) := V(H) + {source, sink} + // 2. E(G) := E(H) + {(u, v) in V(H) x V(H) : (u, v) and (v, u) not in K} + + // {(u, sink) : u in V(H), out-degree[u] = 0} + + // {(source, u) : u in V(H), in-degree[u] = 0}} + // 3. For all e = (u, v) in E(G), + // + // w(e) := if tid(u) == tid(v) || (u == source) || (v == sink) then 0 + // else 1 + // + // B) We observe the following. Firstly, for any two nodes `(u, v) in + // H_TC`, `tid[u] = tid[v] ==> u --> v`. I.e., there is always a + // happens-before relation between two nodes of the same label. + // + // For each sequence V = v_1, v_2, ..., v_r in E(H) such that + // + // 1. For all i in [1, r) out-degree[u_i] = in-degree[u_(i + 1)] = 1 + // + // V must appear in the SOP Hamiltonian cycle C of minimal + // weight. + // + // Informally, given a contiguous "block" of transitions which must appear in + // order but are otherwise independent with other transitions except possibly + // the first and last transitions of the block, anything that doesn't + // "happen-before" the first transition in the block can be placed anywhere + // inside the block (otherwise there would be a transitive dependency + // somewhere in the block). Since the block ordering "forces" a certain + // ordering of the transitions, the number of inversions `I` in the block is + // fixed. For any `j` such that `v_1 --/--> j`, `tid[v_1] != tid[j]` and + // by transitivity `u --/--> j` and hence `tid[u] != tid[j]` for all u in V. + // `u --/--> j` further implies `(u, j) and (j, u) in G` for all `u in C`. + // Since `tid[u] != tid[j]`, `w_uj = w_ju = 1`. Since any Hamiltonian cycle + // must include all nodes in order, either the edge `(v_1, v_2)` is included + // or `(v_1, u_1), ..., (u_n, v_2)` for some `u_l` sequence such that `v_1 + // --\--> u_1`. Since w_(v_1, v_2) <= 1 but `w_(v_1, u_1) = w_(u_n, v_2)`, the + // weight of taking the interior edge instead of two exterior ones smaller + // than taking the two outer ones. We can essentially treat (v_1, v_2), ..., + // (v_(r-1), v_r) as a subgraph with all incoming and outgoing edges of + // weight 1. + // + // Consequently, we can first group together simple chains and solve the + // smaller SOP instance and still obtain the same optimal result. + // + // To compute the reduced graph, we use the Disjoint Set Union. See + // https://cp-algorithms.com/data_structures/disjoint_set_union.html + struct dsu { + std::vector parent; + std::vector sizes; + + public: + dsu(size_t n) : sizes(n, 1) { + parent.resize(n); + std::iota(parent.begin(), parent.end(), 0); + } + + size_t find(size_t i) { + if (parent[i] == i) return i; + return parent[i] = find(parent[i]); + } + + void unite(size_t i, size_t j) { + size_t root_i = find(i); + size_t root_j = find(j); + if (root_i != root_j) { + if (sizes[root_i] < sizes[root_j]) std::swap(root_i, root_j); + parent[root_i] = root_j; + sizes[root_i] += sizes[root_j]; + } + } + }; + + // Let `H_red` be the edge-reduced, transitive reduction graph. For each edge + // `(u, v) in E(H)`, if out(u) = in(v) = 1, then we can effectively combine + // `u` and `v` together into a single node iff they refer to transitions run + // by the same thread. `supernodes` maps nodes in the reduced graph to the + // original graph `H`. + // + // ``` + // Where `i` refers to a node ID in the reduced graph H_red + // supernodes[i] = [u_1, u_2, ..., u_(l+1)] <---> (u_j, u_{j+1}) in V(H) and + // out-degree[u_j] = in-degree[u_(j+1)] = 1 + // ``` + // Where `i` refers to a member of V(H) + // dsu.find(i) = j <---> i is a part of supernode j in the reduced graph + //``` + // The immediate transitive dependencies of `supernodes[i].back()` = u_(l+1) + // are the same as the transitive dependencies of the supernode. Moreoever, + // using the argument in the proof above, the set of edges + // {(u_i, v) in V(H) x V(H) : (u_i, v) and (v, u_i) not in K} is the same for + // any u_i. Here, we pick the first one `supernodes[i].front()` + const auto H = this->transitive_reduction(); + const size_t N = H.size(); + std::vector out_degree(N, 0); + std::vector in_degree(N, 0); + + // INVARIANT: supernodes[i] is sorted by ID (and hence sorted in topological + // order) + std::vector> supernodes; + std::unordered_map H_to_supernode; + { + dsu dsu(N); + { + std::vector tids(N, 0); + + for (size_t u = 0; u < N; ++u) { + out_degree[u] = H[u].size(); + tids[u] = tid(u); + for (size_t v : H[u]) in_degree[v]++; + } + + for (size_t u = 0; u < N; ++u) { + if (out_degree[u] == 1) { + size_t v = H[u][0]; // The only neighbor + if (in_degree[v] == 1 && tids[u] == tids[v]) dsu.unite(u, v); + } + } + } + { + std::map> groups; + for (size_t i = 0; i < N; ++i) groups[dsu.find(i)].push_back(i); + + supernodes.reserve(groups.size()); + for (auto &p : groups) { + const size_t new_supernode_id = supernodes.size(); + for (size_t i : p.second) H_to_supernode[i] = new_supernode_id; + supernodes.push_back(std::move(p.second)); + } + } + } + + // In what follows, there are implicitly two graphs: + // + // 1. The original transitive reduction `H` containing the "happens-before" + // DAG wihtout transitive dependencies with `N` nodes. + // + // 2. The reduced DAG `H_red` with `M <= N` nodes where nodes `(u, v)` in `H` + // where `out-degree[u] = in-degree[v] = 1` are combined together to reduce + // the number of variables. This graph isn't explicitly stored in memory. + // Instead, for each node in `H_red`, a list of back references in `H` + // indicating + // + // Iterations happen over nodes IDs in `H_red`. There a few important + // observations (here `i`s and `j`s are over super node indices) + // ``` + // supernode[i].front() --/--> supernode[j].front() --> i -->_S' j + // out_degree_super[i] = out_degree[supernodes[i].back()] + // in_degree_super[i] = in_degree[supernodes[i].front()] + SCIP *scip = nullptr; + SCIPcreate(&scip); + SCIPsetMessagehdlr(scip, NULL); + SCIPincludeDefaultPlugins(scip); + SCIPcreateProbBasic(scip, "Minimize_Inversions"); + SCIPsetObjsense(scip, SCIP_OBJSENSE_MINIMIZE); + + const size_t M = supernodes.size(); + std::vector u(M); + std::vector> x( + M + 1, std::vector(M + 1, nullptr)); + + // Path variables: 1 <= u_i <= M + for (size_t i = 0; i < M; i++) { + const std::string name = "u_" + std::to_string(i); + SCIPcreateVarBasic(scip, &u[i], name.c_str(), 1.0, (double)M, 0.0, + SCIP_VARTYPE_CONTINUOUS); + SCIPaddVar(scip, u[i]); + } + + // All edges in the transitive reduction, add forward edge x_ij in {0, 1} + for (size_t i = 0; i < M; ++i) { + for (size_t j : H[supernodes[i].back()]) { + assert(supernodes[H_to_supernode[j]].front() == j); + j = H_to_supernode[j]; + assert(i < j); + assert(x[i][j] == nullptr); + std::string name = "x_" + std::to_string(i) + std::to_string(j); + SCIPcreateVarBasic( + scip, &x[i][j], name.c_str(), 0.0, 1.0, + tid(supernodes[i].back()) != tid(supernodes[j].front()), + SCIP_VARTYPE_BINARY); + SCIPaddVar(scip, x[i][j]); + } + } + + // All edges not contained in the transitive closure + // Add forward _and_ backward edges x_ij and x_ji + for (size_t i = 0; i < M; ++i) + for (size_t j = i + 1; j < M; j++) + if (!happens_before(supernodes[i].front(), supernodes[j].front())) { + assert(x[i][j] == nullptr); + assert(x[j][i] == nullptr); + const double w_e = + tid(supernodes[i].back()) != tid(supernodes[j].front()); + { + const std::string name = "x_" + std::to_string(i) + std::to_string(j); + SCIPcreateVarBasic(scip, &x[i][j], name.c_str(), 0.0, 1.0, w_e, + SCIP_VARTYPE_BINARY); + SCIPaddVar(scip, x[i][j]); + } + { + const std::string name = "x_" + std::to_string(j) + std::to_string(i); + SCIPcreateVarBasic(scip, &x[j][i], name.c_str(), 0.0, 1.0, w_e, + SCIP_VARTYPE_BINARY); + SCIPaddVar(scip, x[j][i]); + } + } + + // Source/sink variables --> only added to DAG's entrances + exits + for (size_t i = 0; i < M; ++i) { + if (out_degree[supernodes[i].back()] == 0) { + const std::string name = "x_" + std::to_string(i) + std::to_string(M); + SCIPcreateVarBasic(scip, &x[i][M], name.c_str(), 0.0, 1.0, 0.0, + SCIP_VARTYPE_BINARY); + SCIPaddVar(scip, x[i][M]); + } + + if (in_degree[supernodes[i].front()] == 0) { + const std::string name = "x_" + std::to_string(M) + std::to_string(i); + SCIPcreateVarBasic(scip, &x[M][i], name.c_str(), 0.0, 1.0, 0.0, + SCIP_VARTYPE_BINARY); + SCIPaddVar(scip, x[M][i]); + } + } + + // MTZ Constraints + // A. Exactly one exit and one entrance per node (including source/sink) + for (size_t i = 0; i < M + 1; ++i) { + SCIP_CONS *enter_cons = nullptr; + SCIP_CONS *leave_cons = nullptr; + const std::string enter = "enter_" + std::to_string(i); + const std::string leave = "leave_" + std::to_string(i); + SCIPcreateConsBasicLinear(scip, &enter_cons, enter.c_str(), 0, nullptr, + nullptr, 1.0, 1.0); + SCIPcreateConsBasicLinear(scip, &leave_cons, leave.c_str(), 0, nullptr, + nullptr, 1.0, 1.0); + for (size_t j = 0; j < M + 1; ++j) { + if (x[i][j]) SCIPaddCoefLinear(scip, leave_cons, x[i][j], 1.0); + if (x[j][i]) SCIPaddCoefLinear(scip, enter_cons, x[j][i], 1.0); + } + SCIPaddCons(scip, enter_cons); + SCIPaddCons(scip, leave_cons); + SCIPreleaseCons(scip, &enter_cons); + SCIPreleaseCons(scip, &leave_cons); + } + + // B. MTZ Subtour Elimination: + // -INF <= u_i - u_j + M*x_ij <= (M - 1) for i, j in [1, M], i != j + for (size_t i = 0; i < M; ++i) { + for (size_t j = 0; j < M; ++j) { + if (!x[i][j]) continue; + SCIP_CONS *cons = nullptr; + const std::string name = + "subtour_" + std::to_string(i) + "_" + std::to_string(j); + SCIPcreateConsBasicLinear(scip, &cons, name.c_str(), 0, nullptr, nullptr, + -SCIPinfinity(scip), (double)(M - 1)); + SCIPaddCoefLinear(scip, cons, u[i], 1.0); + SCIPaddCoefLinear(scip, cons, u[j], -1.0); + SCIPaddCoefLinear(scip, cons, x[i][j], (double)M); + SCIPaddCons(scip, cons); + SCIPreleaseCons(scip, &cons); + } + } + + // C. Precedence Constraints: INF >= u_target - u_i >= 1 + for (size_t i = 0; i < M; ++i) { + for (size_t target : H[supernodes[i].back()]) { + target = H_to_supernode[target]; + SCIP_CONS *cons = nullptr; + const std::string name = + "precedence_" + std::to_string(i) + "_" + std::to_string(target); + SCIPcreateConsBasicLinear(scip, &cons, name.c_str(), 0, nullptr, nullptr, + 1.0, SCIPinfinity(scip)); + SCIPaddCoefLinear(scip, cons, u[target], 1.0); + SCIPaddCoefLinear(scip, cons, u[i], -1.0); + SCIPaddCons(scip, cons); + SCIPreleaseCons(scip, &cons); + } + } + SCIPsolve(scip); + SCIP_SOL *sol = SCIPgetBestSol(scip); + + std::vector linearization; + struct NodePosition { + size_t id; + double u_value; + }; + + if (sol != nullptr) { + std::vector sequence; + sequence.reserve(M); + for (size_t i = 0; i < M; ++i) { + double val = SCIPgetSolVal(scip, sol, u[i]); + sequence.push_back({i, val}); + } + std::sort(sequence.begin(), sequence.end(), + [](const NodePosition &a, const NodePosition &b) { + return a.u_value < b.u_value; + }); + for (const auto &item : sequence) + std::move(supernodes[item.id].begin(), supernodes[item.id].end(), + std::back_inserter(linearization)); + } else { + std::cerr << "No solution found!" << std::endl; + return {}; + } + { + for (size_t i = 0; i < M + 1; ++i) { + if (i < M) SCIPreleaseVar(scip, &u[i]); + for (size_t j = 0; j < M + 1; ++j) { + if (x[i][j]) SCIPreleaseVar(scip, &x[i][j]); + } + } + SCIPfree(&scip); + } + + std::vector linearized_trace(N, nullptr); + for (size_t i = 0; i < N; i++) + linearized_trace[i] = get_transition(linearization[i]); + return linearized_trace; +#else + // Fallback to greedy if SCIP isn't available + return linearize_lowest_first(); +#endif +} + +std::vector +classic_dpor::dpor_context::linearize_lowest_first() const { + const auto adj_list = this->transitive_reduction(); + const size_t N = adj_list.size(); + const size_t INF = N + 1; + std::vector linearization; + std::vector ready_set; + std::vector in_degree(N, 0); + for (size_t u = 0; u < N; ++u) + for (size_t v : adj_list[u]) in_degree[v]++; + + for (size_t i = 0; i < N; ++i) + if (in_degree[i] == 0) ready_set.push_back(i); + + runner_id_t last_rid = RUNNER_ID_MAX; + while (!ready_set.empty()) { + size_t next_vertex = INF; + size_t selected_index = INF; + if (last_rid != RUNNER_ID_MAX) { + for (size_t i = 0; i < ready_set.size(); ++i) { + const runner_id_t r = get_transition(ready_set[i])->get_executor(); + if (r == last_rid) { + next_vertex = ready_set[i]; + selected_index = i; + break; // Found the best greedy match that avoids inversion + } + } + } + + // If no ID match, or at the start, pick the thread with the lowest id + if (next_vertex == INF) { + runner_id_t min_rid = RID_INVALID; + for (size_t i = 0; i < ready_set.size(); ++i) { + const runner_id_t r = get_transition(ready_set[i])->get_executor(); + if (r < min_rid) { + next_vertex = ready_set[i]; + selected_index = i; + min_rid = r; + } + } + } + + linearization.push_back(next_vertex); + last_rid = get_transition(next_vertex)->get_executor(); + + // Remove the selected vertex from the ready set + // Swap with the back and pop for O(1) removal from vector (since order in S + // doesn't matter) + std::swap(ready_set[selected_index], ready_set.back()); + ready_set.pop_back(); + + for (int v : adj_list[next_vertex]) { + in_degree[v]--; + if (in_degree[v] == 0) { + ready_set.push_back(v); + } + } + } + + std::vector linearized_trace(N, nullptr); + for (size_t i = 0; i < N; i++) + linearized_trace[i] = get_transition(linearization[i]); + return linearized_trace; +} diff --git a/src/mcmini/model_checking/reporter.cpp b/src/mcmini/model_checking/reporter.cpp new file mode 100644 index 0000000..f41ffb9 --- /dev/null +++ b/src/mcmini/model_checking/reporter.cpp @@ -0,0 +1,99 @@ +#include "mcmini/model_checking/reporter.hpp" + +#include "mcmini/signal.hpp" + +using namespace model; +using namespace model_checking; + +void reporter::dump_relinearized_trace(std::ostream &os, + const algorithm::context &c, + const stats &) const { + if (relinearize_traces) { + os << "\nRELINEARIZED TRACE\n"; + for (const auto &t : c.linearize_trace(use_optimal_linearization)) { + os << "thread " << t->get_executor() << ": " << t->to_string() << "\n"; + } + } +} + +void reporter::trace_completed(const algorithm::context &c, + const stats &stats) const { + if (!verbose) return; + std::cout << "TRACE " << stats.trace_id << "\n"; + for (const auto &t : c.get_model().get_trace()) { + std::cout << "thread " << t->get_executor() << ": " << t->to_string() + << "\n"; + } + dump_relinearized_trace(std::cout, c, stats); + std::cout << "\nNEXT THREAD OPERATIONS\n"; + for (const auto &tpair : c.get_model().get_pending_transitions()) { + std::cout << "thread " << tpair.first << ": " << tpair.second->to_string() + << "\n"; + } + std::cout.flush(); +} + +void reporter::deadlock(const algorithm::context &c, const stats &stats) const { + std::cout << "DEADLOCK (trace ID: " << stats.trace_id << ")" << std::endl; + for (const auto &t : c.get_model().get_trace()) { + std::cout << "thread " << t->get_executor() << ": " << t->to_string() + << "\n"; + } + dump_relinearized_trace(std::cout, c, stats); + std::cout << "\nNEXT THREAD OPERATIONS\n"; + for (const auto &tpair : c.get_model().get_pending_transitions()) { + std::cout << "thread " << tpair.first << ": " << tpair.second->to_string() + << "\n"; + } + std::cout.flush(); +} + +void reporter::undefined_behavior( + const algorithm::context &c, const stats &stats, + const undefined_behavior_exception &ub) const { + std::cout << "UNDEFINED BEHAVIOR:\n" << ub.what() << std::endl; + std::cout << "TRACE " << stats.trace_id << "\n"; + for (const auto &t : c.get_model().get_trace()) { + std::cout << "thread " << t->get_executor() << ": " << t->to_string() + << "\n"; + } + dump_relinearized_trace(std::cout, c, stats); + std::cout << "\nNEXT THREAD OPERATIONS\n"; + for (const auto &tpair : c.get_model().get_pending_transitions()) { + std::cout << "thread " << tpair.first << ": " << tpair.second->to_string() + << "\n"; + } + std::cout.flush(); +} + +void reporter::abnormal_termination( + const algorithm::context &c, const stats &stats, + const real_world::process::termination_error &ub) const { + std::cout << "Abnormally Termination (signo: " << ub.signo + << ", signal: " << sig_to_str.at(ub.signo) << "):\n" + << ub.what() << std::endl; + + std::cout << "TRACE " << stats.trace_id << "\n"; + for (const auto &t : c.get_model().get_trace()) { + std::cout << "thread " << t->get_executor() << ": " << t->to_string() + << "\n"; + } + dump_relinearized_trace(std::cout, c, stats); + const transition *terminator = + c.get_model().get_pending_transition_for(ub.culprit); + std::cout << "thread " << terminator->get_executor() << ": " + << terminator->to_string() << "\n"; + + std::cout << "\nNEXT THREAD OPERATIONS\n"; + for (const auto &tpair : c.get_model().get_pending_transitions()) { + if (tpair.first == terminator->get_executor()) { + std::cout << "thread " << tpair.first << ": executing" + << "\n"; + } else { + std::cout << "thread " << tpair.first << ": " << tpair.second->to_string() + << "\n"; + } + } + std::cout << stats.total_transitions + 1 << " total transitions executed" + << std::endl; +} diff --git a/src/mcmini/real_world/fork_process_source.cpp b/src/mcmini/real_world/fork_process_source.cpp index 7ef730f..21a5a94 100644 --- a/src/mcmini/real_world/fork_process_source.cpp +++ b/src/mcmini/real_world/fork_process_source.cpp @@ -42,17 +42,17 @@ using namespace extensions; logger fps("fork"); logger mfps("mtf"); -fork_process_source::fork_process_source(real_world::target&& tp) +fork_process_source::fork_process_source(real_world::target &&tp) : fork_process_source(tp) {} fork_process_source::fork_process_source( - const real_world::target& target_program) + const real_world::target &target_program) : template_program(extensions::make_unique(target_program)) { this->template_program->set_preload_libmcmini(); } multithreaded_fork_process_source::multithreaded_fork_process_source( - const std::string& ckpt_file) { + const std::string &ckpt_file) { log_debug(mfps) << "Initialized mtf source with checkpoint file `" << ckpt_file << "`"; this->template_program = extensions::make_unique( @@ -71,7 +71,7 @@ multithreaded_fork_process_source::multithreaded_fork_process_source( } std::unique_ptr fork_process_source::make_new_process() { - shared_memory_region* rw_region = + shared_memory_region *rw_region = xpc_resources::get_instance().get_rw_region(); // 1. Set up phase (LD_PRELOAD, binary sempahores, template process creation) @@ -91,18 +91,18 @@ std::unique_ptr fork_process_source::make_new_process() { // 3. If the current template process is alive, tell it to spawn a new // process and then wait for it to successfully call `fork(2)` to tell us // about its new child. - const volatile template_process_t* tstruct = + const volatile template_process_t *tstruct = &(rw_region->as()->tpt); - signal_tracker::set_sem((sem_t*)&tstruct->mcmini_process_sem); - if (sem_post((sem_t*)&tstruct->libmcmini_sem) != 0) { + signal_tracker::set_sem((sem_t *)&tstruct->mcmini_process_sem); + if (sem_post((sem_t *)&tstruct->libmcmini_sem) != 0) { throw process_source::process_creation_exception( "The template process (" + std::to_string(this->template_process_handle->get_pid()) + ") was not synchronized with correctly: " + std::string(strerror(errno))); } - int rc = signal_tracker::sig_semwait((sem_t*)&tstruct->mcmini_process_sem); + int rc = signal_tracker::sig_semwait((sem_t *)&tstruct->mcmini_process_sem); if (rc != 0) { throw process_source::process_creation_exception( "The template process (" + @@ -110,7 +110,7 @@ std::unique_ptr fork_process_source::make_new_process() { ") was not synchronized with correctly: " + std::string(strerror(errno))); } - + signal_tracker::set_sem(nullptr); if (signal_tracker::instance().try_consume_signal(SIGCHLD)) { // TODO: At this point, McMini may have multiple child processes. // Calling `prctl(PR_SETSUBREAPER)` in the branch processes means @@ -151,16 +151,15 @@ void multithreaded_fork_process_source::make_new_template_process() { // Here we need, in addition, to wait for the template thread // to have heard back from all userspace threads before declaing the template // process is ready. - shared_memory_region* rw_region = + shared_memory_region *rw_region = xpc_resources::get_instance().get_rw_region(); - const volatile template_process_t* tstruct = + const volatile template_process_t *tstruct = &(rw_region->as()->tpt); - signal_tracker::set_sem((sem_t*)&tstruct->mcmini_process_sem); - + signal_tracker::set_sem((sem_t *)&tstruct->mcmini_process_sem); fork_process_source::make_new_template_process(); log_verbose(mfps) << "Waiting for the template thread to stabilize"; - int rc = signal_tracker::sig_semwait((sem_t*)&tstruct->mcmini_process_sem); + int rc = signal_tracker::sig_semwait((sem_t *)&tstruct->mcmini_process_sem); if (rc != 0) { throw process_source::process_creation_exception( "The template process (" + @@ -168,4 +167,5 @@ void multithreaded_fork_process_source::make_new_template_process() { ") was not synchronized with correctly: " + std::string(strerror(errno))); } + signal_tracker::set_sem(nullptr); } diff --git a/src/mcmini/real_world/local_linux_process.cpp b/src/mcmini/real_world/local_linux_process.cpp index 75c4603..0b34b6a 100644 --- a/src/mcmini/real_world/local_linux_process.cpp +++ b/src/mcmini/real_world/local_linux_process.cpp @@ -85,6 +85,7 @@ volatile runner_mailbox *local_linux_process::execute_runner(runner_id_t id) { // OK for now, but should be handled in the future. errno = 0; signal_tracker::sig_semwait((sem_t *)&rmb->model_side_sem); + signal_tracker::set_sem(nullptr); if (signal_tracker::instance().try_consume_signal(SIGCHLD)) { // TODO: Get the true failure status from the template process via // e.g. shared memory. diff --git a/src/mcmini/signal.cpp b/src/mcmini/signal.cpp index f7bcbd6..9e4ca00 100644 --- a/src/mcmini/signal.cpp +++ b/src/mcmini/signal.cpp @@ -36,6 +36,7 @@ const std::unordered_map sig_to_str = { }; sem_t *signal_tracker::current_sem = nullptr; +logging::logger signal_logger("signal_tracker"); void signal_tracker_sig_handler(int sig, siginfo_t *, void *) { // TODO: If multiple SIGCHLDs are received before the main thread has a chance @@ -50,6 +51,7 @@ void signal_tracker_sig_handler(int sig, siginfo_t *, void *) { // Reset the global value BEFORE posting. We assume only TWO threads inside // of McMini at this point. sem_post(set_sem); + signal_tracker::instance().set_sem(nullptr); } } @@ -135,7 +137,7 @@ static void handle_incoming_signals(sem_t *rendez_vous) { // """ int sig; sigwait(&all_signals, &sig); - std::cout << "Received signal: " << sig_to_str.at(sig) << std::endl; + log_debug(signal_logger) << "Received signal: " << sig_to_str.at(sig); if (signal_tracker::is_bad_signal(sig)) { std::terminate(); } else if (sig == SIGINT) {