From 065c418ea6b468a878169697eba9c14e0f233b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmnn?= Date: Mon, 28 Sep 2020 14:30:22 +0200 Subject: [PATCH 001/174] zmq powered gui --- Cqse.Teamscale.Profiler.Dotnet.sln | 18 ++- ProfilerGui/App.config | 6 + ProfilerGui/App.xaml | 9 ++ ProfilerGui/App.xaml.cs | 56 +++++++++ ProfilerGui/MainWindow.xaml | 23 ++++ ProfilerGui/MainWindow.xaml.cs | 62 ++++++++++ ProfilerGui/MainWindowVM.cs | 70 +++++++++++ ProfilerGui/ProfilerGui.csproj | 112 ++++++++++++++++++ ProfilerGui/Properties/AssemblyInfo.cs | 55 +++++++++ ProfilerGui/Properties/Resources.Designer.cs | 71 +++++++++++ ProfilerGui/Properties/Resources.resx | 117 +++++++++++++++++++ ProfilerGui/Properties/Settings.Designer.cs | 30 +++++ ProfilerGui/Properties/Settings.settings | 7 ++ ProfilerGui/packages.config | 4 + 14 files changed, 638 insertions(+), 2 deletions(-) create mode 100644 ProfilerGui/App.config create mode 100644 ProfilerGui/App.xaml create mode 100644 ProfilerGui/App.xaml.cs create mode 100644 ProfilerGui/MainWindow.xaml create mode 100644 ProfilerGui/MainWindow.xaml.cs create mode 100644 ProfilerGui/MainWindowVM.cs create mode 100644 ProfilerGui/ProfilerGui.csproj create mode 100644 ProfilerGui/Properties/AssemblyInfo.cs create mode 100644 ProfilerGui/Properties/Resources.Designer.cs create mode 100644 ProfilerGui/Properties/Resources.resx create mode 100644 ProfilerGui/Properties/Settings.Designer.cs create mode 100644 ProfilerGui/Properties/Settings.settings create mode 100644 ProfilerGui/packages.config diff --git a/Cqse.Teamscale.Profiler.Dotnet.sln b/Cqse.Teamscale.Profiler.Dotnet.sln index 5eb83bcc..118f7da9 100644 --- a/Cqse.Teamscale.Profiler.Dotnet.sln +++ b/Cqse.Teamscale.Profiler.Dotnet.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2027 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30517.126 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Profiler", "Profiler\Profiler.vcxproj", "{CC51C2F0-D0F2-41D9-B1D2-5108C1D3B8E6}" EndProject @@ -21,6 +21,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Profiler_Cpp_Test", "Profil EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DumpPdb", "DumpPdb\DumpPdb.csproj", "{BF294B13-B2BB-43B4-9F83-3AA0BE0DC448}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProfilerGui", "ProfilerGui\ProfilerGui.csproj", "{62C8C177-9CEA-41C1-8F11-180BE25DDCB4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -101,6 +103,18 @@ Global {BF294B13-B2BB-43B4-9F83-3AA0BE0DC448}.Release|Win32.Build.0 = Release|Any CPU {BF294B13-B2BB-43B4-9F83-3AA0BE0DC448}.Release|x64.ActiveCfg = Release|Any CPU {BF294B13-B2BB-43B4-9F83-3AA0BE0DC448}.Release|x64.Build.0 = Release|Any CPU + {62C8C177-9CEA-41C1-8F11-180BE25DDCB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62C8C177-9CEA-41C1-8F11-180BE25DDCB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62C8C177-9CEA-41C1-8F11-180BE25DDCB4}.Debug|Win32.ActiveCfg = Debug|Any CPU + {62C8C177-9CEA-41C1-8F11-180BE25DDCB4}.Debug|Win32.Build.0 = Debug|Any CPU + {62C8C177-9CEA-41C1-8F11-180BE25DDCB4}.Debug|x64.ActiveCfg = Debug|Any CPU + {62C8C177-9CEA-41C1-8F11-180BE25DDCB4}.Debug|x64.Build.0 = Debug|Any CPU + {62C8C177-9CEA-41C1-8F11-180BE25DDCB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62C8C177-9CEA-41C1-8F11-180BE25DDCB4}.Release|Any CPU.Build.0 = Release|Any CPU + {62C8C177-9CEA-41C1-8F11-180BE25DDCB4}.Release|Win32.ActiveCfg = Release|Any CPU + {62C8C177-9CEA-41C1-8F11-180BE25DDCB4}.Release|Win32.Build.0 = Release|Any CPU + {62C8C177-9CEA-41C1-8F11-180BE25DDCB4}.Release|x64.ActiveCfg = Release|Any CPU + {62C8C177-9CEA-41C1-8F11-180BE25DDCB4}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ProfilerGui/App.config b/ProfilerGui/App.config new file mode 100644 index 00000000..731f6de6 --- /dev/null +++ b/ProfilerGui/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ProfilerGui/App.xaml b/ProfilerGui/App.xaml new file mode 100644 index 00000000..b255f77b --- /dev/null +++ b/ProfilerGui/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/ProfilerGui/App.xaml.cs b/ProfilerGui/App.xaml.cs new file mode 100644 index 00000000..e01bbfc3 --- /dev/null +++ b/ProfilerGui/App.xaml.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using ZeroMQ; + +namespace ProfilerGui +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + private const string Endpoint = "tcp://127.0.0.1:5555"; + private ZContext context; + private ZSocket socket; + + protected override void OnStartup(StartupEventArgs e) + { + context = new ZContext(); + socket = new ZSocket(context, ZSocketType.PUB); + socket.Bind(Endpoint); + } + + protected override void OnExit(ExitEventArgs e) + { + socket.Dispose(); + context.Dispose(); + } + + internal void StopProfiling() + { + using (var frame = new ZFrame("profilerStop")) + { + socket.Send(frame); + } + } + + internal void StartProfiling(string testName = null) + { + string message = "profilerStart"; + if (!string.IsNullOrWhiteSpace(testName)) + { + message += " " + testName; + } + + using (var frame = new ZFrame(message)) + { + socket.Send(frame); + } + } + } +} diff --git a/ProfilerGui/MainWindow.xaml b/ProfilerGui/MainWindow.xaml new file mode 100644 index 00000000..f5c5627b --- /dev/null +++ b/ProfilerGui/MainWindow.xaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + `; +} + +render(html`<${Main}/>`, document.body); \ No newline at end of file diff --git a/Commander.Server/wwwroot/logo.svg b/Commander.Server/wwwroot/logo.svg new file mode 100644 index 00000000..27cf2a54 --- /dev/null +++ b/Commander.Server/wwwroot/logo.svg @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/Cqse.Teamscale.Profiler.Dotnet.sln b/Cqse.Teamscale.Profiler.Dotnet.sln index 843201f9..65faa1c0 100644 --- a/Cqse.Teamscale.Profiler.Dotnet.sln +++ b/Cqse.Teamscale.Profiler.Dotnet.sln @@ -1,7 +1,7 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30711.63 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Profiler", "Profiler\Profiler.vcxproj", "{CC51C2F0-D0F2-41D9-B1D2-5108C1D3B8E6}" EndProject @@ -31,6 +31,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .appveyor.yml = .appveyor.yml EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Commander.Server", "Commander.Server\Commander.Server.csproj", "{E3667152-6ED8-4CEB-8BF7-DF8E2FACA80B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commander.Cli", "Commander.Cli\Commander.Cli.csproj", "{508A1D10-3D58-4293-8C8F-ADBC7FEE2592}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -135,6 +139,30 @@ Global {1DA1BE8D-9FD4-4F7F-8431-EDBEDCCD533E}.Release|Win32.Build.0 = Release|Any CPU {1DA1BE8D-9FD4-4F7F-8431-EDBEDCCD533E}.Release|x64.ActiveCfg = Release|Any CPU {1DA1BE8D-9FD4-4F7F-8431-EDBEDCCD533E}.Release|x64.Build.0 = Release|Any CPU + {E3667152-6ED8-4CEB-8BF7-DF8E2FACA80B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E3667152-6ED8-4CEB-8BF7-DF8E2FACA80B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3667152-6ED8-4CEB-8BF7-DF8E2FACA80B}.Debug|Win32.ActiveCfg = Debug|Any CPU + {E3667152-6ED8-4CEB-8BF7-DF8E2FACA80B}.Debug|Win32.Build.0 = Debug|Any CPU + {E3667152-6ED8-4CEB-8BF7-DF8E2FACA80B}.Debug|x64.ActiveCfg = Debug|Any CPU + {E3667152-6ED8-4CEB-8BF7-DF8E2FACA80B}.Debug|x64.Build.0 = Debug|Any CPU + {E3667152-6ED8-4CEB-8BF7-DF8E2FACA80B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E3667152-6ED8-4CEB-8BF7-DF8E2FACA80B}.Release|Any CPU.Build.0 = Release|Any CPU + {E3667152-6ED8-4CEB-8BF7-DF8E2FACA80B}.Release|Win32.ActiveCfg = Release|Any CPU + {E3667152-6ED8-4CEB-8BF7-DF8E2FACA80B}.Release|Win32.Build.0 = Release|Any CPU + {E3667152-6ED8-4CEB-8BF7-DF8E2FACA80B}.Release|x64.ActiveCfg = Release|Any CPU + {E3667152-6ED8-4CEB-8BF7-DF8E2FACA80B}.Release|x64.Build.0 = Release|Any CPU + {508A1D10-3D58-4293-8C8F-ADBC7FEE2592}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {508A1D10-3D58-4293-8C8F-ADBC7FEE2592}.Debug|Any CPU.Build.0 = Debug|Any CPU + {508A1D10-3D58-4293-8C8F-ADBC7FEE2592}.Debug|Win32.ActiveCfg = Debug|Any CPU + {508A1D10-3D58-4293-8C8F-ADBC7FEE2592}.Debug|Win32.Build.0 = Debug|Any CPU + {508A1D10-3D58-4293-8C8F-ADBC7FEE2592}.Debug|x64.ActiveCfg = Debug|Any CPU + {508A1D10-3D58-4293-8C8F-ADBC7FEE2592}.Debug|x64.Build.0 = Debug|Any CPU + {508A1D10-3D58-4293-8C8F-ADBC7FEE2592}.Release|Any CPU.ActiveCfg = Release|Any CPU + {508A1D10-3D58-4293-8C8F-ADBC7FEE2592}.Release|Any CPU.Build.0 = Release|Any CPU + {508A1D10-3D58-4293-8C8F-ADBC7FEE2592}.Release|Win32.ActiveCfg = Release|Any CPU + {508A1D10-3D58-4293-8C8F-ADBC7FEE2592}.Release|Win32.Build.0 = Release|Any CPU + {508A1D10-3D58-4293-8C8F-ADBC7FEE2592}.Release|x64.ActiveCfg = Release|Any CPU + {508A1D10-3D58-4293-8C8F-ADBC7FEE2592}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -143,6 +171,6 @@ Global SolutionGuid = {4E1B4522-5DA4-4169-99DD-EF8C6B1E598F} EndGlobalSection GlobalSection(SpecExplorer.ActivityCompletionStatus) = preSolution - SpecExplorer.ActivityCompletionStatus = TestExisting: ;ModelFromScratch: + SpecExplorer.ActivityCompletionStatus = TestExisting: ;ModelFromScratch: EndGlobalSection EndGlobal From 717a669ccf426cd0de318ba050509958d308217a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmnn?= Date: Fri, 1 Apr 2022 15:32:08 +0200 Subject: [PATCH 094/174] Review --- UploadDaemon/Report/Testwise/TestwiseCoverageReport.cs | 6 +++--- UploadDaemon/UploadTask.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/UploadDaemon/Report/Testwise/TestwiseCoverageReport.cs b/UploadDaemon/Report/Testwise/TestwiseCoverageReport.cs index f8099ec4..0ba019be 100644 --- a/UploadDaemon/Report/Testwise/TestwiseCoverageReport.cs +++ b/UploadDaemon/Report/Testwise/TestwiseCoverageReport.cs @@ -15,14 +15,14 @@ public class TestwiseCoverageReport : ICoverageReport public bool Partial { get; } [JsonProperty("tests")] - public List Tests { get; } + public Test[] Tests { get; } public TestwiseCoverageReport(params Test[] tests) : this(false, tests) {} public TestwiseCoverageReport(bool partial, params Test[] tests) { Partial = partial; - Tests = tests.ToList(); + Tests = tests; } /// @@ -43,7 +43,7 @@ public ICoverageReport Union(ICoverageReport coverageReport) } IDictionary mergedCoverage = new Dictionary(); - foreach(Test test in new[] { Tests, other.Tests }.SelectMany(tests => tests)) + foreach (Test test in this.Tests.Concat(other.Tests)) { if (mergedCoverage.ContainsKey(test.UniformPath)) { diff --git a/UploadDaemon/UploadTask.cs b/UploadDaemon/UploadTask.cs index 9f423328..8ee52c66 100644 --- a/UploadDaemon/UploadTask.cs +++ b/UploadDaemon/UploadTask.cs @@ -165,7 +165,7 @@ private void ProcessLineCoverage(TraceFile trace, Archive archive, Config config PrefixTestPaths(processConfig, testwiseCoverageReport); if (processConfig.PartialCoverageReport) { - coverageReport = new TestwiseCoverageReport(true, testwiseCoverageReport.Tests.ToArray()); + coverageReport = new TestwiseCoverageReport(true, testwiseCoverageReport.Tests); } } @@ -272,7 +272,7 @@ private static void ProcessMethodCoverage(TraceFile trace, Archive archive, Conf /// forgetting awaits all over the place. Therefore, we explicitly wait for uploads now here and there's no /// need to await anything further up the call stack. /// - static T RunSync(Task task) + private static T RunSync(Task task) { task.Wait(); return task.Result; From 6717a8e67f3cab220148cc5478c68366122f8066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmann?= Date: Tue, 12 Apr 2022 23:37:38 +0200 Subject: [PATCH 095/174] review --- Commander.Server/ProfilerTestController.cs | 21 +++++++++++++++++-- .../wwwroot/bootstrap/bootstrap.min.css | 3 +-- Commander.Server/wwwroot/index.js | 14 ++++++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Commander.Server/ProfilerTestController.cs b/Commander.Server/ProfilerTestController.cs index 2f7aa14e..ea0b2348 100644 --- a/Commander.Server/ProfilerTestController.cs +++ b/Commander.Server/ProfilerTestController.cs @@ -1,5 +1,6 @@ using Cqse.Teamscale.Profiler.Commons.Ipc; using Microsoft.AspNetCore.Mvc; +using System.Text.Json.Serialization; using System.Web; namespace Cqse.Teamscale.Profiler.Commander.Server @@ -22,7 +23,7 @@ public string GetCurrent() } [HttpPost("start/{testName}")] - public void startTest(string testName) + public void StartTest(string testName) { if (string.IsNullOrEmpty(testName)) { @@ -33,9 +34,25 @@ public void startTest(string testName) } [HttpPost("stop/{result}")] - public void stopTest(TestExecutionResult result) + public void StopTest(TestExecutionResult result) { profilerIpc.EndTest(result); } + + /// + /// Legacy end test to match the JaCoCo API. + /// + [HttpPost("end/{name}")] + public void EndTest(string name, [FromBody] TestResultDto result) + { + profilerIpc.EndTest(result.Result); + } + + public class TestResultDto + { + [JsonConverter(typeof(JsonStringEnumConverter))] + public TestExecutionResult Result { get; set; } + public string? Message { get; set; } + } } } diff --git a/Commander.Server/wwwroot/bootstrap/bootstrap.min.css b/Commander.Server/wwwroot/bootstrap/bootstrap.min.css index 02ae65b5..cf6afa8b 100644 --- a/Commander.Server/wwwroot/bootstrap/bootstrap.min.css +++ b/Commander.Server/wwwroot/bootstrap/bootstrap.min.css @@ -3,5 +3,4 @@ * Copyright 2011-2021 The Bootstrap Authors * Copyright 2011-2021 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-rgb:33,37,41;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file + */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-rgb:33,37,41;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} \ No newline at end of file diff --git a/Commander.Server/wwwroot/index.js b/Commander.Server/wwwroot/index.js index 3b853b3a..ef2b0193 100644 --- a/Commander.Server/wwwroot/index.js +++ b/Commander.Server/wwwroot/index.js @@ -33,7 +33,19 @@ function Controls() { setTestName(e.target.value); } + function onKeyPress(e) { + if (e.key === "Enter") { + e.preventDefault(); + startTest(); + return true; + } + } + async function startTest() { + if (!testName) { + return; + } + setRunning(true); const response = await fetch("test/start/" + encodeURIComponent(testName), { method: 'POST' }); if (!response.ok) { @@ -74,7 +86,7 @@ function Controls() { return html`
- +
From b083f252d8bc6f3ae6b869a0d06815c6c81f9b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmann?= Date: Wed, 13 Apr 2022 08:28:10 +0200 Subject: [PATCH 096/174] add logging --- Commander.Server/ProfilerTestController.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Commander.Server/ProfilerTestController.cs b/Commander.Server/ProfilerTestController.cs index ea0b2348..bf14f1b1 100644 --- a/Commander.Server/ProfilerTestController.cs +++ b/Commander.Server/ProfilerTestController.cs @@ -10,10 +10,12 @@ namespace Cqse.Teamscale.Profiler.Commander.Server public class ProfilerTestController : ControllerBase { private readonly ProfilerIpc profilerIpc; + private readonly ILogger logger; - public ProfilerTestController(ProfilerIpc profilerIpc) + public ProfilerTestController(ProfilerIpc profilerIpc, ILogger logger) { this.profilerIpc = profilerIpc; + this.logger = logger; } [HttpGet] @@ -30,12 +32,14 @@ public void StartTest(string testName) throw new BadHttpRequestException("Test name may not be empty"); } + logger.LogInformation("Starting test: {}", testName); profilerIpc.StartTest(HttpUtility.UrlDecode(testName)); } [HttpPost("stop/{result}")] public void StopTest(TestExecutionResult result) { + logger.LogInformation("Stopping test: {}; Result: {}", GetCurrent(), result); profilerIpc.EndTest(result); } @@ -45,6 +49,7 @@ public void StopTest(TestExecutionResult result) [HttpPost("end/{name}")] public void EndTest(string name, [FromBody] TestResultDto result) { + logger.LogInformation("Stopping test (JaCoCo endpoint): {}; Result: {}", name, result.Result); profilerIpc.EndTest(result.Result); } From 7cece3024ee7d1ffeed9304d902509fcf1d561c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmann?= Date: Wed, 13 Apr 2022 09:03:22 +0200 Subject: [PATCH 097/174] add quiet mode --- Commander.Cli/Program.cs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Commander.Cli/Program.cs b/Commander.Cli/Program.cs index 91f85b7e..45ba9acf 100644 --- a/Commander.Cli/Program.cs +++ b/Commander.Cli/Program.cs @@ -3,6 +3,8 @@ var profilerIpc = new ProfilerIpc(new IpcConfig()); +bool quiet = args.Contains("-q") || args.Contains("--quiet"); + while(true) { Command nextCommand = Command.Start; @@ -24,7 +26,7 @@ bool ExecuteCommand(Command command, string? arg) { case Command.Start: profilerIpc.StartTest(arg); - Help($"Started test {arg}"); + PrintUnlessQuiet($"Started test {arg}"); return false; case Command.Stop: if (arg?.Length > 1) @@ -33,12 +35,12 @@ bool ExecuteCommand(Command command, string? arg) if (Enum.TryParse(arg, out TestExecutionResult result)) { profilerIpc.EndTest(result); - Help($"Stopped test with result {arg}"); + PrintUnlessQuiet($"Stopped test with result {arg}"); } return false; } - Help($"Unknown test result {arg}, use one of these values: {CommandAttribute.ValidResultValues}"); + PrintUnlessQuiet($"Unknown test result {arg}, use one of these values: {CommandAttribute.ValidResultValues}"); return false; case Command.Exit: return true; @@ -50,15 +52,15 @@ bool ExecuteCommand(Command command, string? arg) (Command, string?) WaitForCommand(params Command[] commands) { Dictionary availableCommands = commands.Append(Command.Exit).ToDictionary(command => command.ToString().ToLower(), command => command); - Help("Available commands:"); + PrintUnlessQuiet("Available commands:"); foreach(Command command in availableCommands.Values) { CommandAttribute attribute = CommandAttribute.GetAttribute(command); string synopsis = string.Join(" ", command.ToString().ToLower(), attribute.Argument); - Help($" * {synopsis}: {attribute.Description}"); + PrintUnlessQuiet($" * {synopsis}: {attribute.Description}"); } - Help(string.Empty); + PrintUnlessQuiet(string.Empty); while(true) { string[]? input = Console.ReadLine()?.Split(" ", 2); @@ -67,7 +69,7 @@ bool ExecuteCommand(Command command, string? arg) continue; } - Help(string.Empty); + PrintUnlessQuiet(string.Empty); if (availableCommands.TryGetValue(input[0].ToLower(), out Command command)) { string? arg = null; @@ -78,13 +80,16 @@ bool ExecuteCommand(Command command, string? arg) return (command, arg); } - Help($"Unknown command: {input[0]}"); + PrintUnlessQuiet($"Unknown command: {input[0]}"); } } -void Help(string message) +void PrintUnlessQuiet(string message) { - Console.WriteLine(message); + if (!quiet) + { + Console.WriteLine(message); + } } class CommandAttribute : Attribute From 889d1580bc26c48f4c3d2a9ae303bceb3ae17257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmann?= Date: Wed, 13 Apr 2022 11:02:29 +0200 Subject: [PATCH 098/174] additional null checks --- Commander.Cli/Program.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Commander.Cli/Program.cs b/Commander.Cli/Program.cs index 45ba9acf..62dc4c3d 100644 --- a/Commander.Cli/Program.cs +++ b/Commander.Cli/Program.cs @@ -25,11 +25,15 @@ bool ExecuteCommand(Command command, string? arg) switch(command) { case Command.Start: + ArgumentNullException.ThrowIfNull(arg, nameof(arg)); + profilerIpc.StartTest(arg); PrintUnlessQuiet($"Started test {arg}"); return false; case Command.Stop: - if (arg?.Length > 1) + ArgumentNullException.ThrowIfNull(arg, nameof(arg)); + + if (arg.Length > 1) { arg = char.ToUpper(arg[0]) + arg.Substring(1).ToLower(); if (Enum.TryParse(arg, out TestExecutionResult result)) From 3d3a934ae14441a31c31fda6903d95b8e129c39c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmann?= Date: Wed, 13 Apr 2022 11:40:56 +0200 Subject: [PATCH 099/174] add nullable library --- Commander.Cli/Program.cs | 2 +- Commons/Commons.csproj | 3 +++ Commons/Ipc/IpcServer.cs | 2 +- Commons/Ipc/ZmqIpcServer.cs | 8 ++++---- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Commander.Cli/Program.cs b/Commander.Cli/Program.cs index 62dc4c3d..22c88002 100644 --- a/Commander.Cli/Program.cs +++ b/Commander.Cli/Program.cs @@ -112,7 +112,7 @@ public CommandAttribute(string description, string? argument = null) public static CommandAttribute GetAttribute(Command command) { MemberInfo memberInfo = typeof(Command).GetMember(command.ToString()).First(); - return memberInfo.GetCustomAttribute(); + return memberInfo.GetCustomAttribute()!; } } diff --git a/Commons/Commons.csproj b/Commons/Commons.csproj index 0786b786..11d0e39b 100644 --- a/Commons/Commons.csproj +++ b/Commons/Commons.csproj @@ -10,11 +10,14 @@ Teamscale Ephemeral .NET Profiler Commander Commander for sending test start/end signals to Teamscale Ephemeral .NET Profiler instances running it TIA mode. true + 8.0 + enable + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Commons/Ipc/IpcServer.cs b/Commons/Ipc/IpcServer.cs index ece12006..a4486ba2 100644 --- a/Commons/Ipc/IpcServer.cs +++ b/Commons/Ipc/IpcServer.cs @@ -12,7 +12,7 @@ public abstract class IpcServer : IDisposable protected readonly IpcConfig config; protected readonly RequestHandler requestHandler; - private readonly Thread requestHandlerThread; + private readonly Thread? requestHandlerThread; public IpcServer(IpcConfig config, RequestHandler requestHandler, bool createThread = false) { diff --git a/Commons/Ipc/ZmqIpcServer.cs b/Commons/Ipc/ZmqIpcServer.cs index 7b0dd374..322713e5 100644 --- a/Commons/Ipc/ZmqIpcServer.cs +++ b/Commons/Ipc/ZmqIpcServer.cs @@ -6,9 +6,9 @@ namespace Cqse.Teamscale.Profiler.Commons.Ipc { public class ZmqIpcServer : IpcServer { - private NetMQPoller poller; - private PublisherSocket publishSocket; - private ResponseSocket responseSocket; + private NetMQPoller? poller; + private PublisherSocket? publishSocket; + private ResponseSocket? responseSocket; public ZmqIpcServer(IpcConfig config, RequestHandler requestHandler) : base(config, requestHandler, false) { @@ -38,7 +38,7 @@ override protected void StartPublisher() public override void Publish(params string[] frames) { - this.publishSocket.SendMultipartMessage(new NetMQMessage(frames.Select(frame => new NetMQFrame(frame)))); + this.publishSocket?.SendMultipartMessage(new NetMQMessage(frames.Select(frame => new NetMQFrame(frame)))); } public override void Dispose() From 823bac71ea9ac03b0e698ad9127eae3217b12a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmann?= Date: Wed, 13 Apr 2022 13:14:02 +0200 Subject: [PATCH 100/174] forbid setting test name to null --- Commons/Ipc/ProfilerIpc.cs | 11 +++++++++-- Profiler_Test/Tia/TiaProfilerTest.cs | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Commons/Ipc/ProfilerIpc.cs b/Commons/Ipc/ProfilerIpc.cs index d05ed3e4..5e07a452 100644 --- a/Commons/Ipc/ProfilerIpc.cs +++ b/Commons/Ipc/ProfilerIpc.cs @@ -9,8 +9,10 @@ public class ProfilerIpc : IDisposable private readonly IpcServer ipcServer; - // TODO (MP) We need to define a contract for test names. Is empty allowed? May it be null? - public string TestName { get; private set; } = string.Empty; + /// + /// The name of the current test, empty string if no test is running. + /// + public string TestName { get; private set; } = String.Empty; public IpcConfig Config { get; } @@ -41,6 +43,11 @@ protected virtual string HandleRequest(string message) public void StartTest(string testName) { + if (string.IsNullOrEmpty(testName)) + { + throw new ArgumentException("Test name may not be empty or null"); + } + logger.Info("Broadcasting start of test {testName}", testName); this.TestName = testName; ipcServer.Publish("test:start", testName); diff --git a/Profiler_Test/Tia/TiaProfilerTest.cs b/Profiler_Test/Tia/TiaProfilerTest.cs index 485f74bd..5ff30993 100644 --- a/Profiler_Test/Tia/TiaProfilerTest.cs +++ b/Profiler_Test/Tia/TiaProfilerTest.cs @@ -57,6 +57,13 @@ public void TestCaseDefinedBeforeStartup() Assert.That(testResult.TestCases[1].TraceLines, Has.Some.Matches("^(Inlines|Jitted|Called)")); } + [TestCase(null)] + [TestCase("")] + public void TestCaseMayNotBeNullOrEmpty(string testName) + { + Assert.Throws(() => profilerIpc.StartTest(testName)); + } + [Test] public void TestCaseWithSpecialCharacters() { From f1a85b32835973e8381016b0bb4ab124b22aaa66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmann?= Date: Wed, 13 Apr 2022 13:22:34 +0200 Subject: [PATCH 101/174] =?UTF-8?q?=F0=9F=A4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Commander.Cli/Program.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Commander.Cli/Program.cs b/Commander.Cli/Program.cs index 22c88002..0e10ec2a 100644 --- a/Commander.Cli/Program.cs +++ b/Commander.Cli/Program.cs @@ -35,8 +35,7 @@ bool ExecuteCommand(Command command, string? arg) if (arg.Length > 1) { - arg = char.ToUpper(arg[0]) + arg.Substring(1).ToLower(); - if (Enum.TryParse(arg, out TestExecutionResult result)) + if (Enum.TryParse(arg, ignoreCase: true, out TestExecutionResult result)) { profilerIpc.EndTest(result); PrintUnlessQuiet($"Stopped test with result {arg}"); From 4e08202e1373247f29f298c4af5ca6c337615622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmann?= Date: Wed, 13 Apr 2022 13:27:57 +0200 Subject: [PATCH 102/174] better error handling --- Commander.Cli/Program.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Commander.Cli/Program.cs b/Commander.Cli/Program.cs index 0e10ec2a..55b7e2ca 100644 --- a/Commander.Cli/Program.cs +++ b/Commander.Cli/Program.cs @@ -39,11 +39,10 @@ bool ExecuteCommand(Command command, string? arg) { profilerIpc.EndTest(result); PrintUnlessQuiet($"Stopped test with result {arg}"); + return false; } - - return false; } - PrintUnlessQuiet($"Unknown test result {arg}, use one of these values: {CommandAttribute.ValidResultValues}"); + Console.Error.WriteLine($"Unknown test result: {arg}"); return false; case Command.Exit: return true; @@ -83,7 +82,7 @@ bool ExecuteCommand(Command command, string? arg) return (command, arg); } - PrintUnlessQuiet($"Unknown command: {input[0]}"); + Console.Error.WriteLine($"Unknown command: {input[0]}"); } } From af822feebf70a32b9e9fc0e07182cb7eaf95292f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Tue, 26 Apr 2022 18:03:46 +0200 Subject: [PATCH 103/174] Update Platform version and toolset --- Profiler/Profiler.vcxproj | 9 +++++---- Profiler/packages.config | 3 +-- Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Profiler/Profiler.vcxproj b/Profiler/Profiler.vcxproj index 7994baa4..801759ff 100644 --- a/Profiler/Profiler.vcxproj +++ b/Profiler/Profiler.vcxproj @@ -22,8 +22,8 @@ {CC51C2F0-D0F2-41D9-B1D2-5108C1D3B8E6} Profiler Win32Proj - 10.0.17763.0 - v141 + 10.0.22000.0 + v143 @@ -112,6 +112,7 @@ stdcpp14 true true + Default corguids.lib;version.lib;Ws2_32.lib;Iphlpapi.lib;%(AdditionalDependencies) @@ -319,7 +320,7 @@ - + @@ -334,6 +335,6 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/Profiler/packages.config b/Profiler/packages.config index fa9d0421..3def5e24 100644 --- a/Profiler/packages.config +++ b/Profiler/packages.config @@ -1,5 +1,4 @@  - - + \ No newline at end of file diff --git a/Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj b/Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj index c8cac3cc..5625f0ae 100644 --- a/Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj +++ b/Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj @@ -23,21 +23,21 @@ {A71C4191-5B96-42A6-BD89-32FF4ECC4C0B} Win32Proj Profiler_Cpp_Test - 10.0.17763.0 + 10.0.22000.0 NativeUnitTestProject DynamicLibrary true - v141 + v143 Unicode false DynamicLibrary false - v141 + v143 true Unicode false @@ -45,14 +45,14 @@ DynamicLibrary true - v141 + v143 Unicode false DynamicLibrary false - v141 + v143 true Unicode false From 59171c620ce9ae843fee67c8c49c4bc157e35124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Tue, 26 Apr 2022 18:04:58 +0200 Subject: [PATCH 104/174] Remove ifdefs, rework, add tga toggle to options --- Profiler/CProfilerCallback.cpp | 109 +++++++++++++++---------------- Profiler/CProfilerCallback.h | 11 +--- Profiler/CProfilerCallbackBase.h | 24 ++++--- Profiler/CProfilerWorker.cpp | 3 +- Profiler/CProfilerWorker.h | 5 +- Profiler/config/Config.cpp | 3 +- Profiler/config/Config.h | 12 ++-- Profiler/log/TraceLog.cpp | 4 -- Profiler/log/TraceLog.h | 6 -- Profiler/utils/Ipc.h | 1 + Profiler/utils/MethodEnter.cpp | 3 +- 11 files changed, 77 insertions(+), 104 deletions(-) diff --git a/Profiler/CProfilerCallback.cpp b/Profiler/CProfilerCallback.cpp index f3a3fb2d..16a7eab6 100644 --- a/Profiler/CProfilerCallback.cpp +++ b/Profiler/CProfilerCallback.cpp @@ -59,8 +59,6 @@ void CProfilerCallback::ShutdownFromDllMainDetach() { getShutdownGuard().shutdownInstance(false); } - - CProfilerCallback::CProfilerCallback() { try { InitializeCriticalSection(&methodSetSynchronization); @@ -126,11 +124,8 @@ HRESULT CProfilerCallback::InitializeImplementation(IUnknown* pICorProfilerInfoU createDaemon().launch(traceLog); } -#ifdef TIA - if (config.isTiaEnabled()) { traceLog.info("TIA enabled. SUB: " + config.getTiaSubscribeSocket() + " REQ: " + config.getTiaRequestSocket()); - enableFunctionHooks = true; std::function testStartCallback = std::bind(&CProfilerCallback::onTestStart, this, std::placeholders::_1); std::function testEndCallback = std::bind(&CProfilerCallback::onTestEnd, this, std::placeholders::_1, std::placeholders::_2); std::function errorCallback = std::bind(&TraceLog::error, this->traceLog, std::placeholders::_1); @@ -143,7 +138,6 @@ HRESULT CProfilerCallback::InitializeImplementation(IUnknown* pICorProfilerInfoU traceLog.startTestCase(testName); } } -#endif char appPool[BUFFER_SIZE]; if (GetEnvironmentVariable("APP_POOL_ID", appPool, sizeof(appPool))) { @@ -165,12 +159,10 @@ HRESULT CProfilerCallback::InitializeImplementation(IUnknown* pICorProfilerInfoU return E_INVALIDARG; } - //TODO can we change event mask during running profiler? - DWORD dwEventMask = getEventMask(); - profilerInfo->SetEventMask(dwEventMask); -#ifdef TIA - profilerInfo->SetEnterLeaveFunctionHooks3((FunctionEnter3*)&FnEnterCallback, NULL, NULL); -#endif + adjustEventMask(); + if (config.isTiaEnabled()) { + profilerInfo->SetEnterLeaveFunctionHooks3((FunctionEnter3*)&FnEnterCallback, NULL, NULL); + } traceLog.logProcess(WindowsUtils::getPathOfThisProcess()); return S_OK; @@ -219,19 +211,20 @@ void CProfilerCallback::ShutdownOnce(bool clrIsAvailable) { traceLog.shutdown(); attachLog.shutdown(); -#ifdef TIA if (this->ipc != NULL) { delete ipc; ipc = NULL; } -#endif + if (this->worker != NULL) { + delete worker; + worker = NULL; + } if (config.shouldStartUploadDaemon()) { createDaemon().notifyShutdown(); } if (clrIsAvailable) { profilerInfo->ForceGC(); } - } HRESULT CProfilerCallback::Shutdown() { @@ -244,23 +237,25 @@ HRESULT CProfilerCallback::Shutdown() { return S_OK; } -DWORD CProfilerCallback::getEventMask() { - DWORD dwEventMask = 0; - dwEventMask |= COR_PRF_MONITOR_JIT_COMPILATION; - dwEventMask |= COR_PRF_MONITOR_ASSEMBLY_LOADS; - - // disable force re-jitting for the light variant - if (!config.shouldUseLightMode()) { - dwEventMask |= COR_PRF_DISABLE_ALL_NGEN_IMAGES; +void CProfilerCallback::adjustEventMask() { + DWORD dwEventMaskLow; + DWORD dwEventMaskHigh; + profilerInfo->GetEventMask2(&dwEventMaskLow, &dwEventMaskHigh); + dwEventMaskLow |= COR_PRF_MONITOR_ASSEMBLY_LOADS; + + if (config.isTgaEnabled()) { + dwEventMaskLow |= COR_PRF_MONITOR_JIT_COMPILATION; + // disable force re-jitting for the light variant + if (!config.shouldUseLightMode()) { + dwEventMaskLow |= COR_PRF_DISABLE_ALL_NGEN_IMAGES; + } } -#ifdef TIA if (config.isTiaEnabled()) { - dwEventMask |= COR_PRF_MONITOR_ENTERLEAVE; + dwEventMaskLow |= COR_PRF_MONITOR_ENTERLEAVE; } -#endif - return dwEventMask; + profilerInfo->SetEventMask2(dwEventMaskLow, dwEventMaskHigh); } HRESULT CProfilerCallback::AssemblyLoadFinished(AssemblyID assemblyId, HRESULT hrStatus) { @@ -277,6 +272,7 @@ HRESULT CProfilerCallback::AssemblyLoadFinishedImplementation(AssemblyID assembl if (!config.isProfilingEnabled()) { return S_OK; } + EnterCriticalSection(&callbackSynchronization); int assemblyNumber = registerAssembly(assemblyId); @@ -288,6 +284,8 @@ HRESULT CProfilerCallback::AssemblyLoadFinishedImplementation(AssemblyID assembl ASSEMBLYMETADATA metadata; getAssemblyInfo(assemblyId, assemblyName, assemblyPath, &metadata); + LeaveCriticalSection(&callbackSynchronization); + // Log assembly load. writtenChars += sprintf_s(assemblyInfo + writtenChars, BUFFER_SIZE - writtenChars, "%S:%i", assemblyName, assemblyNumber); @@ -378,7 +376,7 @@ void CProfilerCallback::handleException(std::string context) { HRESULT CProfilerCallback::JITCompilationFinishedImplementation(FunctionID functionId, HRESULT hrStatus, BOOL fIsSafeToBlock) { - if (config.isProfilingEnabled()) { + if (config.isProfilingEnabled() && config.isTgaEnabled()) { EnterCriticalSection(&callbackSynchronization); EnterCriticalSection(&methodSetSynchronization); recordFunctionInfo(&jittedMethods, functionId); @@ -401,18 +399,17 @@ HRESULT CProfilerCallback::JITInlining(FunctionID callerId, FunctionID calleeId, HRESULT CProfilerCallback::JITInliningImplementation(FunctionID callerId, FunctionID calleeId, BOOL* pfShouldInline) { - if (config.isProfilingEnabled()) { - // Save information about inlined method (if not already seen) - - // TODO (MP) Better late call eval here as well. - if (inlinedMethodIds.find(calleeId) == inlinedMethodIds.end()) { - EnterCriticalSection(&callbackSynchronization); - if (inlinedMethodIds.insert(calleeId).second == true) { - recordFunctionInfo(&inlinedMethods, calleeId); - } - LeaveCriticalSection(&callbackSynchronization); + if (config.isProfilingEnabled() && config.isTgaEnabled()) { + // Save information about inlined method (if not already seen) + + // TODO (MP) Better late call eval here as well. + if (inlinedMethodIds.find(calleeId) == inlinedMethodIds.end()) { + EnterCriticalSection(&callbackSynchronization); + if (inlinedMethodIds.insert(calleeId).second == true) { + recordFunctionInfo(&inlinedMethods, calleeId); } - + LeaveCriticalSection(&callbackSynchronization); + } } // Always allow inlining. @@ -441,30 +438,30 @@ void CProfilerCallback::recordFunctionInfo(std::vector* recordedFu inline bool CProfilerCallback::shouldWriteEagerly() { // Must be called from synchronized context size_t overallCount = inlinedMethods.size() + jittedMethods.size(); -#if TIA overallCount += calledMethods.size(); -#endif return config.getEagerness() > 0 && static_cast(overallCount) >= config.getEagerness(); } void CProfilerCallback::writeFunctionInfosToLog() { // Must be called from synchronized context - traceLog.writeInlinedFunctionInfosToLog(&inlinedMethods); - inlinedMethods.clear(); + if (config.isTgaEnabled()) { + traceLog.writeInlinedFunctionInfosToLog(&inlinedMethods); + inlinedMethods.clear(); - traceLog.writeJittedFunctionInfosToLog(&jittedMethods); - jittedMethods.clear(); - -#if TIA - worker->prepareMethodIdSetForWriting(); - for (FunctionID functionId : calledMethodIds) { - recordFunctionInfo(&calledMethods, functionId); + traceLog.writeJittedFunctionInfosToLog(&jittedMethods); + jittedMethods.clear(); } - calledMethodIds.clear(); - traceLog.writeCalledFunctionInfosToLog(&calledMethods); - calledMethods.clear(); -#endif + if (config.isTiaEnabled()) { + worker->prepareMethodIdSetForWriting(); + for (FunctionID functionId : calledMethodIds) { + recordFunctionInfo(&calledMethods, functionId); + } + + calledMethodIds.clear(); + traceLog.writeCalledFunctionInfosToLog(&calledMethods); + calledMethods.clear(); + } } HRESULT CProfilerCallback::getFunctionInfo(FunctionID functionId, FunctionInfo* info) { @@ -522,8 +519,6 @@ int CProfilerCallback::writeFileVersionInfo(LPCWSTR assemblyPath, char* buffer, return writtenChars; } -#ifdef TIA - void CProfilerCallback::onTestStart(std::string testName) { if (config.isProfilingEnabled() && config.isTiaEnabled()) { @@ -551,5 +546,3 @@ void CProfilerCallback::onTestEnd(std::string result, std::string message) LeaveCriticalSection(&methodSetSynchronization); } } - -#endif diff --git a/Profiler/CProfilerCallback.h b/Profiler/CProfilerCallback.h index 525d4899..402a276f 100644 --- a/Profiler/CProfilerCallback.h +++ b/Profiler/CProfilerCallback.h @@ -12,9 +12,7 @@ #include #include "CProfilerWorker.h" #include "UploadDaemon.h" -#ifdef TIA #include "utils/Ipc.h" -#endif /** * Coverage profiler class. Implements JIT event hooks to record method * coverage. @@ -66,11 +64,8 @@ class CProfilerCallback : public CProfilerCallbackBase { /** Counts the number of assemblies loaded. */ int assemblyCounter = 1; -#ifdef TIA bool isTestCaseRecording = false; CProfilerCallback* callbackInstance = NULL; - bool enableFunctionHooks = false; -#endif Config config = Config(WindowsUtils::getConfigValueFromEnvironment); @@ -98,7 +93,7 @@ class CProfilerCallback : public CProfilerCallbackBase { std::vector inlinedMethods; /** Smart pointer to the .NET framework profiler info. */ - CComQIPtr profilerInfo; + CComQIPtr profilerInfo; /** The log to write all results and messages to. */ TraceLog traceLog; @@ -106,7 +101,6 @@ class CProfilerCallback : public CProfilerCallbackBase { /** The log to write attach and detatch events to */ AttachLog attachLog; -#ifdef TIA /** Inter-process connection for TIA communication. null if not in TIA mode. */ Ipc* ipc = NULL; @@ -129,7 +123,6 @@ class CProfilerCallback : public CProfilerCallbackBase { * We use the vector to uniquely store the information about called methods. */ std::vector calledMethods; -#endif private: /** @@ -138,7 +131,7 @@ class CProfilerCallback : public CProfilerCallbackBase { * addition if light mode is disabled, EnterLeave hooks are enabled to force re-jitting of pre-jitted * code, in order to make coverage information independent of pre-jitted code. */ - DWORD getEventMask(); + void adjustEventMask(); /** Dumps all environment variables to the log file. */ void dumpEnvironment(); diff --git a/Profiler/CProfilerCallbackBase.h b/Profiler/CProfilerCallbackBase.h index 62c9e8cb..e6f2563c 100644 --- a/Profiler/CProfilerCallbackBase.h +++ b/Profiler/CProfilerCallbackBase.h @@ -1,7 +1,5 @@ #pragma once -#ifdef TIA #define _WINSOCKAPI_ // has to be defined for zmq before including windows.h -#endif #include #include #include @@ -24,12 +22,12 @@ class CProfilerCallbackBase : public ICorProfilerCallback3 { // IUnknown interface implementation STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); - STDMETHOD(QueryInterface)(REFIID riid, void **ppInterface); + STDMETHOD(QueryInterface)(REFIID riid, void** ppInterface); // End of IUnknown interface implementation // ICorProfilerCallback interface implementation // STARTUP/SHUTDOWN EVENTS - STDMETHOD(Initialize)(IUnknown *pICorProfilerInfoUnk); + STDMETHOD(Initialize)(IUnknown* pICorProfilerInfoUnk); STDMETHOD(Shutdown)(); // APPLICATION DOMAIN EVENTS STDMETHOD(AppDomainCreationStarted)(AppDomainID appDomainID); @@ -56,10 +54,10 @@ class CProfilerCallbackBase : public ICorProfilerCallback3 { // JIT EVENTS STDMETHOD(JITCompilationStarted)(FunctionID functionID, BOOL fIsSafeToBlock); STDMETHOD(JITCompilationFinished)(FunctionID functionID, HRESULT hrStatus, BOOL fIsSafeToBlock); - STDMETHOD(JITCachedFunctionSearchStarted)(FunctionID functionID, BOOL *pbUseCachedFunction); + STDMETHOD(JITCachedFunctionSearchStarted)(FunctionID functionID, BOOL* pbUseCachedFunction); STDMETHOD(JITCachedFunctionSearchFinished)(FunctionID functionID, COR_PRF_JIT_CACHE result); STDMETHOD(JITFunctionPitched)(FunctionID functionID); - STDMETHOD(JITInlining)(FunctionID callerID, FunctionID calleeID, BOOL *pfShouldInline); + STDMETHOD(JITInlining)(FunctionID callerID, FunctionID calleeID, BOOL* pfShouldInline); // THREAD EVENTS STDMETHOD(ThreadCreated)(ThreadID threadID); STDMETHOD(ThreadDestroyed)(ThreadID threadID); @@ -67,14 +65,14 @@ class CProfilerCallbackBase : public ICorProfilerCallback3 { // REMOTING EVENTS // Client-side events STDMETHOD(RemotingClientInvocationStarted)(); - STDMETHOD(RemotingClientSendingMessage)(GUID *pCookie, BOOL fIsAsync); - STDMETHOD(RemotingClientReceivingReply)(GUID *pCookie, BOOL fIsAsync); + STDMETHOD(RemotingClientSendingMessage)(GUID* pCookie, BOOL fIsAsync); + STDMETHOD(RemotingClientReceivingReply)(GUID* pCookie, BOOL fIsAsync); STDMETHOD(RemotingClientInvocationFinished)(); // Server-side events - STDMETHOD(RemotingServerReceivingMessage)(GUID *pCookie, BOOL fIsAsync); + STDMETHOD(RemotingServerReceivingMessage)(GUID* pCookie, BOOL fIsAsync); STDMETHOD(RemotingServerInvocationStarted)(); STDMETHOD(RemotingServerInvocationReturned)(); - STDMETHOD(RemotingServerSendingReply)(GUID *pCookie, BOOL fIsAsync); + STDMETHOD(RemotingServerSendingReply)(GUID* pCookie, BOOL fIsAsync); // CONTEXT EVENTS STDMETHOD(UnmanagedToManagedTransition)(FunctionID functionID, COR_PRF_TRANSITION_REASON reason); STDMETHOD(ManagedToUnmanagedTransition)(FunctionID functionID, COR_PRF_TRANSITION_REASON reason); @@ -113,8 +111,8 @@ class CProfilerCallbackBase : public ICorProfilerCallback3 { STDMETHOD(ExceptionCatcherEnter)(FunctionID functionID, ObjectID objectID); STDMETHOD(ExceptionCatcherLeave)(); // COM CLASSIC VTable - STDMETHOD(COMClassicVTableCreated)(ClassID wrappedClassID, REFGUID implementedIID, void *pVTable, ULONG cSlots); - STDMETHOD(COMClassicVTableDestroyed)(ClassID wrappedClassID, REFGUID implementedIID, void *pVTable); + STDMETHOD(COMClassicVTableCreated)(ClassID wrappedClassID, REFGUID implementedIID, void* pVTable, ULONG cSlots); + STDMETHOD(COMClassicVTableDestroyed)(ClassID wrappedClassID, REFGUID implementedIID, void* pVTable); // End of ICorProfilerCallback interface implementation // ICorProfilerCallback2 interface implementation @@ -129,7 +127,7 @@ class CProfilerCallbackBase : public ICorProfilerCallback3 { // End of ICorProfilerCallback2 interface implementation // ICorProfilerCallback3 interface implementation - STDMETHOD(InitializeForAttach)(IUnknown * pCorProfilerInfoUnk, void * pvClientData, UINT cbClientData); + STDMETHOD(InitializeForAttach)(IUnknown* pCorProfilerInfoUnk, void* pvClientData, UINT cbClientData); STDMETHOD(ProfilerAttachComplete)(); STDMETHOD(ProfilerDetachSucceeded)(); // End of ICorProfilerCallback3 interface implementation diff --git a/Profiler/CProfilerWorker.cpp b/Profiler/CProfilerWorker.cpp index 0a6a50c3..576d98ae 100644 --- a/Profiler/CProfilerWorker.cpp +++ b/Profiler/CProfilerWorker.cpp @@ -1,6 +1,5 @@ #include "CProfilerWorker.h" - CProfilerWorker::CProfilerWorker(Config* config, TraceLog* traceLog, std::unordered_set* calledMethodIds, CRITICAL_SECTION* methodSetSynchronization) { this->traceLog = traceLog; this->calledMethodIds = calledMethodIds; @@ -61,4 +60,4 @@ void CProfilerWorker::transferMethodIds(concurrency::concurrent_vector void CProfilerWorker::logError(std::string message) { std::string error = message; traceLog->info(error); -} \ No newline at end of file +} diff --git a/Profiler/CProfilerWorker.h b/Profiler/CProfilerWorker.h index d2c4bc7d..dfdffb6a 100644 --- a/Profiler/CProfilerWorker.h +++ b/Profiler/CProfilerWorker.h @@ -14,7 +14,7 @@ class CProfilerWorker virtual ~CProfilerWorker(); /** - * Prepares the set of methodIds from called methods for writing, i.e. transfers + * Prepares the set of methodIds from called methods for writing, i.e. transfers * the content of the vectors that have been used for intermittent storage into the set. */ void prepareMethodIdSetForWriting(); @@ -29,11 +29,8 @@ class CProfilerWorker CRITICAL_SECTION* methodSetSynchronization; - // Methods void methodIdThreadLoop(); void logError(std::string); void transferMethodIds(concurrency::concurrent_vector&); - }; - diff --git a/Profiler/config/Config.cpp b/Profiler/config/Config.cpp index 17be4a22..181bd48b 100644 --- a/Profiler/config/Config.cpp +++ b/Profiler/config/Config.cpp @@ -76,7 +76,7 @@ void Config::setOptions() dumpEnvironment = getBooleanOption("dump_environment", false); ignoreExceptions = getBooleanOption("ignore_exceptions", false); startUploadDaemon = getBooleanOption("upload_daemon", false); -#ifdef TIA + tgaEnabled = getBooleanOption("tga", true); tiaEnabled = getBooleanOption("tia", false); tiaSubscribeSocket = getOption("tia_subscribe_socket"); if (tiaSubscribeSocket.empty()) { @@ -87,7 +87,6 @@ void Config::setOptions() if (tiaRequestSocket.empty()) { tiaRequestSocket = "tcp://127.0.0.1:7146"; } -#endif std::string eagernessValue = getOption("eagerness"); if (eagernessValue.empty()) { eagerness = 0; diff --git a/Profiler/config/Config.h b/Profiler/config/Config.h index 00614508..22f26730 100644 --- a/Profiler/config/Config.h +++ b/Profiler/config/Config.h @@ -76,11 +76,17 @@ class Config return startUploadDaemon; } + /** Whether TGA profiling is enabled */ + bool isTgaEnabled() { + return tgaEnabled; + } + /** Whether to eagerly log trace data. */ size_t getEagerness() { return eagerness; } -#ifdef TIA + + /** Whether TIA profiling is enabled */ bool isTiaEnabled() { return tiaEnabled; } @@ -92,7 +98,6 @@ class Config std::string getTiaSubscribeSocket() { return tiaSubscribeSocket; } -#endif private: std::string processPath; @@ -110,11 +115,10 @@ class Config bool ignoreExceptions; bool startUploadDaemon; size_t eagerness; -#ifdef TIA + bool tgaEnabled; bool tiaEnabled; std::string tiaRequestSocket; std::string tiaSubscribeSocket; -#endif void apply(ConfigFile configFile); std::string getOption(std::string key); diff --git a/Profiler/log/TraceLog.cpp b/Profiler/log/TraceLog.cpp index f72168d7..1783014b 100644 --- a/Profiler/log/TraceLog.cpp +++ b/Profiler/log/TraceLog.cpp @@ -22,12 +22,10 @@ void TraceLog::writeInlinedFunctionInfosToLog(std::vector* functio writeFunctionInfosToLog(LOG_KEY_INLINED, functions); } -#ifdef TIA void TraceLog::writeCalledFunctionInfosToLog(std::vector* functions) { writeFunctionInfosToLog(LOG_KEY_CALLED, functions); } -#endif void TraceLog::createLogFile(std::string targetDir) { std::string timeStamp = getFormattedCurrentTime(); @@ -84,7 +82,6 @@ void TraceLog::logAssembly(std::string assembly) writeTupleToFile(LOG_KEY_ASSEMBLY, assembly.c_str()); } -#ifdef TIA void TraceLog::startTestCase(std::string testName) { // Line will look like this: @@ -107,7 +104,6 @@ void TraceLog::endTestCase(std::string result, std::string message) writeTupleToFile(LOG_KEY_TESTCASE, info.c_str()); } -#endif inline std::string TraceLog::escape(std::string message) { static std::regex colonEscape("(:|\\\\)"); diff --git a/Profiler/log/TraceLog.h b/Profiler/log/TraceLog.h index e5950b2a..cf81596d 100644 --- a/Profiler/log/TraceLog.h +++ b/Profiler/log/TraceLog.h @@ -22,10 +22,8 @@ class TraceLog : public FileLogBase /** Write all information about the given inlined functions to the log. */ void writeInlinedFunctionInfosToLog(std::vector* functions); -#ifdef TIA /** Write all information about the given called functions to the log. */ void writeCalledFunctionInfosToLog(std::vector* functions); -#endif /** * Create the log file and add general information. @@ -55,10 +53,8 @@ class TraceLog : public FileLogBase /** Writes info about a profiled assembly into the log. Should only be called once. */ void logAssembly(std::string assembly); -#ifdef TIA void startTestCase(std::string testName); void endTestCase(std::string result = "", std::string message = ""); -#endif protected: /** The key to log information about the profiler startup. */ @@ -73,10 +69,8 @@ class TraceLog : public FileLogBase /** The key to log information about jitted methods. */ const char* LOG_KEY_JITTED = "Jitted"; -#ifdef TIA /** The key to log information about called methods. */ const char* LOG_KEY_CALLED = "Called"; -#endif /** The key to log information about test cases. */ const char* LOG_KEY_TESTCASE = "Test"; diff --git a/Profiler/utils/Ipc.h b/Profiler/utils/Ipc.h index faf01b79..b5d32452 100644 --- a/Profiler/utils/Ipc.h +++ b/Profiler/utils/Ipc.h @@ -4,6 +4,7 @@ #include #include +#include class Ipc { diff --git a/Profiler/utils/MethodEnter.cpp b/Profiler/utils/MethodEnter.cpp index ffd264bd..d7aca78f 100644 --- a/Profiler/utils/MethodEnter.cpp +++ b/Profiler/utils/MethodEnter.cpp @@ -22,7 +22,6 @@ void setTestCaseRecording(bool testCaseRecording) { isTestCaseRecording = testCaseRecording; } - #ifdef _WIN64 void __fastcall FnEnterCallback(FunctionIDOrClientID funcId) { @@ -45,4 +44,4 @@ void __declspec(naked) FnEnterCallback(FunctionIDOrClientID funcId) { } } -#endif \ No newline at end of file +#endif From e1ca9729ab5a770c665edd047e347dbbc8f01da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Thu, 28 Apr 2022 16:18:57 +0200 Subject: [PATCH 105/174] Swap vectors instead of using a toggle boolean --- Profiler/CProfilerWorker.cpp | 47 ++++++++++++++++------------------ Profiler/CProfilerWorker.h | 7 ++--- Profiler/utils/MethodEnter.cpp | 4 +-- Profiler/utils/MethodEnter.h | 2 +- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/Profiler/CProfilerWorker.cpp b/Profiler/CProfilerWorker.cpp index 576d98ae..b2921316 100644 --- a/Profiler/CProfilerWorker.cpp +++ b/Profiler/CProfilerWorker.cpp @@ -13,47 +13,38 @@ CProfilerWorker::~CProfilerWorker() { if (this->workerThread->joinable()) { this->workerThread->join(); } + delete vector1; + delete vector2; } void CProfilerWorker::methodIdThreadLoop() { - // Toggle which vector to use - bool vectorToggle = true; while (!this->shutdown) { - if (vector1.empty() && vector2.empty()) { + if (vector1->empty() && vector2->empty()) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); continue; } - if (vectorToggle) { - EnterCriticalSection(methodSetSynchronization); - setMethodIdVector(this->vector2); - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - transferMethodIds(this->vector1); - LeaveCriticalSection(methodSetSynchronization); - } - else { - EnterCriticalSection(methodSetSynchronization); - setMethodIdVector(this->vector1); - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - transferMethodIds(this->vector2); - LeaveCriticalSection(methodSetSynchronization); - } - vectorToggle = !vectorToggle; + EnterCriticalSection(methodSetSynchronization); + swapVectors(); + setMethodIdVector(this->vector2); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + transferMethodIds(this->vector1); + LeaveCriticalSection(methodSetSynchronization); } } void CProfilerWorker::prepareMethodIdSetForWriting() { - transferMethodIds(this->vector1); + swapVectors(); transferMethodIds(this->vector2); } -void CProfilerWorker::transferMethodIds(concurrency::concurrent_vector& methodIds) { - size_t size = methodIds.size(); +void CProfilerWorker::transferMethodIds(concurrency::concurrent_vector* methodIds) { + size_t size = methodIds->size(); for (unsigned int i = 0; i < size; i++) { - this->calledMethodIds->insert(methodIds[i]); + this->calledMethodIds->insert((*methodIds)[i]); } - methodIds.clear(); - if (methodIds.capacity() > 2'000'000) { - methodIds.shrink_to_fit(); + methodIds->clear(); + if (methodIds->capacity() > 2'000'000) { + methodIds->shrink_to_fit(); } } @@ -61,3 +52,9 @@ void CProfilerWorker::logError(std::string message) { std::string error = message; traceLog->info(error); } + +void CProfilerWorker::swapVectors() { + concurrency::concurrent_vector* temp = vector1; + vector1 = vector2; + vector2 = temp; +} diff --git a/Profiler/CProfilerWorker.h b/Profiler/CProfilerWorker.h index dfdffb6a..b1a7aa50 100644 --- a/Profiler/CProfilerWorker.h +++ b/Profiler/CProfilerWorker.h @@ -23,8 +23,8 @@ class CProfilerWorker std::thread* workerThread = NULL; TraceLog* traceLog = NULL; bool shutdown = false; - concurrency::concurrent_vector vector1; - concurrency::concurrent_vector vector2; + concurrency::concurrent_vector* vector1 = new concurrency::concurrent_vector(); + concurrency::concurrent_vector* vector2 = new concurrency::concurrent_vector(); std::unordered_set* calledMethodIds; CRITICAL_SECTION* methodSetSynchronization; @@ -32,5 +32,6 @@ class CProfilerWorker // Methods void methodIdThreadLoop(); void logError(std::string); - void transferMethodIds(concurrency::concurrent_vector&); + void transferMethodIds(concurrency::concurrent_vector*); + void swapVectors(); }; diff --git a/Profiler/utils/MethodEnter.cpp b/Profiler/utils/MethodEnter.cpp index d7aca78f..da7800b9 100644 --- a/Profiler/utils/MethodEnter.cpp +++ b/Profiler/utils/MethodEnter.cpp @@ -14,8 +14,8 @@ extern "C" void _stdcall EnterCpp( } } -void setMethodIdVector(concurrency::concurrent_vector& vectorToUse) { - vectorInUse = &vectorToUse; +void setMethodIdVector(concurrency::concurrent_vector* vectorToUse) { + vectorInUse = vectorToUse; } void setTestCaseRecording(bool testCaseRecording) { diff --git a/Profiler/utils/MethodEnter.h b/Profiler/utils/MethodEnter.h index d5e74b6f..d6707b7e 100644 --- a/Profiler/utils/MethodEnter.h +++ b/Profiler/utils/MethodEnter.h @@ -9,7 +9,7 @@ /** * Sets the vector to be filled with methodIds from called methods at this time. */ -void setMethodIdVector(concurrency::concurrent_vector&); +void setMethodIdVector(concurrency::concurrent_vector*); /** * Sets the state of test case recording i.e. whether a test case is currently in progress or not. From 9afa9aa5f03bf976d815eb891a900092d74dbe01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Thu, 28 Apr 2022 16:51:02 +0200 Subject: [PATCH 106/174] Rename method id vectors to primary and backing vectors --- Profiler/CProfilerWorker.cpp | 24 +++++++++++++----------- Profiler/CProfilerWorker.h | 4 ++-- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Profiler/CProfilerWorker.cpp b/Profiler/CProfilerWorker.cpp index b2921316..5be41b07 100644 --- a/Profiler/CProfilerWorker.cpp +++ b/Profiler/CProfilerWorker.cpp @@ -4,7 +4,7 @@ CProfilerWorker::CProfilerWorker(Config* config, TraceLog* traceLog, std::unorde this->traceLog = traceLog; this->calledMethodIds = calledMethodIds; this->methodSetSynchronization = methodSetSynchronization; - setMethodIdVector(this->vector1); + setMethodIdVector(this->primary); this->workerThread = new std::thread(&CProfilerWorker::methodIdThreadLoop, this); } @@ -13,28 +13,24 @@ CProfilerWorker::~CProfilerWorker() { if (this->workerThread->joinable()) { this->workerThread->join(); } - delete vector1; - delete vector2; + delete backing; + delete primary; } void CProfilerWorker::methodIdThreadLoop() { while (!this->shutdown) { - if (vector1->empty() && vector2->empty()) { + if (backing->empty() && primary->empty()) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); continue; } EnterCriticalSection(methodSetSynchronization); swapVectors(); - setMethodIdVector(this->vector2); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - transferMethodIds(this->vector1); LeaveCriticalSection(methodSetSynchronization); } } void CProfilerWorker::prepareMethodIdSetForWriting() { swapVectors(); - transferMethodIds(this->vector2); } void CProfilerWorker::transferMethodIds(concurrency::concurrent_vector* methodIds) { @@ -54,7 +50,13 @@ void CProfilerWorker::logError(std::string message) { } void CProfilerWorker::swapVectors() { - concurrency::concurrent_vector* temp = vector1; - vector1 = vector2; - vector2 = temp; + // Must be called from synchronized context + concurrency::concurrent_vector* temp = backing; + backing = primary; + primary = temp; + setMethodIdVector(this->primary); + // Since the reassignment is technically not thread safe, we wait a little bit + // to be sure that everyone is done with writing to the now backing vector. + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + transferMethodIds(this->backing); } diff --git a/Profiler/CProfilerWorker.h b/Profiler/CProfilerWorker.h index b1a7aa50..9bc375e4 100644 --- a/Profiler/CProfilerWorker.h +++ b/Profiler/CProfilerWorker.h @@ -23,8 +23,8 @@ class CProfilerWorker std::thread* workerThread = NULL; TraceLog* traceLog = NULL; bool shutdown = false; - concurrency::concurrent_vector* vector1 = new concurrency::concurrent_vector(); - concurrency::concurrent_vector* vector2 = new concurrency::concurrent_vector(); + concurrency::concurrent_vector* backing = new concurrency::concurrent_vector(); + concurrency::concurrent_vector* primary = new concurrency::concurrent_vector(); std::unordered_set* calledMethodIds; CRITICAL_SECTION* methodSetSynchronization; From 6bde0831cd90413e3d595524b8ffe40e9c418a5e Mon Sep 17 00:00:00 2001 From: Raphael Noemmer Date: Thu, 12 May 2022 14:38:06 +0200 Subject: [PATCH 107/174] Add tests for tga and tia configurations --- Profiler_Test/ProfilerTest.cs | 59 +++++++++++++++++++++++++++- Profiler_Test/Tia/TiaProfilerTest.cs | 20 +++++----- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/Profiler_Test/ProfilerTest.cs b/Profiler_Test/ProfilerTest.cs index 069ce1db..f187ba26 100644 --- a/Profiler_Test/ProfilerTest.cs +++ b/Profiler_Test/ProfilerTest.cs @@ -1,4 +1,6 @@ -using Cqse.Teamscale.Profiler.Dotnet.Proxies; +using Cqse.Teamscale.Profiler.Commons.Ipc; +using Cqse.Teamscale.Profiler.Dotnet.Proxies; +using Cqse.Teamscale.Profiler.Dotnet.Tia; using NUnit.Framework; using System.IO; using System.Linq; @@ -64,12 +66,65 @@ public int TestConfigFile(string regex) "); profiler.ConfigFilePath = configFile; - + new Testee(GetTestProgram("ProfilerTestee.exe")).Run(arguments: "none", profiler); return profiler.GetTraceFiles().Count; } + /// + /// Makes sure that when tga mode is active, we only get regular coverage. + /// + [Test] + public void TestTgaConfig() + { + var configFile = Path.Combine(TestTempDirectory, "profilerconfig.yml"); + File.WriteAllText(configFile, $@" +match: + - profiler: + enabled: true + tga: true +"); + + RecordingProfilerIpc profilerIpc = new RecordingProfilerIpc(null); + profilerIpc.StartTest("Test1"); + + profiler.ConfigFilePath = configFile; + new Testee(GetTestProgram("ProfilerTestee.exe")).Run(arguments: "none", profiler); + + string[] lines = profiler.GetSingleTrace(); + Assert.That(lines, Has.Some.Matches("^(Inlines|Jitted)")); + Assert.That(lines, Has.None.Matches("^(Called)")); + } + + /// + /// Makes sure that when tia mode is active, we only get testwise coverage. + /// + [Test] + public void TestTiaConfig() + { + var configFile = Path.Combine(TestTempDirectory, "profilerconfig.yml"); + RecordingProfilerIpc profilerIpc = new RecordingProfilerIpc(null); + profilerIpc.StartTest("Test1"); + + File.WriteAllText(configFile, $@" +match: + - profiler: + enabled: true + tga: false + tia: true + tia_request_socket: {profilerIpc.Config.RequestSocket} + tia_subscribe_socket: {profilerIpc.Config.PublishSocket} +"); + + profiler.ConfigFilePath = configFile; + new Testee(GetTestProgram("ProfilerTestee.exe")).Run(arguments: "none", profiler); + + string[] lines = profiler.GetSingleTrace(); + Assert.That(lines, Has.None.Matches("^(Inlines|Jitted)")); + Assert.That(lines, Has.Some.Matches("^(Called)")); + } + /// /// Runs the profiler with the environment variable APP_POOL_ID set and asserts its content is logged into the trace. /// diff --git a/Profiler_Test/Tia/TiaProfilerTest.cs b/Profiler_Test/Tia/TiaProfilerTest.cs index 86225eb6..1d812f4f 100644 --- a/Profiler_Test/Tia/TiaProfilerTest.cs +++ b/Profiler_Test/Tia/TiaProfilerTest.cs @@ -129,21 +129,21 @@ public void NoIpcRunning() profilerIpc = null; } - [Ignore("TODO: No idea why this is not working... manual tests have no problem with this.")] [Test] public void IpcStartedAfterStartup() { RecordingProfilerIpc oldProfilerIpc = profilerIpc; - oldProfilerIpc.StartTest("should not be triggered"); - oldProfilerIpc.Dispose(); - + //oldProfilerIpc.StartTest("should not be triggered"); + //oldProfilerIpc.EndTest(ETestExecutionResult.PASSED, ""); + //oldProfilerIpc.Dispose(); + profilerIpc.Dispose(); TesteeProcess testeeProcess = Start(testee, profilerUnderTest); - profilerIpc = CreateProfilerIpc(oldProfilerIpc.Config); + profilerIpc = CreateProfilerIpc(profilerIpc.Config); RunTestCase("A", testeeProcess, profilerIpc); Stop(testeeProcess); - Assert.That(oldProfilerIpc.ReceivedRequests, Is.Empty); + //Assert.That(oldProfilerIpc.ReceivedRequests, Is.Empty); Assert.That(profilerIpc.ReceivedRequests, Is.EquivalentTo(new[] { "profiler_disconnected" })); TiaTestResult testResult = profilerUnderTest.Result; Assert.That(testResult.TestCaseNames, Is.EquivalentTo(new[] { string.Empty, "A" })); @@ -162,14 +162,14 @@ private static TesteeProcess Start(Testee testee, IProfiler profiler) private static void RunTestCase(string testCaseName, TesteeProcess process, RecordingProfilerIpc profilerIpc) { profilerIpc.StartTest(testCaseName); - Thread.Sleep(TimeSpan.FromMilliseconds(10)); // wait shortly, so the profiler registers the change + Thread.Sleep(TimeSpan.FromMilliseconds(20)); // wait shortly, so the profiler registers the change process.Input.WriteLine(testCaseName); Assert.That(process.Output.ReadLine(), Is.EqualTo(testCaseName)); - Thread.Sleep(TimeSpan.FromMilliseconds(10)); // wait shortly - + Thread.Sleep(TimeSpan.FromMilliseconds(20)); // wait shortly + profilerIpc.EndTest(ETestExecutionResult.PASSED); - Thread.Sleep(TimeSpan.FromMilliseconds(10)); // wait shortly + Thread.Sleep(TimeSpan.FromMilliseconds(20)); // wait shortly } private static void Stop(TesteeProcess process) From 776e0bde163655d1528cbb992386b988b777a09f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Sun, 22 May 2022 23:07:04 +0200 Subject: [PATCH 108/174] Implement int set to achieve faster checks --- Profiler/CProfilerCallback.cpp | 8 +- Profiler/CProfilerCallback.h | 7 +- Profiler/CProfilerWorker.cpp | 42 +- Profiler/CProfilerWorker.h | 23 +- Profiler/Profiler.vcxproj | 11 +- Profiler/Profiler.vcxproj.filters | 12 + Profiler/utils/Ipc.cpp | 20 +- Profiler/utils/Ipc.h | 5 +- Profiler/utils/MethodEnter.cpp | 23 +- Profiler/utils/MethodEnter.h | 18 +- Profiler/utils/atomic_queue/atomic_queue.h | 619 ++++ Profiler/utils/atomic_queue/defs.h | 92 + .../utils/functionID_set/functionId_set.h | 115 + Profiler/utils/robin_hood/robin_hood.h | 2551 +++++++++++++++++ 14 files changed, 3462 insertions(+), 84 deletions(-) create mode 100644 Profiler/utils/atomic_queue/atomic_queue.h create mode 100644 Profiler/utils/atomic_queue/defs.h create mode 100644 Profiler/utils/functionID_set/functionId_set.h create mode 100644 Profiler/utils/robin_hood/robin_hood.h diff --git a/Profiler/CProfilerCallback.cpp b/Profiler/CProfilerCallback.cpp index 16a7eab6..f3409590 100644 --- a/Profiler/CProfilerCallback.cpp +++ b/Profiler/CProfilerCallback.cpp @@ -453,9 +453,11 @@ void CProfilerCallback::writeFunctionInfosToLog() { } if (config.isTiaEnabled()) { - worker->prepareMethodIdSetForWriting(); - for (FunctionID functionId : calledMethodIds) { - recordFunctionInfo(&calledMethods, functionId); + for (unsigned int i = 0; i < calledMethodIds.size(); i++) { + FunctionID value = calledMethodIds.at(i); + if (value != 0) { + recordFunctionInfo(&calledMethods, value); + } } calledMethodIds.clear(); diff --git a/Profiler/CProfilerCallback.h b/Profiler/CProfilerCallback.h index 402a276f..37b0e1ad 100644 --- a/Profiler/CProfilerCallback.h +++ b/Profiler/CProfilerCallback.h @@ -9,7 +9,8 @@ #include #include #include -#include +#include +#include #include "CProfilerWorker.h" #include "UploadDaemon.h" #include "utils/Ipc.h" @@ -84,7 +85,7 @@ class CProfilerCallback : public CProfilerCallbackBase { * Keeps track of inlined methods. * We use the set to efficiently determine if we already noticed an inlined method. */ - std::unordered_set inlinedMethodIds; + robin_hood::unordered_flat_set inlinedMethodIds; /** * Keeps track of inlined methods. @@ -114,7 +115,7 @@ class CProfilerCallback : public CProfilerCallbackBase { * Keeps track of called methods. * We use the set to efficiently determine if we already noticed an called method. */ - std::unordered_set calledMethodIds; + functionID_set calledMethodIds; CProfilerWorker* worker = NULL; diff --git a/Profiler/CProfilerWorker.cpp b/Profiler/CProfilerWorker.cpp index 5be41b07..211fd295 100644 --- a/Profiler/CProfilerWorker.cpp +++ b/Profiler/CProfilerWorker.cpp @@ -1,10 +1,12 @@ #include "CProfilerWorker.h" -CProfilerWorker::CProfilerWorker(Config* config, TraceLog* traceLog, std::unordered_set* calledMethodIds, CRITICAL_SECTION* methodSetSynchronization) { +CProfilerWorker::CProfilerWorker(Config* config, TraceLog* traceLog, functionID_set* calledMethodIds, CRITICAL_SECTION* methodSetSynchronization) { this->traceLog = traceLog; this->calledMethodIds = calledMethodIds; this->methodSetSynchronization = methodSetSynchronization; - setMethodIdVector(this->primary); + setCriticalSection(methodSetSynchronization); + setCalledMethodsSet(calledMethodIds); + setMethodIdQueue(methodIdQueue); this->workerThread = new std::thread(&CProfilerWorker::methodIdThreadLoop, this); } @@ -13,34 +15,24 @@ CProfilerWorker::~CProfilerWorker() { if (this->workerThread->joinable()) { this->workerThread->join(); } - delete backing; - delete primary; + delete methodIdQueue; } void CProfilerWorker::methodIdThreadLoop() { while (!this->shutdown) { - if (backing->empty() && primary->empty()) { + if (methodIdQueue->was_empty()) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); - continue; } EnterCriticalSection(methodSetSynchronization); - swapVectors(); + transferMethodIds(); LeaveCriticalSection(methodSetSynchronization); } } -void CProfilerWorker::prepareMethodIdSetForWriting() { - swapVectors(); -} - -void CProfilerWorker::transferMethodIds(concurrency::concurrent_vector* methodIds) { - size_t size = methodIds->size(); - for (unsigned int i = 0; i < size; i++) { - this->calledMethodIds->insert((*methodIds)[i]); - } - methodIds->clear(); - if (methodIds->capacity() > 2'000'000) { - methodIds->shrink_to_fit(); +void CProfilerWorker::transferMethodIds() { + FunctionID i; + while (methodIdQueue->try_pop(i)) { + calledMethodIds->insert(i); } } @@ -48,15 +40,3 @@ void CProfilerWorker::logError(std::string message) { std::string error = message; traceLog->info(error); } - -void CProfilerWorker::swapVectors() { - // Must be called from synchronized context - concurrency::concurrent_vector* temp = backing; - backing = primary; - primary = temp; - setMethodIdVector(this->primary); - // Since the reassignment is technically not thread safe, we wait a little bit - // to be sure that everyone is done with writing to the now backing vector. - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - transferMethodIds(this->backing); -} diff --git a/Profiler/CProfilerWorker.h b/Profiler/CProfilerWorker.h index 9bc375e4..461a4ab6 100644 --- a/Profiler/CProfilerWorker.h +++ b/Profiler/CProfilerWorker.h @@ -4,34 +4,29 @@ #include "log/TraceLog.h" #include #include "utils/MethodEnter.h" -#include -#include +#include +#include class CProfilerWorker { public: - CProfilerWorker(Config*, TraceLog*, std::unordered_set*, CRITICAL_SECTION*); + CProfilerWorker(Config*, TraceLog*, functionID_set*, CRITICAL_SECTION*); virtual ~CProfilerWorker(); - - /** - * Prepares the set of methodIds from called methods for writing, i.e. transfers - * the content of the vectors that have been used for intermittent storage into the set. - */ - void prepareMethodIdSetForWriting(); private: + // Variables std::thread* workerThread = NULL; TraceLog* traceLog = NULL; bool shutdown = false; - concurrency::concurrent_vector* backing = new concurrency::concurrent_vector(); - concurrency::concurrent_vector* primary = new concurrency::concurrent_vector(); - std::unordered_set* calledMethodIds; + +#pragma warning( disable : 4316) + functionID_set* calledMethodIds; + Queue* methodIdQueue = new Queue(65'536); CRITICAL_SECTION* methodSetSynchronization; // Methods void methodIdThreadLoop(); void logError(std::string); - void transferMethodIds(concurrency::concurrent_vector*); - void swapVectors(); + void transferMethodIds(); }; diff --git a/Profiler/Profiler.vcxproj b/Profiler/Profiler.vcxproj index 801759ff..afcadc99 100644 --- a/Profiler/Profiler.vcxproj +++ b/Profiler/Profiler.vcxproj @@ -155,8 +155,8 @@ MaxSpeed - OnlyExplicitInline - true + Default + false TIA;WIN32;NDEBUG;_WINDOWS;_USRDLL;GCPROFILER_EXPORTS;%(PreprocessorDefinitions) true MultiThreaded @@ -169,6 +169,9 @@ %(AdditionalIncludeDirectories) Async true + true + Speed + true corguids.lib;version.lib;Ws2_32.lib;Iphlpapi.lib;%(AdditionalDependencies) @@ -265,6 +268,7 @@ + @@ -295,7 +299,10 @@ + + + diff --git a/Profiler/Profiler.vcxproj.filters b/Profiler/Profiler.vcxproj.filters index 46f21c79..8b719805 100644 --- a/Profiler/Profiler.vcxproj.filters +++ b/Profiler/Profiler.vcxproj.filters @@ -284,6 +284,18 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + diff --git a/Profiler/utils/Ipc.cpp b/Profiler/utils/Ipc.cpp index ea7968eb..b42a966a 100644 --- a/Profiler/utils/Ipc.cpp +++ b/Profiler/utils/Ipc.cpp @@ -1,4 +1,3 @@ -#ifdef TIA #include "Ipc.h" #include @@ -30,7 +29,7 @@ Ipc::~Ipc() this->handlerThread->join(); } - this->request("profiler_disconnected"); + this->sendDisconnect(); if (this->zmqRequestSocket != NULL) { zmq_close(this->zmqRequestSocket); } @@ -49,16 +48,6 @@ void Ipc::handlerThreadLoop() { return; } - /// Alternative way to read messages - //char buffer[IPC_BUFFER_SLOTS][IPC_BUFFER_SIZE]; - //while (!this->shutdown) { - // int len = zmq_recv(this->zmqSubscribeSocket, &buffer[0], IPC_BUFFER_SIZE - 1, 0); - // if (len >= 0) { - // IPC_TERMINATE_STRING(buffer[0], len); - // this->testStartCallback(buffer[0]); - // } - //} - std::vector frames; zmq_msg_t message; while (!this->shutdown) { @@ -103,6 +92,11 @@ std::string Ipc::getCurrentTestName() return this->request("get_testname"); } +void Ipc::sendDisconnect() { + std::string message = "profiler_disconnected"; + zmq_send(this->zmqRequestSocket, message.c_str(), message.length(), 0); +} + std::string Ipc::request(std::string message) { if (!initRequestSocket()) { @@ -134,7 +128,6 @@ bool Ipc::initRequestSocket() { return false; } } - return true; } @@ -142,4 +135,3 @@ void Ipc::logError(std::string message) { std::string error = message + " (ZMQ error: " + zmq_strerror(zmq_errno()) + ")"; errorCallback(error); } -#endif diff --git a/Profiler/utils/Ipc.h b/Profiler/utils/Ipc.h index b5d32452..4a902e03 100644 --- a/Profiler/utils/Ipc.h +++ b/Profiler/utils/Ipc.h @@ -1,5 +1,4 @@ #pragma once -#ifdef TIA #include "config/Config.h" #include @@ -23,10 +22,10 @@ class Ipc std::function testEndCallback; std::function errorCallback; std::atomic shutdown = false; + void sendDisconnect(); void handlerThreadLoop(); void handleMessage(std::vector frames); bool initRequestSocket(); void logError(std::string message); std::string request(std::string message); -}; -#endif +}; \ No newline at end of file diff --git a/Profiler/utils/MethodEnter.cpp b/Profiler/utils/MethodEnter.cpp index da7800b9..a06211c0 100644 --- a/Profiler/utils/MethodEnter.cpp +++ b/Profiler/utils/MethodEnter.cpp @@ -3,19 +3,28 @@ #include "Debug.h" namespace { - concurrency::concurrent_vector* vectorInUse; + functionID_set* calledFunctionSet; bool isTestCaseRecording = false; + CRITICAL_SECTION* methodSetSynchronization; + Queue* methodQueue; } -extern "C" void _stdcall EnterCpp( - FunctionIDOrClientID funcId) { - if (isTestCaseRecording) { - vectorInUse->push_back(funcId.functionID); +extern "C" void _stdcall EnterCpp(FunctionIDOrClientID funcId) { + if (isTestCaseRecording && !calledFunctionSet->contains(funcId.functionID)) { + methodQueue->push(funcId.functionID); } } -void setMethodIdVector(concurrency::concurrent_vector* vectorToUse) { - vectorInUse = vectorToUse; +void setCalledMethodsSet(functionID_set* setToUse) { + calledFunctionSet = setToUse; +} + +void setCriticalSection(CRITICAL_SECTION* methodSetSync) { + methodSetSynchronization = methodSetSync; +} + +void setMethodIdQueue(Queue* methodIdQueue) { + methodQueue = methodIdQueue; } void setTestCaseRecording(bool testCaseRecording) { diff --git a/Profiler/utils/MethodEnter.h b/Profiler/utils/MethodEnter.h index d6707b7e..fe9335d0 100644 --- a/Profiler/utils/MethodEnter.h +++ b/Profiler/utils/MethodEnter.h @@ -4,23 +4,27 @@ #include #include #include -#include +#include +#include + + +FunctionID constexpr NIL = static_cast(-1); +using Queue = atomic_queue::AtomicQueueB, NIL>; /** * Sets the vector to be filled with methodIds from called methods at this time. */ -void setMethodIdVector(concurrency::concurrent_vector*); +void setCalledMethodsSet(functionID_set*); + +void setCriticalSection(CRITICAL_SECTION*); + +void setMethodIdQueue(Queue*); /** * Sets the state of test case recording i.e. whether a test case is currently in progress or not. */ void setTestCaseRecording(bool); -/** - * Sets whether profiling is enabled or not. If not, not methodIds will be recorded. - */ -void setProfilingEnabled(bool); - /** * The callback function that is run on a method enter event. */ diff --git a/Profiler/utils/atomic_queue/atomic_queue.h b/Profiler/utils/atomic_queue/atomic_queue.h new file mode 100644 index 00000000..2c7a67e9 --- /dev/null +++ b/Profiler/utils/atomic_queue/atomic_queue.h @@ -0,0 +1,619 @@ +/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ +#ifndef ATOMIC_QUEUE_ATOMIC_QUEUE_H_INCLUDED +#define ATOMIC_QUEUE_ATOMIC_QUEUE_H_INCLUDED + +// Copyright (c) 2019 Maxim Egorushkin. MIT License. See the full licence in file LICENSE. + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace atomic_queue { + +using std::uint32_t; +using std::uint64_t; +using std::uint8_t; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace details { + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +template struct GetCacheLineIndexBits { static int constexpr value = 0; }; +template<> struct GetCacheLineIndexBits<256> { static int constexpr value = 8; }; +template<> struct GetCacheLineIndexBits<128> { static int constexpr value = 7; }; +template<> struct GetCacheLineIndexBits< 64> { static int constexpr value = 6; }; +template<> struct GetCacheLineIndexBits< 32> { static int constexpr value = 5; }; +template<> struct GetCacheLineIndexBits< 16> { static int constexpr value = 4; }; +template<> struct GetCacheLineIndexBits< 8> { static int constexpr value = 3; }; +template<> struct GetCacheLineIndexBits< 4> { static int constexpr value = 2; }; +template<> struct GetCacheLineIndexBits< 2> { static int constexpr value = 1; }; + +template +struct GetIndexShuffleBits { + static int constexpr bits = GetCacheLineIndexBits::value; + static unsigned constexpr min_size = 1u << (bits * 2); + static int constexpr value = array_size < min_size ? 0 : bits; +}; + +template +struct GetIndexShuffleBits { + static int constexpr value = 0; +}; + +// Multiple writers/readers contend on the same cache line when storing/loading elements at +// subsequent indexes, aka false sharing. For power of 2 ring buffer size it is possible to re-map +// the index in such a way that each subsequent element resides on another cache line, which +// minimizes contention. This is done by swapping the lowest order N bits (which are the index of +// the element within the cache line) with the next N bits (which are the index of the cache line) +// of the element index. +template +constexpr unsigned remap_index_with_mix(unsigned index, unsigned mix) { + return index ^ mix ^ (mix << BITS); +} + +template +constexpr unsigned remap_index(unsigned index) noexcept { + return remap_index_with_mix(index, (index ^ (index >> BITS)) & ((1u << BITS) - 1)); +} + +template<> +constexpr unsigned remap_index<0>(unsigned index) noexcept { + return index; +} + +template +constexpr T& map(T* elements, unsigned index) noexcept { + return elements[remap_index(index)]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Implement a "bit-twiddling hack" for finding the next power of 2 in either 32 bits or 64 bits +// in C++11 compatible constexpr functions. The library no longer maintains C++11 compatibility. + +// "Runtime" version for 32 bits +// --a; +// a |= a >> 1; +// a |= a >> 2; +// a |= a >> 4; +// a |= a >> 8; +// a |= a >> 16; +// ++a; + +template +constexpr T decrement(T x) noexcept { + return x - 1; +} + +template +constexpr T increment(T x) noexcept { + return x + 1; +} + +template +constexpr T or_equal(T x, unsigned u) noexcept { + return x | x >> u; +} + +template +constexpr T or_equal(T x, unsigned u, Args... rest) noexcept { + return or_equal(or_equal(x, u), rest...); +} + +constexpr uint32_t round_up_to_power_of_2(uint32_t a) noexcept { + return increment(or_equal(decrement(a), 1, 2, 4, 8, 16)); +} + +constexpr uint64_t round_up_to_power_of_2(uint64_t a) noexcept { + return increment(or_equal(decrement(a), 1, 2, 4, 8, 16, 32)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace details + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +template +class AtomicQueueCommon { +protected: + // Put these on different cache lines to avoid false sharing between readers and writers. + alignas(CACHE_LINE_SIZE) std::atomic head_ = {}; + alignas(CACHE_LINE_SIZE) std::atomic tail_ = {}; + + // The special member functions are not thread-safe. + + AtomicQueueCommon() noexcept = default; + + AtomicQueueCommon(AtomicQueueCommon const& b) noexcept + : head_(b.head_.load(X)) + , tail_(b.tail_.load(X)) {} + + AtomicQueueCommon& operator=(AtomicQueueCommon const& b) noexcept { + head_.store(b.head_.load(X), X); + tail_.store(b.tail_.load(X), X); + return *this; + } + + void swap(AtomicQueueCommon& b) noexcept { + unsigned h = head_.load(X); + unsigned t = tail_.load(X); + head_.store(b.head_.load(X), X); + tail_.store(b.tail_.load(X), X); + b.head_.store(h, X); + b.tail_.store(t, X); + } + + template + static T do_pop_atomic(std::atomic& q_element) noexcept { + if(Derived::spsc_) { + for(;;) { + T element = q_element.load(X); + if(ATOMIC_QUEUE_LIKELY(element != NIL)) { + q_element.store(NIL, R); + return element; + } + if(Derived::maximize_throughput_) + spin_loop_pause(); + } + } + else { + for(;;) { + T element = q_element.exchange(NIL, R); // (2) The store to wait for. + if(ATOMIC_QUEUE_LIKELY(element != NIL)) + return element; + // Do speculative loads while busy-waiting to avoid broadcasting RFO messages. + do + spin_loop_pause(); + while(Derived::maximize_throughput_ && q_element.load(X) == NIL); + } + } + } + + template + static void do_push_atomic(T element, std::atomic& q_element) noexcept { + assert(element != NIL); + if(Derived::spsc_) { + while(ATOMIC_QUEUE_UNLIKELY(q_element.load(X) != NIL)) + if(Derived::maximize_throughput_) + spin_loop_pause(); + q_element.store(element, R); + } + else { + for(T expected = NIL; ATOMIC_QUEUE_UNLIKELY(!q_element.compare_exchange_strong(expected, element, R, X)); expected = NIL) { + do + spin_loop_pause(); // (1) Wait for store (2) to complete. + while(Derived::maximize_throughput_ && q_element.load(X) != NIL); + } + } + } + + enum State : unsigned char { EMPTY, STORING, STORED, LOADING }; + + template + static T do_pop_any(std::atomic& state, T& q_element) noexcept { + if(Derived::spsc_) { + while(ATOMIC_QUEUE_UNLIKELY(state.load(A) != STORED)) + if(Derived::maximize_throughput_) + spin_loop_pause(); + T element{std::move(q_element)}; + state.store(EMPTY, R); + return element; + } + else { + for(;;) { + unsigned char expected = STORED; + if(ATOMIC_QUEUE_LIKELY(state.compare_exchange_strong(expected, LOADING, A, X))) { + T element{std::move(q_element)}; + state.store(EMPTY, R); + return element; + } + // Do speculative loads while busy-waiting to avoid broadcasting RFO messages. + do + spin_loop_pause(); + while(Derived::maximize_throughput_ && state.load(X) != STORED); + } + } + } + + template + static void do_push_any(U&& element, std::atomic& state, T& q_element) noexcept { + if(Derived::spsc_) { + while(ATOMIC_QUEUE_UNLIKELY(state.load(A) != EMPTY)) + if(Derived::maximize_throughput_) + spin_loop_pause(); + q_element = std::forward(element); + state.store(STORED, R); + } + else { + for(;;) { + unsigned char expected = EMPTY; + if(ATOMIC_QUEUE_LIKELY(state.compare_exchange_strong(expected, STORING, A, X))) { + q_element = std::forward(element); + state.store(STORED, R); + return; + } + // Do speculative loads while busy-waiting to avoid broadcasting RFO messages. + do + spin_loop_pause(); + while(Derived::maximize_throughput_ && state.load(X) != EMPTY); + } + } + } + +public: + template + bool try_push(T&& element) noexcept { + auto head = head_.load(X); + if(Derived::spsc_) { + if(static_cast(head - tail_.load(X)) >= static_cast(static_cast(*this).size_)) + return false; + head_.store(head + 1, X); + } + else { + do { + if(static_cast(head - tail_.load(X)) >= static_cast(static_cast(*this).size_)) + return false; + } while(ATOMIC_QUEUE_UNLIKELY(!head_.compare_exchange_strong(head, head + 1, A, X))); // This loop is not FIFO. + } + + static_cast(*this).do_push(std::forward(element), head); + return true; + } + + template + bool try_pop(T& element) noexcept { + auto tail = tail_.load(X); + if(Derived::spsc_) { + if(static_cast(head_.load(X) - tail) <= 0) + return false; + tail_.store(tail + 1, X); + } + else { + do { + if(static_cast(head_.load(X) - tail) <= 0) + return false; + } while(ATOMIC_QUEUE_UNLIKELY(!tail_.compare_exchange_strong(tail, tail + 1, A, X))); // This loop is not FIFO. + } + + element = static_cast(*this).do_pop(tail); + return true; + } + + template + void push(T&& element) noexcept { + unsigned head; + if(Derived::spsc_) { + head = head_.load(X); + head_.store(head + 1, X); + } + else { + constexpr auto memory_order = Derived::total_order_ ? std::memory_order_seq_cst : std::memory_order_acquire; + head = head_.fetch_add(1, memory_order); // FIFO and total order on Intel regardless, as of 2019. + } + static_cast(*this).do_push(std::forward(element), head); + } + + auto pop() noexcept { + unsigned tail; + if(Derived::spsc_) { + tail = tail_.load(X); + tail_.store(tail + 1, X); + } + else { + constexpr auto memory_order = Derived::total_order_ ? std::memory_order_seq_cst : std::memory_order_acquire; + tail = tail_.fetch_add(1, memory_order); // FIFO and total order on Intel regardless, as of 2019. + } + return static_cast(*this).do_pop(tail); + } + + bool was_empty() const noexcept { + return !was_size(); + } + + bool was_full() const noexcept { + return was_size() >= static_cast(static_cast(*this).size_); + } + + unsigned was_size() const noexcept { + // tail_ can be greater than head_ because of consumers doing pop, rather that try_pop, when the queue is empty. + return (std::max)(static_cast(head_.load(X) - tail_.load(X)), 0); + } + + unsigned capacity() const noexcept { + return static_cast(*this).size_; + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +template +class AtomicQueue : public AtomicQueueCommon> { + using Base = AtomicQueueCommon>; + friend Base; + + static constexpr unsigned size_ = MINIMIZE_CONTENTION ? details::round_up_to_power_of_2(SIZE) : SIZE; + static constexpr int SHUFFLE_BITS = details::GetIndexShuffleBits)>::value; + static constexpr bool total_order_ = TOTAL_ORDER; + static constexpr bool spsc_ = SPSC; + static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; + + alignas(CACHE_LINE_SIZE) std::atomic elements_[size_] = {}; // Empty elements are NIL. + + T do_pop(unsigned tail) noexcept { + std::atomic& q_element = details::map(elements_, tail % size_); + return Base::template do_pop_atomic(q_element); + } + + void do_push(T element, unsigned head) noexcept { + std::atomic& q_element = details::map(elements_, head % size_); + Base::template do_push_atomic(element, q_element); + } + +public: + using value_type = T; + + AtomicQueue() noexcept { + assert(std::atomic{NIL}.is_lock_free()); // This queue is for atomic elements only. AtomicQueue2 is for non-atomic ones. + if(T{} != NIL) + for(auto& element : elements_) + element.store(NIL, X); + } + + AtomicQueue(AtomicQueue const&) = delete; + AtomicQueue& operator=(AtomicQueue const&) = delete; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +template +class AtomicQueue2 : public AtomicQueueCommon> { + using Base = AtomicQueueCommon>; + using State = typename Base::State; + friend Base; + + static constexpr unsigned size_ = MINIMIZE_CONTENTION ? details::round_up_to_power_of_2(SIZE) : SIZE; + static constexpr int SHUFFLE_BITS = details::GetIndexShuffleBits::value; + static constexpr bool total_order_ = TOTAL_ORDER; + static constexpr bool spsc_ = SPSC; + static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; + + alignas(CACHE_LINE_SIZE) std::atomic states_[size_] = {}; + alignas(CACHE_LINE_SIZE) T elements_[size_] = {}; + + T do_pop(unsigned tail) noexcept { + unsigned index = details::remap_index(tail % size_); + return Base::template do_pop_any(states_[index], elements_[index]); + } + + template + void do_push(U&& element, unsigned head) noexcept { + unsigned index = details::remap_index(head % size_); + Base::template do_push_any(std::forward(element), states_[index], elements_[index]); + } + +public: + using value_type = T; + + AtomicQueue2() noexcept = default; + AtomicQueue2(AtomicQueue2 const&) = delete; + AtomicQueue2& operator=(AtomicQueue2 const&) = delete; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +template, T NIL = T{}, bool MAXIMIZE_THROUGHPUT = true, bool TOTAL_ORDER = false, bool SPSC = false> +class AtomicQueueB : public AtomicQueueCommon>, + private std::allocator_traits::template rebind_alloc> { + using Base = AtomicQueueCommon>; + friend Base; + + static constexpr bool total_order_ = TOTAL_ORDER; + static constexpr bool spsc_ = SPSC; + static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; + + using AllocatorElements = typename std::allocator_traits::template rebind_alloc>; + + static constexpr auto ELEMENTS_PER_CACHE_LINE = CACHE_LINE_SIZE / sizeof(std::atomic); + static_assert(ELEMENTS_PER_CACHE_LINE, "Unexpected ELEMENTS_PER_CACHE_LINE."); + + static constexpr auto SHUFFLE_BITS = details::GetCacheLineIndexBits::value; + static_assert(SHUFFLE_BITS, "Unexpected SHUFFLE_BITS."); + + // AtomicQueueCommon members are stored into by readers and writers. + // Allocate these immutable members on another cache line which never gets invalidated by stores. + alignas(CACHE_LINE_SIZE) unsigned size_; + std::atomic* elements_; + + T do_pop(unsigned tail) noexcept { + std::atomic& q_element = details::map(elements_, tail & (size_ - 1)); + return Base::template do_pop_atomic(q_element); + } + + void do_push(T element, unsigned head) noexcept { + std::atomic& q_element = details::map(elements_, head & (size_ - 1)); + Base::template do_push_atomic(element, q_element); + } + +public: + using value_type = T; + + // The special member functions are not thread-safe. + + AtomicQueueB(unsigned size) + : size_((std::max)(details::round_up_to_power_of_2(size), 1u << (SHUFFLE_BITS * 2))) + , elements_(AllocatorElements::allocate(size_)) { + assert(std::atomic{NIL}.is_lock_free()); // This queue is for atomic elements only. AtomicQueueB2 is for non-atomic ones. + for(auto p = elements_, q = elements_ + size_; p < q; ++p) + p->store(NIL, X); + } + + AtomicQueueB(AtomicQueueB&& b) noexcept + : Base(static_cast(b)) + , AllocatorElements(static_cast(b)) // TODO: This must be noexcept, static_assert that. + , size_(b.size_) + , elements_(b.elements_) { + b.size_ = 0; + b.elements_ = 0; + } + + AtomicQueueB& operator=(AtomicQueueB&& b) noexcept { + b.swap(*this); + return *this; + } + + ~AtomicQueueB() noexcept { + if(elements_) + AllocatorElements::deallocate(elements_, size_); // TODO: This must be noexcept, static_assert that. + } + + void swap(AtomicQueueB& b) noexcept { + using std::swap; + this->Base::swap(b); + swap(static_cast(*this), static_cast(b)); + swap(size_, b.size_); + swap(elements_, b.elements_); + } + + friend void swap(AtomicQueueB& a, AtomicQueueB& b) { + a.swap(b); + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +template, bool MAXIMIZE_THROUGHPUT = true, bool TOTAL_ORDER = false, bool SPSC = false> +class AtomicQueueB2 : public AtomicQueueCommon>, + private A, + private std::allocator_traits::template rebind_alloc> { + using Base = AtomicQueueCommon>; + using State = typename Base::State; + friend Base; + + static constexpr bool total_order_ = TOTAL_ORDER; + static constexpr bool spsc_ = SPSC; + static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; + + using AllocatorElements = A; + using AllocatorStates = typename std::allocator_traits::template rebind_alloc>; + + // AtomicQueueCommon members are stored into by readers and writers. + // Allocate these immutable members on another cache line which never gets invalidated by stores. + alignas(CACHE_LINE_SIZE) unsigned size_; + std::atomic* states_; + T* elements_; + + static constexpr auto STATES_PER_CACHE_LINE = CACHE_LINE_SIZE / sizeof(State); + static_assert(STATES_PER_CACHE_LINE, "Unexpected STATES_PER_CACHE_LINE."); + + static constexpr auto SHUFFLE_BITS = details::GetCacheLineIndexBits::value; + static_assert(SHUFFLE_BITS, "Unexpected SHUFFLE_BITS."); + + T do_pop(unsigned tail) noexcept { + unsigned index = details::remap_index(tail & (size_ - 1)); + return Base::template do_pop_any(states_[index], elements_[index]); + } + + template + void do_push(U&& element, unsigned head) noexcept { + unsigned index = details::remap_index(head & (size_ - 1)); + Base::template do_push_any(std::forward(element), states_[index], elements_[index]); + } + +public: + using value_type = T; + + // The special member functions are not thread-safe. + + AtomicQueueB2(unsigned size) + : size_((std::max)(details::round_up_to_power_of_2(size), 1u << (SHUFFLE_BITS * 2))) + , states_(AllocatorStates::allocate(size_)) + , elements_(AllocatorElements::allocate(size_)) { + for(auto p = states_, q = states_ + size_; p < q; ++p) + p->store(Base::EMPTY, X); + + AllocatorElements& ae = *this; + for(auto p = elements_, q = elements_ + size_; p < q; ++p) + std::allocator_traits::construct(ae, p); + } + + AtomicQueueB2(AtomicQueueB2&& b) noexcept + : Base(static_cast(b)) + , AllocatorElements(static_cast(b)) // TODO: This must be noexcept, static_assert that. + , AllocatorStates(static_cast(b)) // TODO: This must be noexcept, static_assert that. + , size_(b.size_) + , states_(b.states_) + , elements_(b.elements_) { + b.size_ = 0; + b.states_ = 0; + b.elements_ = 0; + } + + AtomicQueueB2& operator=(AtomicQueueB2&& b) noexcept { + b.swap(*this); + return *this; + } + + ~AtomicQueueB2() noexcept { + if(elements_) { + AllocatorElements& ae = *this; + for(auto p = elements_, q = elements_ + size_; p < q; ++p) + std::allocator_traits::destroy(ae, p); + AllocatorElements::deallocate(elements_, size_); // TODO: This must be noexcept, static_assert that. + AllocatorStates::deallocate(states_, size_); // TODO: This must be noexcept, static_assert that. + } + } + + void swap(AtomicQueueB2& b) noexcept { + using std::swap; + this->Base::swap(b); + swap(static_cast(*this), static_cast(b)); + swap(static_cast(*this), static_cast(b)); + swap(size_, b.size_); + swap(states_, b.states_); + swap(elements_, b.elements_); + } + + friend void swap(AtomicQueueB2& a, AtomicQueueB2& b) noexcept { + a.swap(b); + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +template +struct RetryDecorator : Queue { + using T = typename Queue::value_type; + + using Queue::Queue; + + void push(T element) noexcept { + while(!this->try_push(element)) + spin_loop_pause(); + } + + T pop() noexcept { + T element; + while(!this->try_pop(element)) + spin_loop_pause(); + return element; + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace atomic_queue + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#endif // ATOMIC_QUEUE_ATOMIC_QUEUE_H_INCLUDED diff --git a/Profiler/utils/atomic_queue/defs.h b/Profiler/utils/atomic_queue/defs.h new file mode 100644 index 00000000..c1e8e6e2 --- /dev/null +++ b/Profiler/utils/atomic_queue/defs.h @@ -0,0 +1,92 @@ +/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ +#ifndef ATOMIC_QUEUE_DEFS_H_INCLUDED +#define ATOMIC_QUEUE_DEFS_H_INCLUDED + +// Copyright (c) 2019 Maxim Egorushkin. MIT License. See the full licence in file LICENSE. + +#include + +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) +#include +namespace atomic_queue { +constexpr int CACHE_LINE_SIZE = 64; +static inline void spin_loop_pause() noexcept { + _mm_pause(); +} +} // namespace atomic_queue +#elif defined(__arm__) || defined(__aarch64__) +namespace atomic_queue { +constexpr int CACHE_LINE_SIZE = 64; +static inline void spin_loop_pause() noexcept { +#if (defined(__ARM_ARCH_6K__) || \ + defined(__ARM_ARCH_6Z__) || \ + defined(__ARM_ARCH_6ZK__) || \ + defined(__ARM_ARCH_6T2__) || \ + defined(__ARM_ARCH_7__) || \ + defined(__ARM_ARCH_7A__) || \ + defined(__ARM_ARCH_7R__) || \ + defined(__ARM_ARCH_7M__) || \ + defined(__ARM_ARCH_7S__) || \ + defined(__ARM_ARCH_8A__) || \ + defined(__aarch64__)) + asm volatile ("yield" ::: "memory"); +#else + asm volatile ("nop" ::: "memory"); +#endif +} +} // namespace atomic_queue +#elif defined(__ppc64__) || defined(__powerpc64__) +namespace atomic_queue { +constexpr int CACHE_LINE_SIZE = 128; // TODO: Review that this is the correct value. +static inline void spin_loop_pause() noexcept { + asm volatile("or 31,31,31 # very low priority"); // TODO: Review and benchmark that this is the right instruction. +} +} // namespace atomic_queue +#elif defined(__s390x__) +namespace atomic_queue { +constexpr int CACHE_LINE_SIZE = 256; // TODO: Review that this is the correct value. +static inline void spin_loop_pause() noexcept {} // TODO: Find the right instruction to use here, if any. +} // namespace atomic_queue +#elif defined(__riscv) +namespace atomic_queue { +constexpr int CACHE_LINE_SIZE = 64; +static inline void spin_loop_pause() noexcept { + asm volatile (".insn i 0x0F, 0, x0, x0, 0x010"); +} +} // namespace atomic_queue +#else +#warning "Unknown CPU architecture. Using L1 cache line size of 64 bytes and no spinloop pause instruction." +namespace atomic_queue { +constexpr int CACHE_LINE_SIZE = 64; // TODO: Review that this is the correct value. +static inline void spin_loop_pause() noexcept {} +} // namespace atomic_queue +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace atomic_queue { + +#if defined(__GNUC__) || defined(__clang__) +#define ATOMIC_QUEUE_LIKELY(expr) __builtin_expect(static_cast(expr), 1) +#define ATOMIC_QUEUE_UNLIKELY(expr) __builtin_expect(static_cast(expr), 0) +#define ATOMIC_QUEUE_NOINLINE __attribute__((noinline)) +#else +#define ATOMIC_QUEUE_LIKELY(expr) (expr) +#define ATOMIC_QUEUE_UNLIKELY(expr) (expr) +#define ATOMIC_QUEUE_NOINLINE +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +auto constexpr A = std::memory_order_acquire; +auto constexpr R = std::memory_order_release; +auto constexpr X = std::memory_order_relaxed; +auto constexpr C = std::memory_order_seq_cst; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace atomic_queue + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#endif // ATOMIC_QUEUE_DEFS_H_INCLUDED diff --git a/Profiler/utils/functionID_set/functionId_set.h b/Profiler/utils/functionID_set/functionId_set.h new file mode 100644 index 00000000..8defbb19 --- /dev/null +++ b/Profiler/utils/functionID_set/functionId_set.h @@ -0,0 +1,115 @@ +#pragma once +#include +class functionID_set +{ +private: + unsigned int default_size = 2'097'152; + unsigned int current_size = default_size; + unsigned int num_elements = 0; + unsigned int max_elements = default_size / 2; + FunctionID* set = new FunctionID[default_size] {0}; + + unsigned int xor_values[10] = { 2134038170, 2362107340, 3229546752, 1302939050, 200405764, 79516981, 2052331209, 3415124361, 940592490, 430981309 }; + + void adjust_size() { + current_size *= 2; + max_elements = current_size / 2; + FunctionID* old_set = set; + set = new FunctionID[current_size]{ 0 }; + for (unsigned int i = 0; i < current_size / 2; i++) { + if (old_set[i] != 0) { + insert(old_set[i]); + } + } + delete old_set; + } + +public: + + ~functionID_set() { + delete set; + } + + void clear() { + delete set; + current_size = default_size; + max_elements = default_size / 2; + num_elements = 0; + set = new FunctionID[default_size]{ 0 }; + } + + unsigned int size() { + return current_size; + } + + FunctionID at(unsigned int i) { + return set[i]; + } + + bool contains(FunctionID f) { + unsigned int position = f & (current_size - 1); + if (set[position] == 0) { + return false; + } + if (set[position] == f) { + return true; + } + + for (int i = 0; i < 10; i++) { + position = (f ^ xor_values[0]) & (current_size - 1); + if (set[position] == 0) { + return false; + } + if (set[position] == f) { + return true; + } + } + + position = (f + 1) & (current_size - 1); + while (set[position] != f) { + if (set[position] == 0) { + return false; + } + position = (position + 1) & (current_size - 1); + } + return set[position]; + } + + + void insert(FunctionID f) { + num_elements++; + if (num_elements > max_elements) { + adjust_size(); + } + unsigned int position = f & (current_size - 1); + if (set[position] == f) { + return; + } + if (set[position] == 0) { + set[position] = f; + return; + } + + + for (int i = 0; i < 10; i++) { + position = (f ^ xor_values[0]) & (current_size - 1); + if (set[position] == f) { + return; + } + if (set[position] == 0) { + set[position] = f; + return; + } + } + + position = (f + 1) & (current_size - 1); + while (set[position] != 0) { + if (set[position] == f) { + return; + } + position = (position + 1) & (current_size - 1); + } + set[position] = f; + } +}; + diff --git a/Profiler/utils/robin_hood/robin_hood.h b/Profiler/utils/robin_hood/robin_hood.h new file mode 100644 index 00000000..00a943c4 --- /dev/null +++ b/Profiler/utils/robin_hood/robin_hood.h @@ -0,0 +1,2551 @@ +// ______ _____ ______ _________ +// ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ / +// __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ / +// _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ / +// /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/ +// _/_____/ +// +// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20 +// https://github.com/martinus/robin-hood-hashing +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2018-2021 Martin Ankerl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef ROBIN_HOOD_H_INCLUDED +#define ROBIN_HOOD_H_INCLUDED + +// see https://semver.org/ +#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes +#define ROBIN_HOOD_VERSION_MINOR 11 // for adding functionality in a backwards-compatible manner +#define ROBIN_HOOD_VERSION_PATCH 5 // for backwards-compatible bug fixes + +#include +#include +#include +#include +#include +#include // only to support hash of smart pointers +#include +#include +#include +#include +#if __cplusplus >= 201703L +# include +#endif + +// #define ROBIN_HOOD_LOG_ENABLED +#ifdef ROBIN_HOOD_LOG_ENABLED +# include +# define ROBIN_HOOD_LOG(...) \ + std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; +#else +# define ROBIN_HOOD_LOG(x) +#endif + +// #define ROBIN_HOOD_TRACE_ENABLED +#ifdef ROBIN_HOOD_TRACE_ENABLED +# include +# define ROBIN_HOOD_TRACE(...) \ + std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; +#else +# define ROBIN_HOOD_TRACE(x) +#endif + +// #define ROBIN_HOOD_COUNT_ENABLED +#ifdef ROBIN_HOOD_COUNT_ENABLED +# include +# define ROBIN_HOOD_COUNT(x) ++counts().x; +namespace robin_hood { + struct Counts { + uint64_t shiftUp{}; + uint64_t shiftDown{}; + }; + inline std::ostream& operator<<(std::ostream& os, Counts const& c) { + return os << c.shiftUp << " shiftUp" << std::endl << c.shiftDown << " shiftDown" << std::endl; + } + + static Counts& counts() { + static Counts counts{}; + return counts; + } +} // namespace robin_hood +#else +# define ROBIN_HOOD_COUNT(x) +#endif + +// all non-argument macros should use this facility. See +// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ +#define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x() + +// mark unused members with this macro +#define ROBIN_HOOD_UNUSED(identifier) + +// bitness +#if SIZE_MAX == UINT32_MAX +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32 +#elif SIZE_MAX == UINT64_MAX +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64 +#else +# error Unsupported bitness +#endif + +// endianess +#ifdef _MSC_VER +# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0 +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \ + (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#endif + +// inline +#ifdef _MSC_VER +# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline) +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline)) +#endif + +// exceptions +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0 +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1 +#endif + +// count leading/trailing bits +#if !defined(ROBIN_HOOD_DISABLE_INTRINSICS) +# ifdef _MSC_VER +# if ROBIN_HOOD(BITNESS) == 32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64 +# endif +# include +# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD)) +# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \ + [](size_t mask) noexcept -> int { \ + unsigned long index; \ + return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast(index) \ + : ROBIN_HOOD(BITNESS); \ + }(x) +# else +# if ROBIN_HOOD(BITNESS) == 32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl +# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll +# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll +# endif +# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS)) +# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS)) +# endif +#endif + +// fallthrough +#ifndef __has_cpp_attribute // For backwards compatibility +# define __has_cpp_attribute(x) 0 +#endif +#if __has_cpp_attribute(clang::fallthrough) +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]] +#elif __has_cpp_attribute(gnu::fallthrough) +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]] +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() +#endif + +// likely/unlikely +#ifdef _MSC_VER +# define ROBIN_HOOD_LIKELY(condition) condition +# define ROBIN_HOOD_UNLIKELY(condition) condition +#else +# define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1) +# define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0) +#endif + +// detect if native wchar_t type is availiable in MSVC +#ifdef _MSC_VER +# ifdef _NATIVE_WCHAR_T_DEFINED +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 0 +# endif +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 +#endif + +// detect if MSVC supports the pair(std::piecewise_construct_t,...) consructor being constexpr +#ifdef _MSC_VER +# if _MSC_VER <= 1900 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 1 +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 +# endif +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 +#endif + +// workaround missing "is_trivially_copyable" in g++ < 5.0 +// See https://stackoverflow.com/a/31798726/48181 +#if defined(__GNUC__) && __GNUC__ < 5 +# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) +#else +# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value +#endif + +// helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) +# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]] +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() +#endif + +namespace robin_hood { + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) +# define ROBIN_HOOD_STD std +#else + + // c++11 compatibility layer + namespace ROBIN_HOOD_STD { + template + struct alignment_of + : std::integral_constant::type)> {}; + + template + class integer_sequence { + public: + using value_type = T; + static_assert(std::is_integral::value, "not integral type"); + static constexpr std::size_t size() noexcept { + return sizeof...(Ints); + } + }; + template + using index_sequence = integer_sequence; + + namespace detail_ { + template + struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)"); + + template + struct IntSeqCombiner; + + template + struct IntSeqCombiner, integer_sequence> { + using TResult = integer_sequence; + }; + + using TResult = + typename IntSeqCombiner::TResult, + typename IntSeqImpl::TResult>::TResult; + }; + + template + struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0, "unexpected argument (Begin<0)"); + using TResult = integer_sequence; + }; + + template + struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0, "unexpected argument (Begin<0)"); + using TResult = integer_sequence; + }; + } // namespace detail_ + + template + using make_integer_sequence = typename detail_::IntSeqImpl::TResult; + + template + using make_index_sequence = make_integer_sequence; + + template + using index_sequence_for = make_index_sequence; + + } // namespace ROBIN_HOOD_STD + +#endif + + namespace detail { + + // make sure we static_cast to the correct type for hash_int +#if ROBIN_HOOD(BITNESS) == 64 + using SizeT = uint64_t; +#else + using SizeT = uint32_t; +#endif + + template + T rotr(T x, unsigned k) { + return (x >> k) | (x << (8U * sizeof(T) - k)); + } + + // This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to + // 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with + // care! + template + inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept { + return reinterpret_cast(ptr); + } + + template + inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept { + return reinterpret_cast(ptr); + } + + // make sure this is not inlined as it is slow and dramatically enlarges code, thus making other + // inlinings more difficult. Throws are also generally the slow path. + template + [[noreturn]] ROBIN_HOOD(NOINLINE) +#if ROBIN_HOOD(HAS_EXCEPTIONS) + void doThrow(Args&&... args) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) + throw E(std::forward(args)...); + } +#else + void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) { + abort(); + } +#endif + + template + T* assertNotNull(T* t, Args&&... args) { + if (ROBIN_HOOD_UNLIKELY(nullptr == t)) { + doThrow(std::forward(args)...); + } + return t; + } + + template + inline T unaligned_load(void const* ptr) noexcept { + // using memcpy so we don't get into unaligned load problems. + // compiler should optimize this very well anyways. + T t; + std::memcpy(&t, ptr, sizeof(T)); + return t; + } + + // Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor, + // and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a + // pointer. + template + class BulkPoolAllocator { + public: + BulkPoolAllocator() noexcept = default; + + // does not copy anything, just creates a new allocator. + BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept + : mHead(nullptr) + , mListForFree(nullptr) {} + + BulkPoolAllocator(BulkPoolAllocator&& o) noexcept + : mHead(o.mHead) + , mListForFree(o.mListForFree) { + o.mListForFree = nullptr; + o.mHead = nullptr; + } + + BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept { + reset(); + mHead = o.mHead; + mListForFree = o.mListForFree; + o.mListForFree = nullptr; + o.mHead = nullptr; + return *this; + } + + BulkPoolAllocator& + // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) + operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept { + // does not do anything + return *this; + } + + ~BulkPoolAllocator() noexcept { + reset(); + } + + // Deallocates all allocated memory. + void reset() noexcept { + while (mListForFree) { + T* tmp = *mListForFree; + ROBIN_HOOD_LOG("std::free") + std::free(mListForFree); + mListForFree = reinterpret_cast_no_cast_align_warning(tmp); + } + mHead = nullptr; + } + + // allocates, but does NOT initialize. Use in-place new constructor, e.g. + // T* obj = pool.allocate(); + // ::new (static_cast(obj)) T(); + T* allocate() { + T* tmp = mHead; + if (!tmp) { + tmp = performAllocation(); + } + + mHead = *reinterpret_cast_no_cast_align_warning(tmp); + return tmp; + } + + // does not actually deallocate but puts it in store. + // make sure you have already called the destructor! e.g. with + // obj->~T(); + // pool.deallocate(obj); + void deallocate(T* obj) noexcept { + *reinterpret_cast_no_cast_align_warning(obj) = mHead; + mHead = obj; + } + + // Adds an already allocated block of memory to the allocator. This allocator is from now on + // responsible for freeing the data (with free()). If the provided data is not large enough to + // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor. + void addOrFree(void* ptr, const size_t numBytes) noexcept { + // calculate number of available elements in ptr + if (numBytes < ALIGNMENT + ALIGNED_SIZE) { + // not enough data for at least one element. Free and return. + ROBIN_HOOD_LOG("std::free") + std::free(ptr); + } + else { + ROBIN_HOOD_LOG("add to buffer") + add(ptr, numBytes); + } + } + + void swap(BulkPoolAllocator& other) noexcept { + using std::swap; + swap(mHead, other.mHead); + swap(mListForFree, other.mListForFree); + } + + private: + // iterates the list of allocated memory to calculate how many to alloc next. + // Recalculating this each time saves us a size_t member. + // This ignores the fact that memory blocks might have been added manually with addOrFree. In + // practice, this should not matter much. + ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept { + auto tmp = mListForFree; + size_t numAllocs = MinNumAllocs; + + while (numAllocs * 2 <= MaxNumAllocs && tmp) { + auto x = reinterpret_cast(tmp); + tmp = *x; + numAllocs *= 2; + } + + return numAllocs; + } + + // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree(). + void add(void* ptr, const size_t numBytes) noexcept { + const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE; + + auto data = reinterpret_cast(ptr); + + // link free list + auto x = reinterpret_cast(data); + *x = mListForFree; + mListForFree = data; + + // create linked list for newly allocated data + auto* const headT = + reinterpret_cast_no_cast_align_warning(reinterpret_cast(ptr) + ALIGNMENT); + + auto* const head = reinterpret_cast(headT); + + // Visual Studio compiler automatically unrolls this loop, which is pretty cool + for (size_t i = 0; i < numElements; ++i) { + *reinterpret_cast_no_cast_align_warning(head + i * ALIGNED_SIZE) = + head + (i + 1) * ALIGNED_SIZE; + } + + // last one points to 0 + *reinterpret_cast_no_cast_align_warning(head + (numElements - 1) * ALIGNED_SIZE) = + mHead; + mHead = headT; + } + + // Called when no memory is available (mHead == 0). + // Don't inline this slow path. + ROBIN_HOOD(NOINLINE) T* performAllocation() { + size_t const numElementsToAlloc = calcNumElementsToAlloc(); + + // alloc new memory: [prev |T, T, ... T] + size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc; + ROBIN_HOOD_LOG("std::malloc " << bytes << " = " << ALIGNMENT << " + " << ALIGNED_SIZE + << " * " << numElementsToAlloc) + add(assertNotNull(std::malloc(bytes)), bytes); + return mHead; + } + + // enforce byte alignment of the T's +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) + static constexpr size_t ALIGNMENT = + (std::max)(std::alignment_of::value, std::alignment_of::value); +#else + static const size_t ALIGNMENT = + (ROBIN_HOOD_STD::alignment_of::value > ROBIN_HOOD_STD::alignment_of::value) + ? ROBIN_HOOD_STD::alignment_of::value + : +ROBIN_HOOD_STD::alignment_of::value; // the + is for walkarround +#endif + + static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT; + + static_assert(MinNumAllocs >= 1, "MinNumAllocs"); + static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs"); + static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE"); + static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod"); + static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT"); + + T* mHead{ nullptr }; + T** mListForFree{ nullptr }; + }; + + template + struct NodeAllocator; + + // dummy allocator that does nothing + template + struct NodeAllocator { + + // we are not using the data, so just free it. + void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept { + ROBIN_HOOD_LOG("std::free") + std::free(ptr); + } + }; + + template + struct NodeAllocator : public BulkPoolAllocator {}; + + // c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making + // my own here. + namespace swappable { +#if ROBIN_HOOD(CXX) < ROBIN_HOOD(CXX17) + using std::swap; + template + struct nothrow { + static const bool value = noexcept(swap(std::declval(), std::declval())); + }; +#else + template + struct nothrow { + static const bool value = std::is_nothrow_swappable::value; + }; +#endif + } // namespace swappable + + } // namespace detail + + struct is_transparent_tag {}; + + // A custom pair implementation is used in the map because std::pair is not is_trivially_copyable, + // which means it would not be allowed to be used in std::memcpy. This struct is copyable, which is + // also tested. + template + struct pair { + using first_type = T1; + using second_type = T2; + + template ::value&& + std::is_default_constructible::value>::type> + constexpr pair() noexcept(noexcept(U1()) && noexcept(U2())) + : first() + , second() {} + + // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. + explicit constexpr pair(std::pair const& o) noexcept( + noexcept(T1(std::declval())) && noexcept(T2(std::declval()))) + : first(o.first) + , second(o.second) {} + + // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. + explicit constexpr pair(std::pair&& o) noexcept(noexcept( + T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) + : first(std::move(o.first)) + , second(std::move(o.second)) {} + + constexpr pair(T1&& a, T2&& b) noexcept(noexcept( + T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) + : first(std::move(a)) + , second(std::move(b)) {} + + template + constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward( + std::declval()))) && noexcept(T2(std::forward(std::declval())))) + : first(std::forward(a)) + , second(std::forward(b)) {} + + template + // MSVC 2015 produces error "C2476: ‘constexpr’ constructor does not initialize all members" + // if this constructor is constexpr +#if !ROBIN_HOOD(BROKEN_CONSTEXPR) + constexpr +#endif + pair(std::piecewise_construct_t /*unused*/, std::tuple a, + std::tuple + b) noexcept(noexcept(pair(std::declval&>(), + std::declval&>(), + ROBIN_HOOD_STD::index_sequence_for(), + ROBIN_HOOD_STD::index_sequence_for()))) + : pair(a, b, ROBIN_HOOD_STD::index_sequence_for(), + ROBIN_HOOD_STD::index_sequence_for()) { + } + + // constructor called from the std::piecewise_construct_t ctor + template + pair(std::tuple& a, std::tuple& b, ROBIN_HOOD_STD::index_sequence /*unused*/, ROBIN_HOOD_STD::index_sequence /*unused*/) noexcept( + noexcept(T1(std::forward(std::get( + std::declval&>()))...)) && noexcept(T2(std:: + forward(std::get( + std::declval&>()))...))) + : first(std::forward(std::get(a))...) + , second(std::forward(std::get(b))...) { + // make visual studio compiler happy about warning about unused a & b. + // Visual studio's pair implementation disables warning 4100. + (void)a; + (void)b; + } + + void swap(pair& o) noexcept((detail::swappable::nothrow::value) && + (detail::swappable::nothrow::value)) { + using std::swap; + swap(first, o.first); + swap(second, o.second); + } + + T1 first; // NOLINT(misc-non-private-member-variables-in-classes) + T2 second; // NOLINT(misc-non-private-member-variables-in-classes) + }; + + template + inline void swap(pair& a, pair& b) noexcept( + noexcept(std::declval&>().swap(std::declval&>()))) { + a.swap(b); + } + + template + inline constexpr bool operator==(pair const& x, pair const& y) { + return (x.first == y.first) && (x.second == y.second); + } + template + inline constexpr bool operator!=(pair const& x, pair const& y) { + return !(x == y); + } + template + inline constexpr bool operator<(pair const& x, pair const& y) noexcept(noexcept( + std::declval() < std::declval()) && noexcept(std::declval() < + std::declval())) { + return x.first < y.first || (!(y.first < x.first) && x.second < y.second); + } + template + inline constexpr bool operator>(pair const& x, pair const& y) { + return y < x; + } + template + inline constexpr bool operator<=(pair const& x, pair const& y) { + return !(x > y); + } + template + inline constexpr bool operator>=(pair const& x, pair const& y) { + return !(x < y); + } + + inline size_t hash_bytes(void const* ptr, size_t len) noexcept { + static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995); + static constexpr uint64_t seed = UINT64_C(0xe17a1465); + static constexpr unsigned int r = 47; + + auto const* const data64 = static_cast(ptr); + uint64_t h = seed ^ (len * m); + + size_t const n_blocks = len / 8; + for (size_t i = 0; i < n_blocks; ++i) { + auto k = detail::unaligned_load(data64 + i); + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + } + + auto const* const data8 = reinterpret_cast(data64 + n_blocks); + switch (len & 7U) { + case 7: + h ^= static_cast(data8[6]) << 48U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 6: + h ^= static_cast(data8[5]) << 40U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 5: + h ^= static_cast(data8[4]) << 32U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 4: + h ^= static_cast(data8[3]) << 24U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 3: + h ^= static_cast(data8[2]) << 16U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 2: + h ^= static_cast(data8[1]) << 8U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 1: + h ^= static_cast(data8[0]); + h *= m; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + default: + break; + } + + h ^= h >> r; + + // not doing the final step here, because this will be done by keyToIdx anyways + // h *= m; + // h ^= h >> r; + return static_cast(h); + } + + inline size_t hash_int(uint64_t x) noexcept { + // tried lots of different hashes, let's stick with murmurhash3. It's simple, fast, well tested, + // and doesn't need any special 128bit operations. + x ^= x >> 33U; + x *= UINT64_C(0xff51afd7ed558ccd); + x ^= x >> 33U; + + // not doing the final step here, because this will be done by keyToIdx anyways + // x *= UINT64_C(0xc4ceb9fe1a85ec53); + // x ^= x >> 33U; + return static_cast(x); + } + + // A thin wrapper around std::hash, performing an additional simple mixing step of the result. + template + struct hash : public std::hash { + size_t operator()(T const& obj) const + noexcept(noexcept(std::declval>().operator()(std::declval()))) { + // call base hash + auto result = std::hash::operator()(obj); + // return mixed of that, to be save against identity has + return hash_int(static_cast(result)); + } + }; + + template + struct hash> { + size_t operator()(std::basic_string const& str) const noexcept { + return hash_bytes(str.data(), sizeof(CharT) * str.size()); + } + }; + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) + template + struct hash> { + size_t operator()(std::basic_string_view const& sv) const noexcept { + return hash_bytes(sv.data(), sizeof(CharT) * sv.size()); + } + }; +#endif + + template + struct hash { + size_t operator()(T* ptr) const noexcept { + return hash_int(reinterpret_cast(ptr)); + } + }; + + template + struct hash> { + size_t operator()(std::unique_ptr const& ptr) const noexcept { + return hash_int(reinterpret_cast(ptr.get())); + } + }; + + template + struct hash> { + size_t operator()(std::shared_ptr const& ptr) const noexcept { + return hash_int(reinterpret_cast(ptr.get())); + } + }; + + template + struct hash::value>::type> { + size_t operator()(Enum e) const noexcept { + using Underlying = typename std::underlying_type::type; + return hash{}(static_cast(e)); + } + }; + +#define ROBIN_HOOD_HASH_INT(T) \ + template <> \ + struct hash { \ + size_t operator()(T const& obj) const noexcept { \ + return hash_int(static_cast(obj)); \ + } \ + } + +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wuseless-cast" +#endif + // see https://en.cppreference.com/w/cpp/utility/hash + ROBIN_HOOD_HASH_INT(bool); + ROBIN_HOOD_HASH_INT(char); + ROBIN_HOOD_HASH_INT(signed char); + ROBIN_HOOD_HASH_INT(unsigned char); + ROBIN_HOOD_HASH_INT(char16_t); + ROBIN_HOOD_HASH_INT(char32_t); +#if ROBIN_HOOD(HAS_NATIVE_WCHART) + ROBIN_HOOD_HASH_INT(wchar_t); +#endif + ROBIN_HOOD_HASH_INT(short); + ROBIN_HOOD_HASH_INT(unsigned short); + ROBIN_HOOD_HASH_INT(int); + ROBIN_HOOD_HASH_INT(unsigned int); + ROBIN_HOOD_HASH_INT(long); + ROBIN_HOOD_HASH_INT(long long); + ROBIN_HOOD_HASH_INT(unsigned long); + ROBIN_HOOD_HASH_INT(unsigned long long); +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +#endif + namespace detail { + + template + struct void_type { + using type = void; + }; + + template + struct has_is_transparent : public std::false_type {}; + + template + struct has_is_transparent::type> + : public std::true_type {}; + + // using wrapper classes for hash and key_equal prevents the diamond problem when the same type + // is used. see https://stackoverflow.com/a/28771920/48181 + template + struct WrapHash : public T { + WrapHash() = default; + explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval()))) + : T(o) {} + }; + + template + struct WrapKeyEqual : public T { + WrapKeyEqual() = default; + explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval()))) + : T(o) {} + }; + + // A highly optimized hashmap implementation, using the Robin Hood algorithm. + // + // In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but + // be about 2x faster in most cases and require much less allocations. + // + // This implementation uses the following memory layout: + // + // [Node, Node, ... Node | info, info, ... infoSentinel ] + // + // * Node: either a DataNode that directly has the std::pair as member, + // or a DataNode with a pointer to std::pair. Which DataNode representation to use + // depends on how fast the swap() operation is. Heuristically, this is automatically choosen + // based on sizeof(). there are always 2^n Nodes. + // + // * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes. + // Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the + // corresponding node contains data. Set to 2 means the corresponding Node is filled, but it + // actually belongs to the previous position and was pushed out because that place is already + // taken. + // + // * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the + // need for a idx variable. + // + // According to STL, order of templates has effect on throughput. That's why I've moved the + // boolean to the front. + // https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/ + template + class Table + : public WrapHash, + public WrapKeyEqual, + detail::NodeAllocator< + typename std::conditional< + std::is_void::value, Key, + robin_hood::pair::type, T>>::type, + 4, 16384, IsFlat> { + public: + static constexpr bool is_flat = IsFlat; + static constexpr bool is_map = !std::is_void::value; + static constexpr bool is_set = !is_map; + static constexpr bool is_transparent = + has_is_transparent::value && has_is_transparent::value; + + using key_type = Key; + using mapped_type = T; + using value_type = typename std::conditional< + is_set, Key, + robin_hood::pair::type, T>>::type; + using size_type = size_t; + using hasher = Hash; + using key_equal = KeyEqual; + using Self = Table; + + private: + static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100, + "MaxLoadFactor100 needs to be >10 && < 100"); + + using WHash = WrapHash; + using WKeyEqual = WrapKeyEqual; + + // configuration defaults + + // make sure we have 8 elements, needed to quickly rehash mInfo + static constexpr size_t InitialNumElements = sizeof(uint64_t); + static constexpr uint32_t InitialInfoNumBits = 5; + static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits; + static constexpr size_t InfoMask = InitialInfoInc - 1U; + static constexpr uint8_t InitialInfoHashShift = 0; + using DataPool = detail::NodeAllocator; + + // type needs to be wider than uint8_t. + using InfoType = uint32_t; + + // DataNode //////////////////////////////////////////////////////// + + // Primary template for the data node. We have special implementations for small and big + // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these + // on the heap so swap merely swaps a pointer. + template + class DataNode {}; + + // Small: just allocate on the stack. + template + class DataNode final { + public: + template + explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept( + noexcept(value_type(std::forward(args)...))) + : mData(std::forward(args)...) {} + + DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept( + std::is_nothrow_move_constructible::value) + : mData(std::move(n.mData)) {} + + // doesn't do anything + void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {} + void destroyDoNotDeallocate() noexcept {} + + value_type const* operator->() const noexcept { + return &mData; + } + value_type* operator->() noexcept { + return &mData; + } + + const value_type& operator*() const noexcept { + return mData; + } + + value_type& operator*() noexcept { + return mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return mData.first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type + getFirst() const noexcept { + return mData.first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() const noexcept { + return mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() noexcept { + return mData.second; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() const noexcept { + return mData.second; + } + + void swap(DataNode& o) noexcept( + noexcept(std::declval().swap(std::declval()))) { + mData.swap(o.mData); + } + + private: + value_type mData; + }; + + // big object: allocate on heap. + template + class DataNode { + public: + template + explicit DataNode(M& map, Args&&... args) + : mData(map.allocate()) { + ::new (static_cast(mData)) value_type(std::forward(args)...); + } + + DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept + : mData(std::move(n.mData)) {} + + void destroy(M& map) noexcept { + // don't deallocate, just put it into list of datapool. + mData->~value_type(); + map.deallocate(mData); + } + + void destroyDoNotDeallocate() noexcept { + mData->~value_type(); + } + + value_type const* operator->() const noexcept { + return mData; + } + + value_type* operator->() noexcept { + return mData; + } + + const value_type& operator*() const { + return *mData; + } + + value_type& operator*() { + return *mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return mData->first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return *mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type + getFirst() const noexcept { + return mData->first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() const noexcept { + return *mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() noexcept { + return mData->second; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() const noexcept { + return mData->second; + } + + void swap(DataNode& o) noexcept { + using std::swap; + swap(mData, o.mData); + } + + private: + value_type* mData; + }; + + using Node = DataNode; + + // helpers for insertKeyPrepareEmptySpot: extract first entry (only const required) + ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(Node const& n) const noexcept { + return n.getFirst(); + } + + // in case we have void mapped_type, we are not using a pair, thus we just route k through. + // No need to disable this because it's just not used if not applicable. + ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(key_type const& k) const noexcept { + return k; + } + + // in case we have non-void mapped_type, we have a standard robin_hood::pair + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, key_type const&>::type + getFirstConst(value_type const& vt) const noexcept { + return vt.first; + } + + // Cloner ////////////////////////////////////////////////////////// + + template + struct Cloner; + + // fast path: Just copy data, without allocating anything. + template + struct Cloner { + void operator()(M const& source, M& target) const { + auto const* const src = reinterpret_cast(source.mKeyVals); + auto* tgt = reinterpret_cast(target.mKeyVals); + auto const numElementsWithBuffer = target.calcNumElementsWithBuffer(target.mMask + 1); + std::copy(src, src + target.calcNumBytesTotal(numElementsWithBuffer), tgt); + } + }; + + template + struct Cloner { + void operator()(M const& s, M& t) const { + auto const numElementsWithBuffer = t.calcNumElementsWithBuffer(t.mMask + 1); + std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(numElementsWithBuffer), t.mInfo); + + for (size_t i = 0; i < numElementsWithBuffer; ++i) { + if (t.mInfo[i]) { + ::new (static_cast(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]); + } + } + } + }; + + // Destroyer /////////////////////////////////////////////////////// + + template + struct Destroyer {}; + + template + struct Destroyer { + void nodes(M& m) const noexcept { + m.mNumElements = 0; + } + + void nodesDoNotDeallocate(M& m) const noexcept { + m.mNumElements = 0; + } + }; + + template + struct Destroyer { + void nodes(M& m) const noexcept { + m.mNumElements = 0; + // clear also resets mInfo to 0, that's sometimes not necessary. + auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); + + for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { + if (0 != m.mInfo[idx]) { + Node& n = m.mKeyVals[idx]; + n.destroy(m); + n.~Node(); + } + } + } + + void nodesDoNotDeallocate(M& m) const noexcept { + m.mNumElements = 0; + // clear also resets mInfo to 0, that's sometimes not necessary. + auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); + for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { + if (0 != m.mInfo[idx]) { + Node& n = m.mKeyVals[idx]; + n.destroyDoNotDeallocate(); + n.~Node(); + } + } + } + }; + + // Iter //////////////////////////////////////////////////////////// + + struct fast_forward_tag {}; + + // generic iterator for both const_iterator and iterator. + template + // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions) + class Iter { + private: + using NodePtr = typename std::conditional::type; + + public: + using difference_type = std::ptrdiff_t; + using value_type = typename Self::value_type; + using reference = typename std::conditional::type; + using pointer = typename std::conditional::type; + using iterator_category = std::forward_iterator_tag; + + // default constructed iterator can be compared to itself, but WON'T return true when + // compared to end(). + Iter() = default; + + // Rule of zero: nothing specified. The conversion constructor is only enabled for + // iterator to const_iterator, so it doesn't accidentally work as a copy ctor. + + // Conversion constructor from iterator to const_iterator. + template ::type> + // NOLINTNEXTLINE(hicpp-explicit-conversions) + Iter(Iter const& other) noexcept + : mKeyVals(other.mKeyVals) + , mInfo(other.mInfo) {} + + Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept + : mKeyVals(valPtr) + , mInfo(infoPtr) {} + + Iter(NodePtr valPtr, uint8_t const* infoPtr, + fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept + : mKeyVals(valPtr) + , mInfo(infoPtr) { + fastForward(); + } + + template ::type> + Iter& operator=(Iter const& other) noexcept { + mKeyVals = other.mKeyVals; + mInfo = other.mInfo; + return *this; + } + + // prefix increment. Undefined behavior if we are at end()! + Iter& operator++() noexcept { + mInfo++; + mKeyVals++; + fastForward(); + return *this; + } + + Iter operator++(int) noexcept { + Iter tmp = *this; + ++(*this); + return tmp; + } + + reference operator*() const { + return **mKeyVals; + } + + pointer operator->() const { + return &**mKeyVals; + } + + template + bool operator==(Iter const& o) const noexcept { + return mKeyVals == o.mKeyVals; + } + + template + bool operator!=(Iter const& o) const noexcept { + return mKeyVals != o.mKeyVals; + } + + private: + // fast forward to the next non-free info byte + // I've tried a few variants that don't depend on intrinsics, but unfortunately they are + // quite a bit slower than this one. So I've reverted that change again. See map_benchmark. + void fastForward() noexcept { + size_t n = 0; + while (0U == (n = detail::unaligned_load(mInfo))) { + mInfo += sizeof(size_t); + mKeyVals += sizeof(size_t); + } +#if defined(ROBIN_HOOD_DISABLE_INTRINSICS) + // we know for certain that within the next 8 bytes we'll find a non-zero one. + if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { + mInfo += 4; + mKeyVals += 4; + } + if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { + mInfo += 2; + mKeyVals += 2; + } + if (ROBIN_HOOD_UNLIKELY(0U == *mInfo)) { + mInfo += 1; + mKeyVals += 1; + } +#else +# if ROBIN_HOOD(LITTLE_ENDIAN) + auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8; +# else + auto inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8; +# endif + mInfo += inc; + mKeyVals += inc; +#endif + } + + friend class Table; + NodePtr mKeyVals{ nullptr }; + uint8_t const* mInfo{ nullptr }; + }; + + //////////////////////////////////////////////////////////////////// + + // highly performance relevant code. + // Lower bits are used for indexing into the array (2^n size) + // The upper 1-5 bits need to be a reasonable good hash, to save comparisons. + template + void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const { + // In addition to whatever hash is used, add another mul & shift so we get better hashing. + // This serves as a bad hash prevention, if the given data is + // badly mixed. + auto h = static_cast(WHash::operator()(key)); + + h *= mHashMultiplier; + h ^= h >> 33U; + + // the lower InitialInfoNumBits are reserved for info. + *info = mInfoInc + static_cast((h & InfoMask) >> mInfoHashShift); + *idx = (static_cast(h) >> InitialInfoNumBits) & mMask; + } + + // forwards the index by one, wrapping around at the end + void next(InfoType* info, size_t* idx) const noexcept { + *idx = *idx + 1; + *info += mInfoInc; + } + + void nextWhileLess(InfoType* info, size_t* idx) const noexcept { + // unrolling this by hand did not bring any speedups. + while (*info < mInfo[*idx]) { + next(info, idx); + } + } + + // Shift everything up by one element. Tries to move stuff around. + void + shiftUp(size_t startIdx, + size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable::value) { + auto idx = startIdx; + ::new (static_cast(mKeyVals + idx)) Node(std::move(mKeyVals[idx - 1])); + while (--idx != insertion_idx) { + mKeyVals[idx] = std::move(mKeyVals[idx - 1]); + } + + idx = startIdx; + while (idx != insertion_idx) { + ROBIN_HOOD_COUNT(shiftUp) + mInfo[idx] = static_cast(mInfo[idx - 1] + mInfoInc); + if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + --idx; + } + } + + void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable::value) { + // until we find one that is either empty or has zero offset. + // TODO(martinus) we don't need to move everything, just the last one for the same + // bucket. + mKeyVals[idx].destroy(*this); + + // until we find one that is either empty or has zero offset. + while (mInfo[idx + 1] >= 2 * mInfoInc) { + ROBIN_HOOD_COUNT(shiftDown) + mInfo[idx] = static_cast(mInfo[idx + 1] - mInfoInc); + mKeyVals[idx] = std::move(mKeyVals[idx + 1]); + ++idx; + } + + mInfo[idx] = 0; + // don't destroy, we've moved it + // mKeyVals[idx].destroy(*this); + mKeyVals[idx].~Node(); + } + + // copy of find(), except that it returns iterator instead of const_iterator. + template + ROBIN_HOOD(NODISCARD) + size_t findIdx(Other const& key) const { + size_t idx{}; + InfoType info{}; + keyToIdx(key, &idx, &info); + + do { + // unrolling this twice gives a bit of a speedup. More unrolling did not help. + if (info == mInfo[idx] && + ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { + return idx; + } + next(&info, &idx); + if (info == mInfo[idx] && + ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { + return idx; + } + next(&info, &idx); + } while (info <= mInfo[idx]); + + // nothing found! + return mMask == 0 ? 0 + : static_cast(std::distance( + mKeyVals, reinterpret_cast_no_cast_align_warning(mInfo))); + } + + void cloneData(const Table& o) { + Cloner()(o, *this); + } + + // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized. + // @return True on success, false if something went wrong + void insert_move(Node&& keyval) { + // we don't retry, fail if overflowing + // don't need to check max num elements + if (0 == mMaxNumElementsAllowed && !try_increase_info()) { + throwOverflowError(); + } + + size_t idx{}; + InfoType info{}; + keyToIdx(keyval.getFirst(), &idx, &info); + + // skip forward. Use <= because we are certain that the element is not there. + while (info <= mInfo[idx]) { + idx = idx + 1; + info += mInfoInc; + } + + // key not found, so we are now exactly where we want to insert it. + auto const insertion_idx = idx; + auto const insertion_info = static_cast(info); + if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + + // find an empty spot + while (0 != mInfo[idx]) { + next(&info, &idx); + } + + auto& l = mKeyVals[insertion_idx]; + if (idx == insertion_idx) { + ::new (static_cast(&l)) Node(std::move(keyval)); + } + else { + shiftUp(idx, insertion_idx); + l = std::move(keyval); + } + + // put at empty spot + mInfo[insertion_idx] = insertion_info; + + ++mNumElements; + } + + public: + using iterator = Iter; + using const_iterator = Iter; + + Table() noexcept(noexcept(Hash()) && noexcept(KeyEqual())) + : WHash() + , WKeyEqual() { + ROBIN_HOOD_TRACE(this) + } + + // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. + // This tremendously speeds up ctor & dtor of a map that never receives an element. The + // penalty is payed at the first insert, and not before. Lookup of this empty map works + // because everybody points to DummyInfoByte::b. parameter bucket_count is dictated by the + // standard, but we can ignore it. + explicit Table( + size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/, const Hash& h = Hash{}, + const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && noexcept(KeyEqual(equal))) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) + } + + template + Table(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, + const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) + insert(first, last); + } + + Table(std::initializer_list initlist, + size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{}, + const KeyEqual& equal = KeyEqual{}) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) + insert(initlist.begin(), initlist.end()); + } + + Table(Table&& o) noexcept + : WHash(std::move(static_cast(o))) + , WKeyEqual(std::move(static_cast(o))) + , DataPool(std::move(static_cast(o))) { + ROBIN_HOOD_TRACE(this) + if (o.mMask) { + mHashMultiplier = std::move(o.mHashMultiplier); + mKeyVals = std::move(o.mKeyVals); + mInfo = std::move(o.mInfo); + mNumElements = std::move(o.mNumElements); + mMask = std::move(o.mMask); + mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); + mInfoInc = std::move(o.mInfoInc); + mInfoHashShift = std::move(o.mInfoHashShift); + // set other's mask to 0 so its destructor won't do anything + o.init(); + } + } + + Table& operator=(Table&& o) noexcept { + ROBIN_HOOD_TRACE(this) + if (&o != this) { + if (o.mMask) { + // only move stuff if the other map actually has some data + destroy(); + mHashMultiplier = std::move(o.mHashMultiplier); + mKeyVals = std::move(o.mKeyVals); + mInfo = std::move(o.mInfo); + mNumElements = std::move(o.mNumElements); + mMask = std::move(o.mMask); + mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); + mInfoInc = std::move(o.mInfoInc); + mInfoHashShift = std::move(o.mInfoHashShift); + WHash::operator=(std::move(static_cast(o))); + WKeyEqual::operator=(std::move(static_cast(o))); + DataPool::operator=(std::move(static_cast(o))); + + o.init(); + + } + else { + // nothing in the other map => just clear us. + clear(); + } + } + return *this; + } + + Table(const Table& o) + : WHash(static_cast(o)) + , WKeyEqual(static_cast(o)) + , DataPool(static_cast(o)) { + ROBIN_HOOD_TRACE(this) + if (!o.empty()) { + // not empty: create an exact copy. it is also possible to just iterate through all + // elements and insert them, but copying is probably faster. + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); + auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); + + ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" + << numElementsWithBuffer << ")") + mHashMultiplier = o.mHashMultiplier; + mKeyVals = static_cast( + detail::assertNotNull(std::malloc(numBytesTotal))); + // no need for calloc because clonData does memcpy + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + mNumElements = o.mNumElements; + mMask = o.mMask; + mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; + mInfoInc = o.mInfoInc; + mInfoHashShift = o.mInfoHashShift; + cloneData(o); + } + } + + // Creates a copy of the given map. Copy constructor of each entry is used. + // Not sure why clang-tidy thinks this doesn't handle self assignment, it does + // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) + Table& operator=(Table const& o) { + ROBIN_HOOD_TRACE(this) + if (&o == this) { + // prevent assigning of itself + return *this; + } + + // we keep using the old allocator and not assign the new one, because we want to keep + // the memory available. when it is the same size. + if (o.empty()) { + if (0 == mMask) { + // nothing to do, we are empty too + return *this; + } + + // not empty: destroy what we have there + // clear also resets mInfo to 0, that's sometimes not necessary. + destroy(); + init(); + WHash::operator=(static_cast(o)); + WKeyEqual::operator=(static_cast(o)); + DataPool::operator=(static_cast(o)); + + return *this; + } + + // clean up old stuff + Destroyer::value>{}.nodes(*this); + + if (mMask != o.mMask) { + // no luck: we don't have the same array size allocated, so we need to realloc. + if (0 != mMask) { + // only deallocate if we actually have data! + ROBIN_HOOD_LOG("std::free") + std::free(mKeyVals); + } + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); + auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); + ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" + << numElementsWithBuffer << ")") + mKeyVals = static_cast( + detail::assertNotNull(std::malloc(numBytesTotal))); + + // no need for calloc here because cloneData performs a memcpy. + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + // sentinel is set in cloneData + } + WHash::operator=(static_cast(o)); + WKeyEqual::operator=(static_cast(o)); + DataPool::operator=(static_cast(o)); + mHashMultiplier = o.mHashMultiplier; + mNumElements = o.mNumElements; + mMask = o.mMask; + mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; + mInfoInc = o.mInfoInc; + mInfoHashShift = o.mInfoHashShift; + cloneData(o); + + return *this; + } + + // Swaps everything between the two maps. + void swap(Table& o) { + ROBIN_HOOD_TRACE(this) + using std::swap; + swap(o, *this); + } + + // Clears all data, without resizing. + void clear() { + ROBIN_HOOD_TRACE(this) + if (empty()) { + // don't do anything! also important because we don't want to write to + // DummyInfoByte::b, even though we would just write 0 to it. + return; + } + + Destroyer::value>{}.nodes(*this); + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); + // clear everything, then set the sentinel again + uint8_t const z = 0; + std::fill(mInfo, mInfo + calcNumBytesInfo(numElementsWithBuffer), z); + mInfo[numElementsWithBuffer] = 1; + + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + // Destroys the map and all it's contents. + ~Table() { + ROBIN_HOOD_TRACE(this) + destroy(); + } + + // Checks if both tables contain the same entries. Order is irrelevant. + bool operator==(const Table& other) const { + ROBIN_HOOD_TRACE(this) + if (other.size() != size()) { + return false; + } + for (auto const& otherEntry : other) { + if (!has(otherEntry)) { + return false; + } + } + + return true; + } + + bool operator!=(const Table& other) const { + ROBIN_HOOD_TRACE(this) + return !operator==(other); + } + + template + typename std::enable_if::value, Q&>::type operator[](const key_type& key) { + ROBIN_HOOD_TRACE(this) + auto idxAndState = insertKeyPrepareEmptySpot(key); + switch (idxAndState.second) { + case InsertionState::key_found: + break; + + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) + Node(*this, std::piecewise_construct, std::forward_as_tuple(key), + std::forward_as_tuple()); + break; + + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, + std::forward_as_tuple(key), std::forward_as_tuple()); + break; + + case InsertionState::overflow_error: + throwOverflowError(); + } + + return mKeyVals[idxAndState.first].getSecond(); + } + + template + typename std::enable_if::value, Q&>::type operator[](key_type&& key) { + ROBIN_HOOD_TRACE(this) + auto idxAndState = insertKeyPrepareEmptySpot(key); + switch (idxAndState.second) { + case InsertionState::key_found: + break; + + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) + Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), + std::forward_as_tuple()); + break; + + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = + Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), + std::forward_as_tuple()); + break; + + case InsertionState::overflow_error: + throwOverflowError(); + } + + return mKeyVals[idxAndState.first].getSecond(); + } + + template + void insert(Iter first, Iter last) { + for (; first != last; ++first) { + // value_type ctor needed because this might be called with std::pair's + insert(value_type(*first)); + } + } + + void insert(std::initializer_list ilist) { + for (auto&& vt : ilist) { + insert(std::move(vt)); + } + } + + template + std::pair emplace(Args&&... args) { + ROBIN_HOOD_TRACE(this) + Node n { + *this, std::forward(args)... + }; + auto idxAndState = insertKeyPrepareEmptySpot(getFirstConst(n)); + switch (idxAndState.second) { + case InsertionState::key_found: + n.destroy(*this); + break; + + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) Node(*this, std::move(n)); + break; + + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = std::move(n); + break; + + case InsertionState::overflow_error: + n.destroy(*this); + throwOverflowError(); + break; + } + + return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), + InsertionState::key_found != idxAndState.second); + } + + template + iterator emplace_hint(const_iterator position, Args&&... args) { + (void)position; + return emplace(std::forward(args)...).first; + } + + template + std::pair try_emplace(const key_type& key, Args&&... args) { + return try_emplace_impl(key, std::forward(args)...); + } + + template + std::pair try_emplace(key_type&& key, Args&&... args) { + return try_emplace_impl(std::move(key), std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, const key_type& key, Args&&... args) { + (void)hint; + return try_emplace_impl(key, std::forward(args)...).first; + } + + template + iterator try_emplace(const_iterator hint, key_type&& key, Args&&... args) { + (void)hint; + return try_emplace_impl(std::move(key), std::forward(args)...).first; + } + + template + std::pair insert_or_assign(const key_type& key, Mapped&& obj) { + return insertOrAssignImpl(key, std::forward(obj)); + } + + template + std::pair insert_or_assign(key_type&& key, Mapped&& obj) { + return insertOrAssignImpl(std::move(key), std::forward(obj)); + } + + template + iterator insert_or_assign(const_iterator hint, const key_type& key, Mapped&& obj) { + (void)hint; + return insertOrAssignImpl(key, std::forward(obj)).first; + } + + template + iterator insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) { + (void)hint; + return insertOrAssignImpl(std::move(key), std::forward(obj)).first; + } + + std::pair insert(const value_type& keyval) { + ROBIN_HOOD_TRACE(this) + return emplace(keyval); + } + + iterator insert(const_iterator hint, const value_type& keyval) { + (void)hint; + return emplace(keyval).first; + } + + std::pair insert(value_type&& keyval) { + return emplace(std::move(keyval)); + } + + iterator insert(const_iterator hint, value_type&& keyval) { + (void)hint; + return emplace(std::move(keyval)).first; + } + + // Returns 1 if key is found, 0 otherwise. + size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { + return 1; + } + return 0; + } + + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::type count(const OtherKey& key) const { + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { + return 1; + } + return 0; + } + + bool contains(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + return 1U == count(key); + } + + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::type contains(const OtherKey& key) const { + return 1U == count(key); + } + + // Returns a reference to the value found for key. + // Throws std::out_of_range if element cannot be found + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::value, Q&>::type at(key_type const& key) { + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { + doThrow("key not found"); + } + return kv->getSecond(); + } + + // Returns a reference to the value found for key. + // Throws std::out_of_range if element cannot be found + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::value, Q const&>::type at(key_type const& key) const { + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { + doThrow("key not found"); + } + return kv->getSecond(); + } + + const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return const_iterator{ mKeyVals + idx, mInfo + idx }; + } + + template + const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return const_iterator{ mKeyVals + idx, mInfo + idx }; + } + + template + typename std::enable_if::type // NOLINT(modernize-use-nodiscard) + find(const OtherKey& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return const_iterator{ mKeyVals + idx, mInfo + idx }; + } + + iterator find(const key_type& key) { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return iterator{ mKeyVals + idx, mInfo + idx }; + } + + template + iterator find(const OtherKey& key, is_transparent_tag /*unused*/) { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return iterator{ mKeyVals + idx, mInfo + idx }; + } + + template + typename std::enable_if::type find(const OtherKey& key) { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return iterator{ mKeyVals + idx, mInfo + idx }; + } + + iterator begin() { + ROBIN_HOOD_TRACE(this) + if (empty()) { + return end(); + } + return iterator(mKeyVals, mInfo, fast_forward_tag{}); + } + const_iterator begin() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return cbegin(); + } + const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + if (empty()) { + return cend(); + } + return const_iterator(mKeyVals, mInfo, fast_forward_tag{}); + } + + iterator end() { + ROBIN_HOOD_TRACE(this) + // no need to supply valid info pointer: end() must not be dereferenced, and only node + // pointer is compared. + return iterator{ reinterpret_cast_no_cast_align_warning(mInfo), nullptr }; + } + const_iterator end() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return cend(); + } + const_iterator cend() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return const_iterator{ reinterpret_cast_no_cast_align_warning(mInfo), nullptr }; + } + + iterator erase(const_iterator pos) { + ROBIN_HOOD_TRACE(this) + // its safe to perform const cast here + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + return erase(iterator{ const_cast(pos.mKeyVals), const_cast(pos.mInfo) }); + } + + // Erases element at pos, returns iterator to the next element. + iterator erase(iterator pos) { + ROBIN_HOOD_TRACE(this) + // we assume that pos always points to a valid entry, and not end(). + auto const idx = static_cast(pos.mKeyVals - mKeyVals); + + shiftDown(idx); + --mNumElements; + + if (*pos.mInfo) { + // we've backward shifted, return this again + return pos; + } + + // no backward shift, return next element + return ++pos; + } + + size_t erase(const key_type& key) { + ROBIN_HOOD_TRACE(this) + size_t idx {}; + InfoType info{}; + keyToIdx(key, &idx, &info); + + // check while info matches with the source idx + do { + if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + shiftDown(idx); + --mNumElements; + return 1; + } + next(&info, &idx); + } while (info <= mInfo[idx]); + + // nothing found to delete + return 0; + } + + // reserves space for the specified number of elements. Makes sure the old data fits. + // exactly the same as reserve(c). + void rehash(size_t c) { + // forces a reserve + reserve(c, true); + } + + // reserves space for the specified number of elements. Makes sure the old data fits. + // Exactly the same as rehash(c). Use rehash(0) to shrink to fit. + void reserve(size_t c) { + // reserve, but don't force rehash + reserve(c, false); + } + + // If possible reallocates the map to a smaller one. This frees the underlying table. + // Does not do anything if load_factor is too large for decreasing the table's size. + void compact() { + ROBIN_HOOD_TRACE(this) + auto newSize = InitialNumElements; + while (calcMaxNumElementsAllowed(newSize) < mNumElements && newSize != 0) { + newSize *= 2; + } + if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { + throwOverflowError(); + } + + ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") + + // only actually do anything when the new size is bigger than the old one. This prevents to + // continuously allocate for each reserve() call. + if (newSize < mMask + 1) { + rehashPowerOfTwo(newSize, true); + } + } + + size_type size() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return mNumElements; + } + + size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return static_cast(-1); + } + + ROBIN_HOOD(NODISCARD) bool empty() const noexcept { + ROBIN_HOOD_TRACE(this) + return 0 == mNumElements; + } + + float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return MaxLoadFactor100 / 100.0F; + } + + // Average number of elements per bucket. Since we allow only 1 per bucket + float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return static_cast(size()) / static_cast(mMask + 1); + } + + ROBIN_HOOD(NODISCARD) size_t mask() const noexcept { + ROBIN_HOOD_TRACE(this) + return mMask; + } + + ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept { + if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits::max)() / 100)) { + return maxElements * MaxLoadFactor100 / 100; + } + + // we might be a bit inprecise, but since maxElements is quite large that doesn't matter + return (maxElements / 100) * MaxLoadFactor100; + } + + ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const noexcept { + // we add a uint64_t, which houses the sentinel (first byte) and padding so we can load + // 64bit types. + return numElements + sizeof(uint64_t); + } + + ROBIN_HOOD(NODISCARD) + size_t calcNumElementsWithBuffer(size_t numElements) const noexcept { + auto maxNumElementsAllowed = calcMaxNumElementsAllowed(numElements); + return numElements + (std::min)(maxNumElementsAllowed, (static_cast(0xFF))); + } + + // calculation only allowed for 2^n values + ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const { +#if ROBIN_HOOD(BITNESS) == 64 + return numElements * sizeof(Node) + calcNumBytesInfo(numElements); +#else + // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows. + auto const ne = static_cast(numElements); + auto const s = static_cast(sizeof(Node)); + auto const infos = static_cast(calcNumBytesInfo(numElements)); + + auto const total64 = ne * s + infos; + auto const total = static_cast(total64); + + if (ROBIN_HOOD_UNLIKELY(static_cast(total) != total64)) { + throwOverflowError(); + } + return total; +#endif + } + + private: + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, bool>::type has(const value_type& e) const { + ROBIN_HOOD_TRACE(this) + auto it = find(e.first); + return it != end() && it->second == e.second; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, bool>::type has(const value_type& e) const { + ROBIN_HOOD_TRACE(this) + return find(e) != end(); + } + + void reserve(size_t c, bool forceRehash) { + ROBIN_HOOD_TRACE(this) + auto const minElementsAllowed = (std::max)(c, mNumElements); + auto newSize = InitialNumElements; + while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) { + newSize *= 2; + } + if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { + throwOverflowError(); + } + + ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") + + // only actually do anything when the new size is bigger than the old one. This prevents to + // continuously allocate for each reserve() call. + if (forceRehash || newSize > mMask + 1) { + rehashPowerOfTwo(newSize, false); + } + } + + // reserves space for at least the specified number of elements. + // only works if numBuckets if power of two + // True on success, false otherwise + void rehashPowerOfTwo(size_t numBuckets, bool forceFree) { + ROBIN_HOOD_TRACE(this) + + Node* const oldKeyVals = mKeyVals; + uint8_t const* const oldInfo = mInfo; + + const size_t oldMaxElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); + + // resize operation: move stuff + initData(numBuckets); + if (oldMaxElementsWithBuffer > 1) { + for (size_t i = 0; i < oldMaxElementsWithBuffer; ++i) { + if (oldInfo[i] != 0) { + // might throw an exception, which is really bad since we are in the middle of + // moving stuff. + insert_move(std::move(oldKeyVals[i])); + // destroy the node but DON'T destroy the data. + oldKeyVals[i].~Node(); + } + } + + // this check is not necessary as it's guarded by the previous if, but it helps + // silence g++'s overeager "attempt to free a non-heap object 'map' + // [-Werror=free-nonheap-object]" warning. + if (oldKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { + // don't destroy old data: put it into the pool instead + if (forceFree) { + std::free(oldKeyVals); + } + else { + DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer)); + } + } + } + } + + ROBIN_HOOD(NOINLINE) void throwOverflowError() const { +#if ROBIN_HOOD(HAS_EXCEPTIONS) + throw std::overflow_error("robin_hood::map overflow"); +#else + abort(); +#endif + } + + template + std::pair try_emplace_impl(OtherKey&& key, Args&&... args) { + ROBIN_HOOD_TRACE(this) + auto idxAndState = insertKeyPrepareEmptySpot(key); + switch (idxAndState.second) { + case InsertionState::key_found: + break; + + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) Node( + *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); + break; + + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); + break; + + case InsertionState::overflow_error: + throwOverflowError(); + break; + } + + return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), + InsertionState::key_found != idxAndState.second); + } + + template + std::pair insertOrAssignImpl(OtherKey&& key, Mapped&& obj) { + ROBIN_HOOD_TRACE(this) + auto idxAndState = insertKeyPrepareEmptySpot(key); + switch (idxAndState.second) { + case InsertionState::key_found: + mKeyVals[idxAndState.first].getSecond() = std::forward(obj); + break; + + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) Node( + *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(obj))); + break; + + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(obj))); + break; + + case InsertionState::overflow_error: + throwOverflowError(); + break; + } + + return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), + InsertionState::key_found != idxAndState.second); + } + + void initData(size_t max_elements) { + mNumElements = 0; + mMask = max_elements - 1; + mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements); + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements); + + // malloc & zero mInfo. Faster than calloc everything. + auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); + ROBIN_HOOD_LOG("std::calloc " << numBytesTotal << " = calcNumBytesTotal(" + << numElementsWithBuffer << ")") + mKeyVals = reinterpret_cast( + detail::assertNotNull(std::malloc(numBytesTotal))); + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + std::memset(mInfo, 0, numBytesTotal - numElementsWithBuffer * sizeof(Node)); + + // set sentinel + mInfo[numElementsWithBuffer] = 1; + + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + enum class InsertionState { overflow_error, key_found, new_node, overwrite_node }; + + // Finds key, and if not already present prepares a spot where to pot the key & value. + // This potentially shifts nodes out of the way, updates mInfo and number of inserted + // elements, so the only operation left to do is create/assign a new node at that spot. + template + std::pair insertKeyPrepareEmptySpot(OtherKey&& key) { + for (int i = 0; i < 256; ++i) { + size_t idx{}; + InfoType info{}; + keyToIdx(key, &idx, &info); + nextWhileLess(&info, &idx); + + // while we potentially have a match + while (info == mInfo[idx]) { + if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + // key already exists, do NOT insert. + // see http://en.cppreference.com/w/cpp/container/unordered_map/insert + return std::make_pair(idx, InsertionState::key_found); + } + next(&info, &idx); + } + + // unlikely that this evaluates to true + if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { + if (!increase_size()) { + return std::make_pair(size_t(0), InsertionState::overflow_error); + } + continue; + } + + // key not found, so we are now exactly where we want to insert it. + auto const insertion_idx = idx; + auto const insertion_info = info; + if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + + // find an empty spot + while (0 != mInfo[idx]) { + next(&info, &idx); + } + + if (idx != insertion_idx) { + shiftUp(idx, insertion_idx); + } + // put at empty spot + mInfo[insertion_idx] = static_cast(insertion_info); + ++mNumElements; + return std::make_pair(insertion_idx, idx == insertion_idx + ? InsertionState::new_node + : InsertionState::overwrite_node); + } + + // enough attempts failed, so finally give up. + return std::make_pair(size_t(0), InsertionState::overflow_error); + } + + bool try_increase_info() { + ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements + << ", maxNumElementsAllowed=" + << calcMaxNumElementsAllowed(mMask + 1)) + if (mInfoInc <= 2) { + // need to be > 2 so that shift works (otherwise undefined behavior!) + return false; + } + // we got space left, try to make info smaller + mInfoInc = static_cast(mInfoInc >> 1U); + + // remove one bit of the hash, leaving more space for the distance info. + // This is extremely fast because we can operate on 8 bytes at once. + ++mInfoHashShift; + auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); + + for (size_t i = 0; i < numElementsWithBuffer; i += 8) { + auto val = unaligned_load(mInfo + i); + val = (val >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f); + std::memcpy(mInfo + i, &val, sizeof(val)); + } + // update sentinel, which might have been cleared out! + mInfo[numElementsWithBuffer] = 1; + + mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); + return true; + } + + // True if resize was possible, false otherwise + bool increase_size() { + // nothing allocated yet? just allocate InitialNumElements + if (0 == mMask) { + initData(InitialNumElements); + return true; + } + + auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); + if (mNumElements < maxNumElementsAllowed && try_increase_info()) { + return true; + } + + ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed=" + << maxNumElementsAllowed << ", load=" + << (static_cast(mNumElements) * 100.0 / + (static_cast(mMask) + 1))) + + if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) { + // we have to resize, even though there would still be plenty of space left! + // Try to rehash instead. Delete freed memory so we don't steadyily increase mem in case + // we have to rehash a few times + nextHashMultiplier(); + rehashPowerOfTwo(mMask + 1, true); + } + else { + // we've reached the capacity of the map, so the hash seems to work nice. Keep using it. + rehashPowerOfTwo((mMask + 1) * 2, false); + } + return true; + } + + void nextHashMultiplier() { + // adding an *even* number, so that the multiplier will always stay odd. This is necessary + // so that the hash stays a mixing function (and thus doesn't have any information loss). + mHashMultiplier += UINT64_C(0xc4ceb9fe1a85ec54); + } + + void destroy() { + if (0 == mMask) { + // don't deallocate! + return; + } + + Destroyer::value>{} + .nodesDoNotDeallocate(*this); + + // This protection against not deleting mMask shouldn't be needed as it's sufficiently + // protected with the 0==mMask check, but I have this anyways because g++ 7 otherwise + // reports a compile error: attempt to free a non-heap object 'fm' + // [-Werror=free-nonheap-object] + if (mKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { + ROBIN_HOOD_LOG("std::free") + std::free(mKeyVals); + } + } + + void init() noexcept { + mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); + mInfo = reinterpret_cast(&mMask); + mNumElements = 0; + mMask = 0; + mMaxNumElementsAllowed = 0; + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + // members are sorted so no padding occurs + uint64_t mHashMultiplier = UINT64_C(0xc4ceb9fe1a85ec53); // 8 byte 8 + Node* mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); // 8 byte 16 + uint8_t* mInfo = reinterpret_cast(&mMask); // 8 byte 24 + size_t mNumElements = 0; // 8 byte 32 + size_t mMask = 0; // 8 byte 40 + size_t mMaxNumElementsAllowed = 0; // 8 byte 48 + InfoType mInfoInc = InitialInfoInc; // 4 byte 52 + InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 56 + // 16 byte 56 if NodeAllocator + }; + + } // namespace detail + + // map + + template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> + using unordered_flat_map = detail::Table; + + template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> + using unordered_node_map = detail::Table; + + template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> + using unordered_map = + detail::Table) <= sizeof(size_t) * 6 && + std::is_nothrow_move_constructible>::value && + std::is_nothrow_move_assignable>::value, + MaxLoadFactor100, Key, T, Hash, KeyEqual>; + + // set + + template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> + using unordered_flat_set = detail::Table; + + template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> + using unordered_node_set = detail::Table; + + template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> + using unordered_set = detail::Table::value && + std::is_nothrow_move_assignable::value, + MaxLoadFactor100, Key, void, Hash, KeyEqual>; + +} // namespace robin_hood + +#endif From ef52fae6d845fffac94af07e46a46a8e52a71d21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Wed, 1 Jun 2022 15:39:20 +0200 Subject: [PATCH 109/174] Add documentation to custom set implementation --- Commons/Ipc/ProfilerIpc.cs | 2 +- Profiler/CProfilerCallback.cpp | 7 +- Profiler/CProfilerCallback.h | 3 +- Profiler/CProfilerWorker.h | 2 +- Profiler/utils/Ipc.cpp | 6 - Profiler/utils/Ipc.h | 2 +- Profiler/utils/MethodEnter.h | 1 - Profiler/utils/atomic_queue/atomic_queue.h | 1186 ++++---- .../utils/functionID_set/functionId_set.h | 49 +- Profiler/utils/robin_hood/robin_hood.h | 2551 ----------------- Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj | 4 +- .../Profiler_Cpp_Test.vcxproj.filters | 3 + Profiler_Cpp_Test/tests/FunctionIDSetTest.cpp | 35 + 13 files changed, 681 insertions(+), 3170 deletions(-) delete mode 100644 Profiler/utils/robin_hood/robin_hood.h create mode 100644 Profiler_Cpp_Test/tests/FunctionIDSetTest.cpp diff --git a/Commons/Ipc/ProfilerIpc.cs b/Commons/Ipc/ProfilerIpc.cs index 62f1a9e4..70ea539f 100644 --- a/Commons/Ipc/ProfilerIpc.cs +++ b/Commons/Ipc/ProfilerIpc.cs @@ -34,7 +34,7 @@ protected virtual string HandleRequest(string message) logger.Info("Received request get_testname. Response {testName}", testName); return testName; default: - logger.Info("Received request: {request}", message); + logger.Info("Received request: {request}.", message); return string.Empty; } } diff --git a/Profiler/CProfilerCallback.cpp b/Profiler/CProfilerCallback.cpp index f3409590..5f6ad91a 100644 --- a/Profiler/CProfilerCallback.cpp +++ b/Profiler/CProfilerCallback.cpp @@ -403,11 +403,10 @@ HRESULT CProfilerCallback::JITInliningImplementation(FunctionID callerId, Functi // Save information about inlined method (if not already seen) // TODO (MP) Better late call eval here as well. - if (inlinedMethodIds.find(calleeId) == inlinedMethodIds.end()) { + if (!inlinedMethodIds.contains(calleeId)) { EnterCriticalSection(&callbackSynchronization); - if (inlinedMethodIds.insert(calleeId).second == true) { - recordFunctionInfo(&inlinedMethods, calleeId); - } + inlinedMethodIds.insert(calleeId); + recordFunctionInfo(&inlinedMethods, calleeId); LeaveCriticalSection(&callbackSynchronization); } } diff --git a/Profiler/CProfilerCallback.h b/Profiler/CProfilerCallback.h index 37b0e1ad..0e6d8bce 100644 --- a/Profiler/CProfilerCallback.h +++ b/Profiler/CProfilerCallback.h @@ -9,7 +9,6 @@ #include #include #include -#include #include #include "CProfilerWorker.h" #include "UploadDaemon.h" @@ -85,7 +84,7 @@ class CProfilerCallback : public CProfilerCallbackBase { * Keeps track of inlined methods. * We use the set to efficiently determine if we already noticed an inlined method. */ - robin_hood::unordered_flat_set inlinedMethodIds; + functionID_set inlinedMethodIds; /** * Keeps track of inlined methods. diff --git a/Profiler/CProfilerWorker.h b/Profiler/CProfilerWorker.h index 461a4ab6..bb4d91a0 100644 --- a/Profiler/CProfilerWorker.h +++ b/Profiler/CProfilerWorker.h @@ -13,7 +13,7 @@ class CProfilerWorker CProfilerWorker(Config*, TraceLog*, functionID_set*, CRITICAL_SECTION*); virtual ~CProfilerWorker(); private: - + // Variables std::thread* workerThread = NULL; TraceLog* traceLog = NULL; diff --git a/Profiler/utils/Ipc.cpp b/Profiler/utils/Ipc.cpp index b42a966a..afbb88b9 100644 --- a/Profiler/utils/Ipc.cpp +++ b/Profiler/utils/Ipc.cpp @@ -29,7 +29,6 @@ Ipc::~Ipc() this->handlerThread->join(); } - this->sendDisconnect(); if (this->zmqRequestSocket != NULL) { zmq_close(this->zmqRequestSocket); } @@ -92,11 +91,6 @@ std::string Ipc::getCurrentTestName() return this->request("get_testname"); } -void Ipc::sendDisconnect() { - std::string message = "profiler_disconnected"; - zmq_send(this->zmqRequestSocket, message.c_str(), message.length(), 0); -} - std::string Ipc::request(std::string message) { if (!initRequestSocket()) { diff --git a/Profiler/utils/Ipc.h b/Profiler/utils/Ipc.h index 4a902e03..1c776133 100644 --- a/Profiler/utils/Ipc.h +++ b/Profiler/utils/Ipc.h @@ -28,4 +28,4 @@ class Ipc bool initRequestSocket(); void logError(std::string message); std::string request(std::string message); -}; \ No newline at end of file +}; diff --git a/Profiler/utils/MethodEnter.h b/Profiler/utils/MethodEnter.h index fe9335d0..6953e8ee 100644 --- a/Profiler/utils/MethodEnter.h +++ b/Profiler/utils/MethodEnter.h @@ -7,7 +7,6 @@ #include #include - FunctionID constexpr NIL = static_cast(-1); using Queue = atomic_queue::AtomicQueueB, NIL>; diff --git a/Profiler/utils/atomic_queue/atomic_queue.h b/Profiler/utils/atomic_queue/atomic_queue.h index 2c7a67e9..84281c9b 100644 --- a/Profiler/utils/atomic_queue/atomic_queue.h +++ b/Profiler/utils/atomic_queue/atomic_queue.h @@ -17,601 +17,597 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace atomic_queue { - -using std::uint32_t; -using std::uint64_t; -using std::uint8_t; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace details { - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -template struct GetCacheLineIndexBits { static int constexpr value = 0; }; -template<> struct GetCacheLineIndexBits<256> { static int constexpr value = 8; }; -template<> struct GetCacheLineIndexBits<128> { static int constexpr value = 7; }; -template<> struct GetCacheLineIndexBits< 64> { static int constexpr value = 6; }; -template<> struct GetCacheLineIndexBits< 32> { static int constexpr value = 5; }; -template<> struct GetCacheLineIndexBits< 16> { static int constexpr value = 4; }; -template<> struct GetCacheLineIndexBits< 8> { static int constexpr value = 3; }; -template<> struct GetCacheLineIndexBits< 4> { static int constexpr value = 2; }; -template<> struct GetCacheLineIndexBits< 2> { static int constexpr value = 1; }; - -template -struct GetIndexShuffleBits { - static int constexpr bits = GetCacheLineIndexBits::value; - static unsigned constexpr min_size = 1u << (bits * 2); - static int constexpr value = array_size < min_size ? 0 : bits; -}; - -template -struct GetIndexShuffleBits { - static int constexpr value = 0; -}; - -// Multiple writers/readers contend on the same cache line when storing/loading elements at -// subsequent indexes, aka false sharing. For power of 2 ring buffer size it is possible to re-map -// the index in such a way that each subsequent element resides on another cache line, which -// minimizes contention. This is done by swapping the lowest order N bits (which are the index of -// the element within the cache line) with the next N bits (which are the index of the cache line) -// of the element index. -template -constexpr unsigned remap_index_with_mix(unsigned index, unsigned mix) { - return index ^ mix ^ (mix << BITS); -} - -template -constexpr unsigned remap_index(unsigned index) noexcept { - return remap_index_with_mix(index, (index ^ (index >> BITS)) & ((1u << BITS) - 1)); -} - -template<> -constexpr unsigned remap_index<0>(unsigned index) noexcept { - return index; -} - -template -constexpr T& map(T* elements, unsigned index) noexcept { - return elements[remap_index(index)]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// Implement a "bit-twiddling hack" for finding the next power of 2 in either 32 bits or 64 bits -// in C++11 compatible constexpr functions. The library no longer maintains C++11 compatibility. - -// "Runtime" version for 32 bits -// --a; -// a |= a >> 1; -// a |= a >> 2; -// a |= a >> 4; -// a |= a >> 8; -// a |= a >> 16; -// ++a; - -template -constexpr T decrement(T x) noexcept { - return x - 1; -} - -template -constexpr T increment(T x) noexcept { - return x + 1; -} - -template -constexpr T or_equal(T x, unsigned u) noexcept { - return x | x >> u; -} - -template -constexpr T or_equal(T x, unsigned u, Args... rest) noexcept { - return or_equal(or_equal(x, u), rest...); -} - -constexpr uint32_t round_up_to_power_of_2(uint32_t a) noexcept { - return increment(or_equal(decrement(a), 1, 2, 4, 8, 16)); -} - -constexpr uint64_t round_up_to_power_of_2(uint64_t a) noexcept { - return increment(or_equal(decrement(a), 1, 2, 4, 8, 16, 32)); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -} // namespace details - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -template -class AtomicQueueCommon { -protected: - // Put these on different cache lines to avoid false sharing between readers and writers. - alignas(CACHE_LINE_SIZE) std::atomic head_ = {}; - alignas(CACHE_LINE_SIZE) std::atomic tail_ = {}; - - // The special member functions are not thread-safe. - - AtomicQueueCommon() noexcept = default; - - AtomicQueueCommon(AtomicQueueCommon const& b) noexcept - : head_(b.head_.load(X)) - , tail_(b.tail_.load(X)) {} - - AtomicQueueCommon& operator=(AtomicQueueCommon const& b) noexcept { - head_.store(b.head_.load(X), X); - tail_.store(b.tail_.load(X), X); - return *this; - } - - void swap(AtomicQueueCommon& b) noexcept { - unsigned h = head_.load(X); - unsigned t = tail_.load(X); - head_.store(b.head_.load(X), X); - tail_.store(b.tail_.load(X), X); - b.head_.store(h, X); - b.tail_.store(t, X); - } - - template - static T do_pop_atomic(std::atomic& q_element) noexcept { - if(Derived::spsc_) { - for(;;) { - T element = q_element.load(X); - if(ATOMIC_QUEUE_LIKELY(element != NIL)) { - q_element.store(NIL, R); - return element; - } - if(Derived::maximize_throughput_) - spin_loop_pause(); - } - } - else { - for(;;) { - T element = q_element.exchange(NIL, R); // (2) The store to wait for. - if(ATOMIC_QUEUE_LIKELY(element != NIL)) - return element; - // Do speculative loads while busy-waiting to avoid broadcasting RFO messages. - do - spin_loop_pause(); - while(Derived::maximize_throughput_ && q_element.load(X) == NIL); - } - } - } - - template - static void do_push_atomic(T element, std::atomic& q_element) noexcept { - assert(element != NIL); - if(Derived::spsc_) { - while(ATOMIC_QUEUE_UNLIKELY(q_element.load(X) != NIL)) - if(Derived::maximize_throughput_) - spin_loop_pause(); - q_element.store(element, R); - } - else { - for(T expected = NIL; ATOMIC_QUEUE_UNLIKELY(!q_element.compare_exchange_strong(expected, element, R, X)); expected = NIL) { - do - spin_loop_pause(); // (1) Wait for store (2) to complete. - while(Derived::maximize_throughput_ && q_element.load(X) != NIL); - } - } - } - - enum State : unsigned char { EMPTY, STORING, STORED, LOADING }; - - template - static T do_pop_any(std::atomic& state, T& q_element) noexcept { - if(Derived::spsc_) { - while(ATOMIC_QUEUE_UNLIKELY(state.load(A) != STORED)) - if(Derived::maximize_throughput_) - spin_loop_pause(); - T element{std::move(q_element)}; - state.store(EMPTY, R); - return element; - } - else { - for(;;) { - unsigned char expected = STORED; - if(ATOMIC_QUEUE_LIKELY(state.compare_exchange_strong(expected, LOADING, A, X))) { - T element{std::move(q_element)}; - state.store(EMPTY, R); - return element; - } - // Do speculative loads while busy-waiting to avoid broadcasting RFO messages. - do - spin_loop_pause(); - while(Derived::maximize_throughput_ && state.load(X) != STORED); - } - } - } - - template - static void do_push_any(U&& element, std::atomic& state, T& q_element) noexcept { - if(Derived::spsc_) { - while(ATOMIC_QUEUE_UNLIKELY(state.load(A) != EMPTY)) - if(Derived::maximize_throughput_) - spin_loop_pause(); - q_element = std::forward(element); - state.store(STORED, R); - } - else { - for(;;) { - unsigned char expected = EMPTY; - if(ATOMIC_QUEUE_LIKELY(state.compare_exchange_strong(expected, STORING, A, X))) { - q_element = std::forward(element); - state.store(STORED, R); - return; - } - // Do speculative loads while busy-waiting to avoid broadcasting RFO messages. - do - spin_loop_pause(); - while(Derived::maximize_throughput_ && state.load(X) != EMPTY); - } - } - } - -public: - template - bool try_push(T&& element) noexcept { - auto head = head_.load(X); - if(Derived::spsc_) { - if(static_cast(head - tail_.load(X)) >= static_cast(static_cast(*this).size_)) - return false; - head_.store(head + 1, X); - } - else { - do { - if(static_cast(head - tail_.load(X)) >= static_cast(static_cast(*this).size_)) - return false; - } while(ATOMIC_QUEUE_UNLIKELY(!head_.compare_exchange_strong(head, head + 1, A, X))); // This loop is not FIFO. - } - - static_cast(*this).do_push(std::forward(element), head); - return true; - } - - template - bool try_pop(T& element) noexcept { - auto tail = tail_.load(X); - if(Derived::spsc_) { - if(static_cast(head_.load(X) - tail) <= 0) - return false; - tail_.store(tail + 1, X); - } - else { - do { - if(static_cast(head_.load(X) - tail) <= 0) - return false; - } while(ATOMIC_QUEUE_UNLIKELY(!tail_.compare_exchange_strong(tail, tail + 1, A, X))); // This loop is not FIFO. - } - - element = static_cast(*this).do_pop(tail); - return true; - } - - template - void push(T&& element) noexcept { - unsigned head; - if(Derived::spsc_) { - head = head_.load(X); - head_.store(head + 1, X); - } - else { - constexpr auto memory_order = Derived::total_order_ ? std::memory_order_seq_cst : std::memory_order_acquire; - head = head_.fetch_add(1, memory_order); // FIFO and total order on Intel regardless, as of 2019. - } - static_cast(*this).do_push(std::forward(element), head); - } - - auto pop() noexcept { - unsigned tail; - if(Derived::spsc_) { - tail = tail_.load(X); - tail_.store(tail + 1, X); - } - else { - constexpr auto memory_order = Derived::total_order_ ? std::memory_order_seq_cst : std::memory_order_acquire; - tail = tail_.fetch_add(1, memory_order); // FIFO and total order on Intel regardless, as of 2019. - } - return static_cast(*this).do_pop(tail); - } - - bool was_empty() const noexcept { - return !was_size(); - } - - bool was_full() const noexcept { - return was_size() >= static_cast(static_cast(*this).size_); - } - - unsigned was_size() const noexcept { - // tail_ can be greater than head_ because of consumers doing pop, rather that try_pop, when the queue is empty. - return (std::max)(static_cast(head_.load(X) - tail_.load(X)), 0); - } - - unsigned capacity() const noexcept { - return static_cast(*this).size_; - } -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -template -class AtomicQueue : public AtomicQueueCommon> { - using Base = AtomicQueueCommon>; - friend Base; - - static constexpr unsigned size_ = MINIMIZE_CONTENTION ? details::round_up_to_power_of_2(SIZE) : SIZE; - static constexpr int SHUFFLE_BITS = details::GetIndexShuffleBits)>::value; - static constexpr bool total_order_ = TOTAL_ORDER; - static constexpr bool spsc_ = SPSC; - static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; - - alignas(CACHE_LINE_SIZE) std::atomic elements_[size_] = {}; // Empty elements are NIL. - - T do_pop(unsigned tail) noexcept { - std::atomic& q_element = details::map(elements_, tail % size_); - return Base::template do_pop_atomic(q_element); - } - - void do_push(T element, unsigned head) noexcept { - std::atomic& q_element = details::map(elements_, head % size_); - Base::template do_push_atomic(element, q_element); - } - -public: - using value_type = T; - - AtomicQueue() noexcept { - assert(std::atomic{NIL}.is_lock_free()); // This queue is for atomic elements only. AtomicQueue2 is for non-atomic ones. - if(T{} != NIL) - for(auto& element : elements_) - element.store(NIL, X); - } - - AtomicQueue(AtomicQueue const&) = delete; - AtomicQueue& operator=(AtomicQueue const&) = delete; -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -template -class AtomicQueue2 : public AtomicQueueCommon> { - using Base = AtomicQueueCommon>; - using State = typename Base::State; - friend Base; - - static constexpr unsigned size_ = MINIMIZE_CONTENTION ? details::round_up_to_power_of_2(SIZE) : SIZE; - static constexpr int SHUFFLE_BITS = details::GetIndexShuffleBits::value; - static constexpr bool total_order_ = TOTAL_ORDER; - static constexpr bool spsc_ = SPSC; - static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; - - alignas(CACHE_LINE_SIZE) std::atomic states_[size_] = {}; - alignas(CACHE_LINE_SIZE) T elements_[size_] = {}; - - T do_pop(unsigned tail) noexcept { - unsigned index = details::remap_index(tail % size_); - return Base::template do_pop_any(states_[index], elements_[index]); - } - - template - void do_push(U&& element, unsigned head) noexcept { - unsigned index = details::remap_index(head % size_); - Base::template do_push_any(std::forward(element), states_[index], elements_[index]); - } - -public: - using value_type = T; - - AtomicQueue2() noexcept = default; - AtomicQueue2(AtomicQueue2 const&) = delete; - AtomicQueue2& operator=(AtomicQueue2 const&) = delete; -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -template, T NIL = T{}, bool MAXIMIZE_THROUGHPUT = true, bool TOTAL_ORDER = false, bool SPSC = false> -class AtomicQueueB : public AtomicQueueCommon>, - private std::allocator_traits::template rebind_alloc> { - using Base = AtomicQueueCommon>; - friend Base; - - static constexpr bool total_order_ = TOTAL_ORDER; - static constexpr bool spsc_ = SPSC; - static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; - - using AllocatorElements = typename std::allocator_traits::template rebind_alloc>; - - static constexpr auto ELEMENTS_PER_CACHE_LINE = CACHE_LINE_SIZE / sizeof(std::atomic); - static_assert(ELEMENTS_PER_CACHE_LINE, "Unexpected ELEMENTS_PER_CACHE_LINE."); - - static constexpr auto SHUFFLE_BITS = details::GetCacheLineIndexBits::value; - static_assert(SHUFFLE_BITS, "Unexpected SHUFFLE_BITS."); - - // AtomicQueueCommon members are stored into by readers and writers. - // Allocate these immutable members on another cache line which never gets invalidated by stores. - alignas(CACHE_LINE_SIZE) unsigned size_; - std::atomic* elements_; - - T do_pop(unsigned tail) noexcept { - std::atomic& q_element = details::map(elements_, tail & (size_ - 1)); - return Base::template do_pop_atomic(q_element); - } - - void do_push(T element, unsigned head) noexcept { - std::atomic& q_element = details::map(elements_, head & (size_ - 1)); - Base::template do_push_atomic(element, q_element); - } - -public: - using value_type = T; - - // The special member functions are not thread-safe. - - AtomicQueueB(unsigned size) - : size_((std::max)(details::round_up_to_power_of_2(size), 1u << (SHUFFLE_BITS * 2))) - , elements_(AllocatorElements::allocate(size_)) { - assert(std::atomic{NIL}.is_lock_free()); // This queue is for atomic elements only. AtomicQueueB2 is for non-atomic ones. - for(auto p = elements_, q = elements_ + size_; p < q; ++p) - p->store(NIL, X); - } - - AtomicQueueB(AtomicQueueB&& b) noexcept - : Base(static_cast(b)) - , AllocatorElements(static_cast(b)) // TODO: This must be noexcept, static_assert that. - , size_(b.size_) - , elements_(b.elements_) { - b.size_ = 0; - b.elements_ = 0; - } - - AtomicQueueB& operator=(AtomicQueueB&& b) noexcept { - b.swap(*this); - return *this; - } - - ~AtomicQueueB() noexcept { - if(elements_) - AllocatorElements::deallocate(elements_, size_); // TODO: This must be noexcept, static_assert that. - } - - void swap(AtomicQueueB& b) noexcept { - using std::swap; - this->Base::swap(b); - swap(static_cast(*this), static_cast(b)); - swap(size_, b.size_); - swap(elements_, b.elements_); - } - - friend void swap(AtomicQueueB& a, AtomicQueueB& b) { - a.swap(b); - } -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -template, bool MAXIMIZE_THROUGHPUT = true, bool TOTAL_ORDER = false, bool SPSC = false> -class AtomicQueueB2 : public AtomicQueueCommon>, - private A, - private std::allocator_traits::template rebind_alloc> { - using Base = AtomicQueueCommon>; - using State = typename Base::State; - friend Base; - - static constexpr bool total_order_ = TOTAL_ORDER; - static constexpr bool spsc_ = SPSC; - static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; - - using AllocatorElements = A; - using AllocatorStates = typename std::allocator_traits::template rebind_alloc>; - - // AtomicQueueCommon members are stored into by readers and writers. - // Allocate these immutable members on another cache line which never gets invalidated by stores. - alignas(CACHE_LINE_SIZE) unsigned size_; - std::atomic* states_; - T* elements_; - - static constexpr auto STATES_PER_CACHE_LINE = CACHE_LINE_SIZE / sizeof(State); - static_assert(STATES_PER_CACHE_LINE, "Unexpected STATES_PER_CACHE_LINE."); - - static constexpr auto SHUFFLE_BITS = details::GetCacheLineIndexBits::value; - static_assert(SHUFFLE_BITS, "Unexpected SHUFFLE_BITS."); - - T do_pop(unsigned tail) noexcept { - unsigned index = details::remap_index(tail & (size_ - 1)); - return Base::template do_pop_any(states_[index], elements_[index]); - } - - template - void do_push(U&& element, unsigned head) noexcept { - unsigned index = details::remap_index(head & (size_ - 1)); - Base::template do_push_any(std::forward(element), states_[index], elements_[index]); - } - -public: - using value_type = T; - - // The special member functions are not thread-safe. - - AtomicQueueB2(unsigned size) - : size_((std::max)(details::round_up_to_power_of_2(size), 1u << (SHUFFLE_BITS * 2))) - , states_(AllocatorStates::allocate(size_)) - , elements_(AllocatorElements::allocate(size_)) { - for(auto p = states_, q = states_ + size_; p < q; ++p) - p->store(Base::EMPTY, X); - - AllocatorElements& ae = *this; - for(auto p = elements_, q = elements_ + size_; p < q; ++p) - std::allocator_traits::construct(ae, p); - } - - AtomicQueueB2(AtomicQueueB2&& b) noexcept - : Base(static_cast(b)) - , AllocatorElements(static_cast(b)) // TODO: This must be noexcept, static_assert that. - , AllocatorStates(static_cast(b)) // TODO: This must be noexcept, static_assert that. - , size_(b.size_) - , states_(b.states_) - , elements_(b.elements_) { - b.size_ = 0; - b.states_ = 0; - b.elements_ = 0; - } - - AtomicQueueB2& operator=(AtomicQueueB2&& b) noexcept { - b.swap(*this); - return *this; - } - - ~AtomicQueueB2() noexcept { - if(elements_) { - AllocatorElements& ae = *this; - for(auto p = elements_, q = elements_ + size_; p < q; ++p) - std::allocator_traits::destroy(ae, p); - AllocatorElements::deallocate(elements_, size_); // TODO: This must be noexcept, static_assert that. - AllocatorStates::deallocate(states_, size_); // TODO: This must be noexcept, static_assert that. - } - } - - void swap(AtomicQueueB2& b) noexcept { - using std::swap; - this->Base::swap(b); - swap(static_cast(*this), static_cast(b)); - swap(static_cast(*this), static_cast(b)); - swap(size_, b.size_); - swap(states_, b.states_); - swap(elements_, b.elements_); - } - - friend void swap(AtomicQueueB2& a, AtomicQueueB2& b) noexcept { - a.swap(b); - } -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -template -struct RetryDecorator : Queue { - using T = typename Queue::value_type; - - using Queue::Queue; - - void push(T element) noexcept { - while(!this->try_push(element)) - spin_loop_pause(); - } - - T pop() noexcept { - T element; - while(!this->try_pop(element)) - spin_loop_pause(); - return element; - } -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - + using std::uint32_t; + using std::uint64_t; + using std::uint8_t; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + namespace details { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + template struct GetCacheLineIndexBits { static int constexpr value = 0; }; + template<> struct GetCacheLineIndexBits<256> { static int constexpr value = 8; }; + template<> struct GetCacheLineIndexBits<128> { static int constexpr value = 7; }; + template<> struct GetCacheLineIndexBits< 64> { static int constexpr value = 6; }; + template<> struct GetCacheLineIndexBits< 32> { static int constexpr value = 5; }; + template<> struct GetCacheLineIndexBits< 16> { static int constexpr value = 4; }; + template<> struct GetCacheLineIndexBits< 8> { static int constexpr value = 3; }; + template<> struct GetCacheLineIndexBits< 4> { static int constexpr value = 2; }; + template<> struct GetCacheLineIndexBits< 2> { static int constexpr value = 1; }; + + template + struct GetIndexShuffleBits { + static int constexpr bits = GetCacheLineIndexBits::value; + static unsigned constexpr min_size = 1u << (bits * 2); + static int constexpr value = array_size < min_size ? 0 : bits; + }; + + template + struct GetIndexShuffleBits { + static int constexpr value = 0; + }; + + // Multiple writers/readers contend on the same cache line when storing/loading elements at + // subsequent indexes, aka false sharing. For power of 2 ring buffer size it is possible to re-map + // the index in such a way that each subsequent element resides on another cache line, which + // minimizes contention. This is done by swapping the lowest order N bits (which are the index of + // the element within the cache line) with the next N bits (which are the index of the cache line) + // of the element index. + template + constexpr unsigned remap_index_with_mix(unsigned index, unsigned mix) { + return index ^ mix ^ (mix << BITS); + } + + template + constexpr unsigned remap_index(unsigned index) noexcept { + return remap_index_with_mix(index, (index ^ (index >> BITS)) & ((1u << BITS) - 1)); + } + + template<> + constexpr unsigned remap_index<0>(unsigned index) noexcept { + return index; + } + + template + constexpr T& map(T* elements, unsigned index) noexcept { + return elements[remap_index(index)]; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // Implement a "bit-twiddling hack" for finding the next power of 2 in either 32 bits or 64 bits + // in C++11 compatible constexpr functions. The library no longer maintains C++11 compatibility. + + // "Runtime" version for 32 bits + // --a; + // a |= a >> 1; + // a |= a >> 2; + // a |= a >> 4; + // a |= a >> 8; + // a |= a >> 16; + // ++a; + + template + constexpr T decrement(T x) noexcept { + return x - 1; + } + + template + constexpr T increment(T x) noexcept { + return x + 1; + } + + template + constexpr T or_equal(T x, unsigned u) noexcept { + return x | x >> u; + } + + template + constexpr T or_equal(T x, unsigned u, Args... rest) noexcept { + return or_equal(or_equal(x, u), rest...); + } + + constexpr uint32_t round_up_to_power_of_2(uint32_t a) noexcept { + return increment(or_equal(decrement(a), 1, 2, 4, 8, 16)); + } + + constexpr uint64_t round_up_to_power_of_2(uint64_t a) noexcept { + return increment(or_equal(decrement(a), 1, 2, 4, 8, 16, 32)); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + } // namespace details + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + template + class AtomicQueueCommon { + protected: + // Put these on different cache lines to avoid false sharing between readers and writers. + alignas(CACHE_LINE_SIZE) std::atomic head_ = {}; + alignas(CACHE_LINE_SIZE) std::atomic tail_ = {}; + + // The special member functions are not thread-safe. + + AtomicQueueCommon() noexcept = default; + + AtomicQueueCommon(AtomicQueueCommon const& b) noexcept + : head_(b.head_.load(X)) + , tail_(b.tail_.load(X)) {} + + AtomicQueueCommon& operator=(AtomicQueueCommon const& b) noexcept { + head_.store(b.head_.load(X), X); + tail_.store(b.tail_.load(X), X); + return *this; + } + + void swap(AtomicQueueCommon& b) noexcept { + unsigned h = head_.load(X); + unsigned t = tail_.load(X); + head_.store(b.head_.load(X), X); + tail_.store(b.tail_.load(X), X); + b.head_.store(h, X); + b.tail_.store(t, X); + } + + template + static T do_pop_atomic(std::atomic& q_element) noexcept { + if (Derived::spsc_) { + for (;;) { + T element = q_element.load(X); + if (ATOMIC_QUEUE_LIKELY(element != NIL)) { + q_element.store(NIL, R); + return element; + } + if (Derived::maximize_throughput_) + spin_loop_pause(); + } + } + else { + for (;;) { + T element = q_element.exchange(NIL, R); // (2) The store to wait for. + if (ATOMIC_QUEUE_LIKELY(element != NIL)) + return element; + // Do speculative loads while busy-waiting to avoid broadcasting RFO messages. + do + spin_loop_pause(); + while (Derived::maximize_throughput_ && q_element.load(X) == NIL); + } + } + } + + template + static void do_push_atomic(T element, std::atomic& q_element) noexcept { + assert(element != NIL); + if (Derived::spsc_) { + while (ATOMIC_QUEUE_UNLIKELY(q_element.load(X) != NIL)) + if (Derived::maximize_throughput_) + spin_loop_pause(); + q_element.store(element, R); + } + else { + for (T expected = NIL; ATOMIC_QUEUE_UNLIKELY(!q_element.compare_exchange_strong(expected, element, R, X)); expected = NIL) { + do + spin_loop_pause(); // (1) Wait for store (2) to complete. + while (Derived::maximize_throughput_ && q_element.load(X) != NIL); + } + } + } + + enum State : unsigned char { EMPTY, STORING, STORED, LOADING }; + + template + static T do_pop_any(std::atomic& state, T& q_element) noexcept { + if (Derived::spsc_) { + while (ATOMIC_QUEUE_UNLIKELY(state.load(A) != STORED)) + if (Derived::maximize_throughput_) + spin_loop_pause(); + T element{ std::move(q_element) }; + state.store(EMPTY, R); + return element; + } + else { + for (;;) { + unsigned char expected = STORED; + if (ATOMIC_QUEUE_LIKELY(state.compare_exchange_strong(expected, LOADING, A, X))) { + T element{ std::move(q_element) }; + state.store(EMPTY, R); + return element; + } + // Do speculative loads while busy-waiting to avoid broadcasting RFO messages. + do + spin_loop_pause(); + while (Derived::maximize_throughput_ && state.load(X) != STORED); + } + } + } + + template + static void do_push_any(U&& element, std::atomic& state, T& q_element) noexcept { + if (Derived::spsc_) { + while (ATOMIC_QUEUE_UNLIKELY(state.load(A) != EMPTY)) + if (Derived::maximize_throughput_) + spin_loop_pause(); + q_element = std::forward(element); + state.store(STORED, R); + } + else { + for (;;) { + unsigned char expected = EMPTY; + if (ATOMIC_QUEUE_LIKELY(state.compare_exchange_strong(expected, STORING, A, X))) { + q_element = std::forward(element); + state.store(STORED, R); + return; + } + // Do speculative loads while busy-waiting to avoid broadcasting RFO messages. + do + spin_loop_pause(); + while (Derived::maximize_throughput_ && state.load(X) != EMPTY); + } + } + } + + public: + template + bool try_push(T&& element) noexcept { + auto head = head_.load(X); + if (Derived::spsc_) { + if (static_cast(head - tail_.load(X)) >= static_cast(static_cast(*this).size_)) + return false; + head_.store(head + 1, X); + } + else { + do { + if (static_cast(head - tail_.load(X)) >= static_cast(static_cast(*this).size_)) + return false; + } while (ATOMIC_QUEUE_UNLIKELY(!head_.compare_exchange_strong(head, head + 1, A, X))); // This loop is not FIFO. + } + + static_cast(*this).do_push(std::forward(element), head); + return true; + } + + template + bool try_pop(T& element) noexcept { + auto tail = tail_.load(X); + if (Derived::spsc_) { + if (static_cast(head_.load(X) - tail) <= 0) + return false; + tail_.store(tail + 1, X); + } + else { + do { + if (static_cast(head_.load(X) - tail) <= 0) + return false; + } while (ATOMIC_QUEUE_UNLIKELY(!tail_.compare_exchange_strong(tail, tail + 1, A, X))); // This loop is not FIFO. + } + + element = static_cast(*this).do_pop(tail); + return true; + } + + template + void push(T&& element) noexcept { + unsigned head; + if (Derived::spsc_) { + head = head_.load(X); + head_.store(head + 1, X); + } + else { + constexpr auto memory_order = Derived::total_order_ ? std::memory_order_seq_cst : std::memory_order_acquire; + head = head_.fetch_add(1, memory_order); // FIFO and total order on Intel regardless, as of 2019. + } + static_cast(*this).do_push(std::forward(element), head); + } + + auto pop() noexcept { + unsigned tail; + if (Derived::spsc_) { + tail = tail_.load(X); + tail_.store(tail + 1, X); + } + else { + constexpr auto memory_order = Derived::total_order_ ? std::memory_order_seq_cst : std::memory_order_acquire; + tail = tail_.fetch_add(1, memory_order); // FIFO and total order on Intel regardless, as of 2019. + } + return static_cast(*this).do_pop(tail); + } + + bool was_empty() const noexcept { + return !was_size(); + } + + bool was_full() const noexcept { + return was_size() >= static_cast(static_cast(*this).size_); + } + + unsigned was_size() const noexcept { + // tail_ can be greater than head_ because of consumers doing pop, rather that try_pop, when the queue is empty. + return (std::max)(static_cast(head_.load(X) - tail_.load(X)), 0); + } + + unsigned capacity() const noexcept { + return static_cast(*this).size_; + } + }; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + template + class AtomicQueue : public AtomicQueueCommon> { + using Base = AtomicQueueCommon>; + friend Base; + + static constexpr unsigned size_ = MINIMIZE_CONTENTION ? details::round_up_to_power_of_2(SIZE) : SIZE; + static constexpr int SHUFFLE_BITS = details::GetIndexShuffleBits)>::value; + static constexpr bool total_order_ = TOTAL_ORDER; + static constexpr bool spsc_ = SPSC; + static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; + + alignas(CACHE_LINE_SIZE) std::atomic elements_[size_] = {}; // Empty elements are NIL. + + T do_pop(unsigned tail) noexcept { + std::atomic& q_element = details::map(elements_, tail % size_); + return Base::template do_pop_atomic(q_element); + } + + void do_push(T element, unsigned head) noexcept { + std::atomic& q_element = details::map(elements_, head % size_); + Base::template do_push_atomic(element, q_element); + } + + public: + using value_type = T; + + AtomicQueue() noexcept { + assert(std::atomic{NIL}.is_lock_free()); // This queue is for atomic elements only. AtomicQueue2 is for non-atomic ones. + if (T{} != NIL) + for (auto& element : elements_) + element.store(NIL, X); + } + + AtomicQueue(AtomicQueue const&) = delete; + AtomicQueue& operator=(AtomicQueue const&) = delete; + }; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + template + class AtomicQueue2 : public AtomicQueueCommon> { + using Base = AtomicQueueCommon>; + using State = typename Base::State; + friend Base; + + static constexpr unsigned size_ = MINIMIZE_CONTENTION ? details::round_up_to_power_of_2(SIZE) : SIZE; + static constexpr int SHUFFLE_BITS = details::GetIndexShuffleBits::value; + static constexpr bool total_order_ = TOTAL_ORDER; + static constexpr bool spsc_ = SPSC; + static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; + + alignas(CACHE_LINE_SIZE) std::atomic states_[size_] = {}; + alignas(CACHE_LINE_SIZE) T elements_[size_] = {}; + + T do_pop(unsigned tail) noexcept { + unsigned index = details::remap_index(tail % size_); + return Base::template do_pop_any(states_[index], elements_[index]); + } + + template + void do_push(U&& element, unsigned head) noexcept { + unsigned index = details::remap_index(head % size_); + Base::template do_push_any(std::forward(element), states_[index], elements_[index]); + } + + public: + using value_type = T; + + AtomicQueue2() noexcept = default; + AtomicQueue2(AtomicQueue2 const&) = delete; + AtomicQueue2& operator=(AtomicQueue2 const&) = delete; + }; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + template, T NIL = T{}, bool MAXIMIZE_THROUGHPUT = true, bool TOTAL_ORDER = false, bool SPSC = false > + class AtomicQueueB : public AtomicQueueCommon>, + private std::allocator_traits::template rebind_alloc> { + using Base = AtomicQueueCommon>; + friend Base; + + static constexpr bool total_order_ = TOTAL_ORDER; + static constexpr bool spsc_ = SPSC; + static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; + + using AllocatorElements = typename std::allocator_traits::template rebind_alloc>; + + static constexpr auto ELEMENTS_PER_CACHE_LINE = CACHE_LINE_SIZE / sizeof(std::atomic); + static_assert(ELEMENTS_PER_CACHE_LINE, "Unexpected ELEMENTS_PER_CACHE_LINE."); + + static constexpr auto SHUFFLE_BITS = details::GetCacheLineIndexBits::value; + static_assert(SHUFFLE_BITS, "Unexpected SHUFFLE_BITS."); + + // AtomicQueueCommon members are stored into by readers and writers. + // Allocate these immutable members on another cache line which never gets invalidated by stores. + alignas(CACHE_LINE_SIZE) unsigned size_; + std::atomic* elements_; + + T do_pop(unsigned tail) noexcept { + std::atomic& q_element = details::map(elements_, tail & (size_ - 1)); + return Base::template do_pop_atomic(q_element); + } + + void do_push(T element, unsigned head) noexcept { + std::atomic& q_element = details::map(elements_, head & (size_ - 1)); + Base::template do_push_atomic(element, q_element); + } + + public: + using value_type = T; + + // The special member functions are not thread-safe. + + AtomicQueueB(unsigned size) + : size_((std::max)(details::round_up_to_power_of_2(size), 1u << (SHUFFLE_BITS * 2))) + , elements_(AllocatorElements::allocate(size_)) { + assert(std::atomic{NIL}.is_lock_free()); // This queue is for atomic elements only. AtomicQueueB2 is for non-atomic ones. + for (auto p = elements_, q = elements_ + size_; p < q; ++p) + p->store(NIL, X); + } + + AtomicQueueB(AtomicQueueB&& b) noexcept + : Base(static_cast(b)) + , AllocatorElements(static_cast(b)) // TODO: This must be noexcept, static_assert that. + , size_(b.size_) + , elements_(b.elements_) { + b.size_ = 0; + b.elements_ = 0; + } + + AtomicQueueB& operator=(AtomicQueueB&& b) noexcept { + b.swap(*this); + return *this; + } + + ~AtomicQueueB() noexcept { + if (elements_) + AllocatorElements::deallocate(elements_, size_); // TODO: This must be noexcept, static_assert that. + } + + void swap(AtomicQueueB& b) noexcept { + using std::swap; + this->Base::swap(b); + swap(static_cast(*this), static_cast(b)); + swap(size_, b.size_); + swap(elements_, b.elements_); + } + + friend void swap(AtomicQueueB& a, AtomicQueueB& b) { + a.swap(b); + } + }; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + template, bool MAXIMIZE_THROUGHPUT = true, bool TOTAL_ORDER = false, bool SPSC = false> + class AtomicQueueB2 : public AtomicQueueCommon>, + private A, + private std::allocator_traits::template rebind_alloc> { + using Base = AtomicQueueCommon>; + using State = typename Base::State; + friend Base; + + static constexpr bool total_order_ = TOTAL_ORDER; + static constexpr bool spsc_ = SPSC; + static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; + + using AllocatorElements = A; + using AllocatorStates = typename std::allocator_traits::template rebind_alloc>; + + // AtomicQueueCommon members are stored into by readers and writers. + // Allocate these immutable members on another cache line which never gets invalidated by stores. + alignas(CACHE_LINE_SIZE) unsigned size_; + std::atomic* states_; + T* elements_; + + static constexpr auto STATES_PER_CACHE_LINE = CACHE_LINE_SIZE / sizeof(State); + static_assert(STATES_PER_CACHE_LINE, "Unexpected STATES_PER_CACHE_LINE."); + + static constexpr auto SHUFFLE_BITS = details::GetCacheLineIndexBits::value; + static_assert(SHUFFLE_BITS, "Unexpected SHUFFLE_BITS."); + + T do_pop(unsigned tail) noexcept { + unsigned index = details::remap_index(tail & (size_ - 1)); + return Base::template do_pop_any(states_[index], elements_[index]); + } + + template + void do_push(U&& element, unsigned head) noexcept { + unsigned index = details::remap_index(head & (size_ - 1)); + Base::template do_push_any(std::forward(element), states_[index], elements_[index]); + } + + public: + using value_type = T; + + // The special member functions are not thread-safe. + + AtomicQueueB2(unsigned size) + : size_((std::max)(details::round_up_to_power_of_2(size), 1u << (SHUFFLE_BITS * 2))) + , states_(AllocatorStates::allocate(size_)) + , elements_(AllocatorElements::allocate(size_)) { + for (auto p = states_, q = states_ + size_; p < q; ++p) + p->store(Base::EMPTY, X); + + AllocatorElements& ae = *this; + for (auto p = elements_, q = elements_ + size_; p < q; ++p) + std::allocator_traits::construct(ae, p); + } + + AtomicQueueB2(AtomicQueueB2&& b) noexcept + : Base(static_cast(b)) + , AllocatorElements(static_cast(b)) // TODO: This must be noexcept, static_assert that. + , AllocatorStates(static_cast(b)) // TODO: This must be noexcept, static_assert that. + , size_(b.size_) + , states_(b.states_) + , elements_(b.elements_) { + b.size_ = 0; + b.states_ = 0; + b.elements_ = 0; + } + + AtomicQueueB2& operator=(AtomicQueueB2&& b) noexcept { + b.swap(*this); + return *this; + } + + ~AtomicQueueB2() noexcept { + if (elements_) { + AllocatorElements& ae = *this; + for (auto p = elements_, q = elements_ + size_; p < q; ++p) + std::allocator_traits::destroy(ae, p); + AllocatorElements::deallocate(elements_, size_); // TODO: This must be noexcept, static_assert that. + AllocatorStates::deallocate(states_, size_); // TODO: This must be noexcept, static_assert that. + } + } + + void swap(AtomicQueueB2& b) noexcept { + using std::swap; + this->Base::swap(b); + swap(static_cast(*this), static_cast(b)); + swap(static_cast(*this), static_cast(b)); + swap(size_, b.size_); + swap(states_, b.states_); + swap(elements_, b.elements_); + } + + friend void swap(AtomicQueueB2& a, AtomicQueueB2& b) noexcept { + a.swap(b); + } + }; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + template + struct RetryDecorator : Queue { + using T = typename Queue::value_type; + + using Queue::Queue; + + void push(T element) noexcept { + while (!this->try_push(element)) + spin_loop_pause(); + } + + T pop() noexcept { + T element; + while (!this->try_pop(element)) + spin_loop_pause(); + return element; + } + }; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// } // namespace atomic_queue //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Profiler/utils/functionID_set/functionId_set.h b/Profiler/utils/functionID_set/functionId_set.h index 8defbb19..fd9e8d1a 100644 --- a/Profiler/utils/functionID_set/functionId_set.h +++ b/Profiler/utils/functionID_set/functionId_set.h @@ -1,16 +1,29 @@ #pragma once #include +#include + +/// +/// Set that can only contain functionIDs, which are just unsigned ints. +/// This is based on an array and is a lot faster than the default set implementation of the standard library for this use case. +/// class functionID_set { private: - unsigned int default_size = 2'097'152; + const unsigned int default_size = 2'097'152; + static const unsigned int rotation_mask = (CHAR_BIT * sizeof(default_size) - 1); + unsigned int current_size = default_size; unsigned int num_elements = 0; unsigned int max_elements = default_size / 2; - FunctionID* set = new FunctionID[default_size] {0}; + FunctionID* set = new FunctionID[default_size]{ 0 }; + + // Predetermined values for xor operation to place in different spots unsigned int xor_values[10] = { 2134038170, 2362107340, 3229546752, 1302939050, 200405764, 79516981, 2052331209, 3415124361, 940592490, 430981309 }; + /// + /// Updates the size of the array and reinserts the element into the new array. + /// void adjust_size() { current_size *= 2; max_elements = current_size / 2; @@ -24,12 +37,22 @@ class functionID_set delete old_set; } + /// + /// Circular Shift/Rotate integer by one to the right. + /// + static inline FunctionID rotr32(FunctionID n) { + return (n >> 1) | (n << ((-1) & rotation_mask)); + } + public: - ~functionID_set() { + ~functionID_set() { delete set; } + /// + /// Empties the set and sets back the size of the underlying array. + /// void clear() { delete set; current_size = default_size; @@ -38,15 +61,25 @@ class functionID_set set = new FunctionID[default_size]{ 0 }; } + /// + /// Current size of the set. + /// unsigned int size() { return current_size; } + /// + /// Get the element at index i of the underlying array. + /// FunctionID at(unsigned int i) { return set[i]; } + /// + /// True if the set contains FunctionID f, false otherwise. + /// bool contains(FunctionID f) { + // Check the number modulo the size of the set first unsigned int position = f & (current_size - 1); if (set[position] == 0) { return false; @@ -55,8 +88,9 @@ class functionID_set return true; } + // Then rotate bits and xor to try and find a new position for (int i = 0; i < 10; i++) { - position = (f ^ xor_values[0]) & (current_size - 1); + position = (rotr32(f) ^ xor_values[0]) & (current_size - 1); if (set[position] == 0) { return false; } @@ -65,6 +99,7 @@ class functionID_set } } + // If no position was found, just go to the next position with +1 position = (f + 1) & (current_size - 1); while (set[position] != f) { if (set[position] == 0) { @@ -75,7 +110,9 @@ class functionID_set return set[position]; } - + /// + /// Inserts FunctionID f into the set. + /// void insert(FunctionID f) { num_elements++; if (num_elements > max_elements) { @@ -90,7 +127,6 @@ class functionID_set return; } - for (int i = 0; i < 10; i++) { position = (f ^ xor_values[0]) & (current_size - 1); if (set[position] == f) { @@ -112,4 +148,3 @@ class functionID_set set[position] = f; } }; - diff --git a/Profiler/utils/robin_hood/robin_hood.h b/Profiler/utils/robin_hood/robin_hood.h deleted file mode 100644 index 00a943c4..00000000 --- a/Profiler/utils/robin_hood/robin_hood.h +++ /dev/null @@ -1,2551 +0,0 @@ -// ______ _____ ______ _________ -// ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ / -// __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ / -// _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ / -// /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/ -// _/_____/ -// -// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20 -// https://github.com/martinus/robin-hood-hashing -// -// Licensed under the MIT License . -// SPDX-License-Identifier: MIT -// Copyright (c) 2018-2021 Martin Ankerl -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#ifndef ROBIN_HOOD_H_INCLUDED -#define ROBIN_HOOD_H_INCLUDED - -// see https://semver.org/ -#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes -#define ROBIN_HOOD_VERSION_MINOR 11 // for adding functionality in a backwards-compatible manner -#define ROBIN_HOOD_VERSION_PATCH 5 // for backwards-compatible bug fixes - -#include -#include -#include -#include -#include -#include // only to support hash of smart pointers -#include -#include -#include -#include -#if __cplusplus >= 201703L -# include -#endif - -// #define ROBIN_HOOD_LOG_ENABLED -#ifdef ROBIN_HOOD_LOG_ENABLED -# include -# define ROBIN_HOOD_LOG(...) \ - std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; -#else -# define ROBIN_HOOD_LOG(x) -#endif - -// #define ROBIN_HOOD_TRACE_ENABLED -#ifdef ROBIN_HOOD_TRACE_ENABLED -# include -# define ROBIN_HOOD_TRACE(...) \ - std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; -#else -# define ROBIN_HOOD_TRACE(x) -#endif - -// #define ROBIN_HOOD_COUNT_ENABLED -#ifdef ROBIN_HOOD_COUNT_ENABLED -# include -# define ROBIN_HOOD_COUNT(x) ++counts().x; -namespace robin_hood { - struct Counts { - uint64_t shiftUp{}; - uint64_t shiftDown{}; - }; - inline std::ostream& operator<<(std::ostream& os, Counts const& c) { - return os << c.shiftUp << " shiftUp" << std::endl << c.shiftDown << " shiftDown" << std::endl; - } - - static Counts& counts() { - static Counts counts{}; - return counts; - } -} // namespace robin_hood -#else -# define ROBIN_HOOD_COUNT(x) -#endif - -// all non-argument macros should use this facility. See -// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ -#define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x() - -// mark unused members with this macro -#define ROBIN_HOOD_UNUSED(identifier) - -// bitness -#if SIZE_MAX == UINT32_MAX -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32 -#elif SIZE_MAX == UINT64_MAX -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64 -#else -# error Unsupported bitness -#endif - -// endianess -#ifdef _MSC_VER -# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1 -# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0 -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \ - (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) -# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) -#endif - -// inline -#ifdef _MSC_VER -# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline) -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline)) -#endif - -// exceptions -#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0 -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1 -#endif - -// count leading/trailing bits -#if !defined(ROBIN_HOOD_DISABLE_INTRINSICS) -# ifdef _MSC_VER -# if ROBIN_HOOD(BITNESS) == 32 -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64 -# endif -# include -# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD)) -# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \ - [](size_t mask) noexcept -> int { \ - unsigned long index; \ - return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast(index) \ - : ROBIN_HOOD(BITNESS); \ - }(x) -# else -# if ROBIN_HOOD(BITNESS) == 32 -# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl -# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll -# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll -# endif -# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS)) -# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS)) -# endif -#endif - -// fallthrough -#ifndef __has_cpp_attribute // For backwards compatibility -# define __has_cpp_attribute(x) 0 -#endif -#if __has_cpp_attribute(clang::fallthrough) -# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]] -#elif __has_cpp_attribute(gnu::fallthrough) -# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]] -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() -#endif - -// likely/unlikely -#ifdef _MSC_VER -# define ROBIN_HOOD_LIKELY(condition) condition -# define ROBIN_HOOD_UNLIKELY(condition) condition -#else -# define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1) -# define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0) -#endif - -// detect if native wchar_t type is availiable in MSVC -#ifdef _MSC_VER -# ifdef _NATIVE_WCHAR_T_DEFINED -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 0 -# endif -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 -#endif - -// detect if MSVC supports the pair(std::piecewise_construct_t,...) consructor being constexpr -#ifdef _MSC_VER -# if _MSC_VER <= 1900 -# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 1 -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 -# endif -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 -#endif - -// workaround missing "is_trivially_copyable" in g++ < 5.0 -// See https://stackoverflow.com/a/31798726/48181 -#if defined(__GNUC__) && __GNUC__ < 5 -# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) -#else -# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value -#endif - -// helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L - -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) -# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]] -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() -#endif - -namespace robin_hood { - -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) -# define ROBIN_HOOD_STD std -#else - - // c++11 compatibility layer - namespace ROBIN_HOOD_STD { - template - struct alignment_of - : std::integral_constant::type)> {}; - - template - class integer_sequence { - public: - using value_type = T; - static_assert(std::is_integral::value, "not integral type"); - static constexpr std::size_t size() noexcept { - return sizeof...(Ints); - } - }; - template - using index_sequence = integer_sequence; - - namespace detail_ { - template - struct IntSeqImpl { - using TValue = T; - static_assert(std::is_integral::value, "not integral type"); - static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)"); - - template - struct IntSeqCombiner; - - template - struct IntSeqCombiner, integer_sequence> { - using TResult = integer_sequence; - }; - - using TResult = - typename IntSeqCombiner::TResult, - typename IntSeqImpl::TResult>::TResult; - }; - - template - struct IntSeqImpl { - using TValue = T; - static_assert(std::is_integral::value, "not integral type"); - static_assert(Begin >= 0, "unexpected argument (Begin<0)"); - using TResult = integer_sequence; - }; - - template - struct IntSeqImpl { - using TValue = T; - static_assert(std::is_integral::value, "not integral type"); - static_assert(Begin >= 0, "unexpected argument (Begin<0)"); - using TResult = integer_sequence; - }; - } // namespace detail_ - - template - using make_integer_sequence = typename detail_::IntSeqImpl::TResult; - - template - using make_index_sequence = make_integer_sequence; - - template - using index_sequence_for = make_index_sequence; - - } // namespace ROBIN_HOOD_STD - -#endif - - namespace detail { - - // make sure we static_cast to the correct type for hash_int -#if ROBIN_HOOD(BITNESS) == 64 - using SizeT = uint64_t; -#else - using SizeT = uint32_t; -#endif - - template - T rotr(T x, unsigned k) { - return (x >> k) | (x << (8U * sizeof(T) - k)); - } - - // This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to - // 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with - // care! - template - inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept { - return reinterpret_cast(ptr); - } - - template - inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept { - return reinterpret_cast(ptr); - } - - // make sure this is not inlined as it is slow and dramatically enlarges code, thus making other - // inlinings more difficult. Throws are also generally the slow path. - template - [[noreturn]] ROBIN_HOOD(NOINLINE) -#if ROBIN_HOOD(HAS_EXCEPTIONS) - void doThrow(Args&&... args) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) - throw E(std::forward(args)...); - } -#else - void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) { - abort(); - } -#endif - - template - T* assertNotNull(T* t, Args&&... args) { - if (ROBIN_HOOD_UNLIKELY(nullptr == t)) { - doThrow(std::forward(args)...); - } - return t; - } - - template - inline T unaligned_load(void const* ptr) noexcept { - // using memcpy so we don't get into unaligned load problems. - // compiler should optimize this very well anyways. - T t; - std::memcpy(&t, ptr, sizeof(T)); - return t; - } - - // Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor, - // and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a - // pointer. - template - class BulkPoolAllocator { - public: - BulkPoolAllocator() noexcept = default; - - // does not copy anything, just creates a new allocator. - BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept - : mHead(nullptr) - , mListForFree(nullptr) {} - - BulkPoolAllocator(BulkPoolAllocator&& o) noexcept - : mHead(o.mHead) - , mListForFree(o.mListForFree) { - o.mListForFree = nullptr; - o.mHead = nullptr; - } - - BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept { - reset(); - mHead = o.mHead; - mListForFree = o.mListForFree; - o.mListForFree = nullptr; - o.mHead = nullptr; - return *this; - } - - BulkPoolAllocator& - // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) - operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept { - // does not do anything - return *this; - } - - ~BulkPoolAllocator() noexcept { - reset(); - } - - // Deallocates all allocated memory. - void reset() noexcept { - while (mListForFree) { - T* tmp = *mListForFree; - ROBIN_HOOD_LOG("std::free") - std::free(mListForFree); - mListForFree = reinterpret_cast_no_cast_align_warning(tmp); - } - mHead = nullptr; - } - - // allocates, but does NOT initialize. Use in-place new constructor, e.g. - // T* obj = pool.allocate(); - // ::new (static_cast(obj)) T(); - T* allocate() { - T* tmp = mHead; - if (!tmp) { - tmp = performAllocation(); - } - - mHead = *reinterpret_cast_no_cast_align_warning(tmp); - return tmp; - } - - // does not actually deallocate but puts it in store. - // make sure you have already called the destructor! e.g. with - // obj->~T(); - // pool.deallocate(obj); - void deallocate(T* obj) noexcept { - *reinterpret_cast_no_cast_align_warning(obj) = mHead; - mHead = obj; - } - - // Adds an already allocated block of memory to the allocator. This allocator is from now on - // responsible for freeing the data (with free()). If the provided data is not large enough to - // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor. - void addOrFree(void* ptr, const size_t numBytes) noexcept { - // calculate number of available elements in ptr - if (numBytes < ALIGNMENT + ALIGNED_SIZE) { - // not enough data for at least one element. Free and return. - ROBIN_HOOD_LOG("std::free") - std::free(ptr); - } - else { - ROBIN_HOOD_LOG("add to buffer") - add(ptr, numBytes); - } - } - - void swap(BulkPoolAllocator& other) noexcept { - using std::swap; - swap(mHead, other.mHead); - swap(mListForFree, other.mListForFree); - } - - private: - // iterates the list of allocated memory to calculate how many to alloc next. - // Recalculating this each time saves us a size_t member. - // This ignores the fact that memory blocks might have been added manually with addOrFree. In - // practice, this should not matter much. - ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept { - auto tmp = mListForFree; - size_t numAllocs = MinNumAllocs; - - while (numAllocs * 2 <= MaxNumAllocs && tmp) { - auto x = reinterpret_cast(tmp); - tmp = *x; - numAllocs *= 2; - } - - return numAllocs; - } - - // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree(). - void add(void* ptr, const size_t numBytes) noexcept { - const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE; - - auto data = reinterpret_cast(ptr); - - // link free list - auto x = reinterpret_cast(data); - *x = mListForFree; - mListForFree = data; - - // create linked list for newly allocated data - auto* const headT = - reinterpret_cast_no_cast_align_warning(reinterpret_cast(ptr) + ALIGNMENT); - - auto* const head = reinterpret_cast(headT); - - // Visual Studio compiler automatically unrolls this loop, which is pretty cool - for (size_t i = 0; i < numElements; ++i) { - *reinterpret_cast_no_cast_align_warning(head + i * ALIGNED_SIZE) = - head + (i + 1) * ALIGNED_SIZE; - } - - // last one points to 0 - *reinterpret_cast_no_cast_align_warning(head + (numElements - 1) * ALIGNED_SIZE) = - mHead; - mHead = headT; - } - - // Called when no memory is available (mHead == 0). - // Don't inline this slow path. - ROBIN_HOOD(NOINLINE) T* performAllocation() { - size_t const numElementsToAlloc = calcNumElementsToAlloc(); - - // alloc new memory: [prev |T, T, ... T] - size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc; - ROBIN_HOOD_LOG("std::malloc " << bytes << " = " << ALIGNMENT << " + " << ALIGNED_SIZE - << " * " << numElementsToAlloc) - add(assertNotNull(std::malloc(bytes)), bytes); - return mHead; - } - - // enforce byte alignment of the T's -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) - static constexpr size_t ALIGNMENT = - (std::max)(std::alignment_of::value, std::alignment_of::value); -#else - static const size_t ALIGNMENT = - (ROBIN_HOOD_STD::alignment_of::value > ROBIN_HOOD_STD::alignment_of::value) - ? ROBIN_HOOD_STD::alignment_of::value - : +ROBIN_HOOD_STD::alignment_of::value; // the + is for walkarround -#endif - - static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT; - - static_assert(MinNumAllocs >= 1, "MinNumAllocs"); - static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs"); - static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE"); - static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod"); - static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT"); - - T* mHead{ nullptr }; - T** mListForFree{ nullptr }; - }; - - template - struct NodeAllocator; - - // dummy allocator that does nothing - template - struct NodeAllocator { - - // we are not using the data, so just free it. - void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept { - ROBIN_HOOD_LOG("std::free") - std::free(ptr); - } - }; - - template - struct NodeAllocator : public BulkPoolAllocator {}; - - // c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making - // my own here. - namespace swappable { -#if ROBIN_HOOD(CXX) < ROBIN_HOOD(CXX17) - using std::swap; - template - struct nothrow { - static const bool value = noexcept(swap(std::declval(), std::declval())); - }; -#else - template - struct nothrow { - static const bool value = std::is_nothrow_swappable::value; - }; -#endif - } // namespace swappable - - } // namespace detail - - struct is_transparent_tag {}; - - // A custom pair implementation is used in the map because std::pair is not is_trivially_copyable, - // which means it would not be allowed to be used in std::memcpy. This struct is copyable, which is - // also tested. - template - struct pair { - using first_type = T1; - using second_type = T2; - - template ::value&& - std::is_default_constructible::value>::type> - constexpr pair() noexcept(noexcept(U1()) && noexcept(U2())) - : first() - , second() {} - - // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. - explicit constexpr pair(std::pair const& o) noexcept( - noexcept(T1(std::declval())) && noexcept(T2(std::declval()))) - : first(o.first) - , second(o.second) {} - - // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. - explicit constexpr pair(std::pair&& o) noexcept(noexcept( - T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) - : first(std::move(o.first)) - , second(std::move(o.second)) {} - - constexpr pair(T1&& a, T2&& b) noexcept(noexcept( - T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) - : first(std::move(a)) - , second(std::move(b)) {} - - template - constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward( - std::declval()))) && noexcept(T2(std::forward(std::declval())))) - : first(std::forward(a)) - , second(std::forward(b)) {} - - template - // MSVC 2015 produces error "C2476: ‘constexpr’ constructor does not initialize all members" - // if this constructor is constexpr -#if !ROBIN_HOOD(BROKEN_CONSTEXPR) - constexpr -#endif - pair(std::piecewise_construct_t /*unused*/, std::tuple a, - std::tuple - b) noexcept(noexcept(pair(std::declval&>(), - std::declval&>(), - ROBIN_HOOD_STD::index_sequence_for(), - ROBIN_HOOD_STD::index_sequence_for()))) - : pair(a, b, ROBIN_HOOD_STD::index_sequence_for(), - ROBIN_HOOD_STD::index_sequence_for()) { - } - - // constructor called from the std::piecewise_construct_t ctor - template - pair(std::tuple& a, std::tuple& b, ROBIN_HOOD_STD::index_sequence /*unused*/, ROBIN_HOOD_STD::index_sequence /*unused*/) noexcept( - noexcept(T1(std::forward(std::get( - std::declval&>()))...)) && noexcept(T2(std:: - forward(std::get( - std::declval&>()))...))) - : first(std::forward(std::get(a))...) - , second(std::forward(std::get(b))...) { - // make visual studio compiler happy about warning about unused a & b. - // Visual studio's pair implementation disables warning 4100. - (void)a; - (void)b; - } - - void swap(pair& o) noexcept((detail::swappable::nothrow::value) && - (detail::swappable::nothrow::value)) { - using std::swap; - swap(first, o.first); - swap(second, o.second); - } - - T1 first; // NOLINT(misc-non-private-member-variables-in-classes) - T2 second; // NOLINT(misc-non-private-member-variables-in-classes) - }; - - template - inline void swap(pair& a, pair& b) noexcept( - noexcept(std::declval&>().swap(std::declval&>()))) { - a.swap(b); - } - - template - inline constexpr bool operator==(pair const& x, pair const& y) { - return (x.first == y.first) && (x.second == y.second); - } - template - inline constexpr bool operator!=(pair const& x, pair const& y) { - return !(x == y); - } - template - inline constexpr bool operator<(pair const& x, pair const& y) noexcept(noexcept( - std::declval() < std::declval()) && noexcept(std::declval() < - std::declval())) { - return x.first < y.first || (!(y.first < x.first) && x.second < y.second); - } - template - inline constexpr bool operator>(pair const& x, pair const& y) { - return y < x; - } - template - inline constexpr bool operator<=(pair const& x, pair const& y) { - return !(x > y); - } - template - inline constexpr bool operator>=(pair const& x, pair const& y) { - return !(x < y); - } - - inline size_t hash_bytes(void const* ptr, size_t len) noexcept { - static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995); - static constexpr uint64_t seed = UINT64_C(0xe17a1465); - static constexpr unsigned int r = 47; - - auto const* const data64 = static_cast(ptr); - uint64_t h = seed ^ (len * m); - - size_t const n_blocks = len / 8; - for (size_t i = 0; i < n_blocks; ++i) { - auto k = detail::unaligned_load(data64 + i); - - k *= m; - k ^= k >> r; - k *= m; - - h ^= k; - h *= m; - } - - auto const* const data8 = reinterpret_cast(data64 + n_blocks); - switch (len & 7U) { - case 7: - h ^= static_cast(data8[6]) << 48U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 6: - h ^= static_cast(data8[5]) << 40U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 5: - h ^= static_cast(data8[4]) << 32U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 4: - h ^= static_cast(data8[3]) << 24U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 3: - h ^= static_cast(data8[2]) << 16U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 2: - h ^= static_cast(data8[1]) << 8U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 1: - h ^= static_cast(data8[0]); - h *= m; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - default: - break; - } - - h ^= h >> r; - - // not doing the final step here, because this will be done by keyToIdx anyways - // h *= m; - // h ^= h >> r; - return static_cast(h); - } - - inline size_t hash_int(uint64_t x) noexcept { - // tried lots of different hashes, let's stick with murmurhash3. It's simple, fast, well tested, - // and doesn't need any special 128bit operations. - x ^= x >> 33U; - x *= UINT64_C(0xff51afd7ed558ccd); - x ^= x >> 33U; - - // not doing the final step here, because this will be done by keyToIdx anyways - // x *= UINT64_C(0xc4ceb9fe1a85ec53); - // x ^= x >> 33U; - return static_cast(x); - } - - // A thin wrapper around std::hash, performing an additional simple mixing step of the result. - template - struct hash : public std::hash { - size_t operator()(T const& obj) const - noexcept(noexcept(std::declval>().operator()(std::declval()))) { - // call base hash - auto result = std::hash::operator()(obj); - // return mixed of that, to be save against identity has - return hash_int(static_cast(result)); - } - }; - - template - struct hash> { - size_t operator()(std::basic_string const& str) const noexcept { - return hash_bytes(str.data(), sizeof(CharT) * str.size()); - } - }; - -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) - template - struct hash> { - size_t operator()(std::basic_string_view const& sv) const noexcept { - return hash_bytes(sv.data(), sizeof(CharT) * sv.size()); - } - }; -#endif - - template - struct hash { - size_t operator()(T* ptr) const noexcept { - return hash_int(reinterpret_cast(ptr)); - } - }; - - template - struct hash> { - size_t operator()(std::unique_ptr const& ptr) const noexcept { - return hash_int(reinterpret_cast(ptr.get())); - } - }; - - template - struct hash> { - size_t operator()(std::shared_ptr const& ptr) const noexcept { - return hash_int(reinterpret_cast(ptr.get())); - } - }; - - template - struct hash::value>::type> { - size_t operator()(Enum e) const noexcept { - using Underlying = typename std::underlying_type::type; - return hash{}(static_cast(e)); - } - }; - -#define ROBIN_HOOD_HASH_INT(T) \ - template <> \ - struct hash { \ - size_t operator()(T const& obj) const noexcept { \ - return hash_int(static_cast(obj)); \ - } \ - } - -#if defined(__GNUC__) && !defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wuseless-cast" -#endif - // see https://en.cppreference.com/w/cpp/utility/hash - ROBIN_HOOD_HASH_INT(bool); - ROBIN_HOOD_HASH_INT(char); - ROBIN_HOOD_HASH_INT(signed char); - ROBIN_HOOD_HASH_INT(unsigned char); - ROBIN_HOOD_HASH_INT(char16_t); - ROBIN_HOOD_HASH_INT(char32_t); -#if ROBIN_HOOD(HAS_NATIVE_WCHART) - ROBIN_HOOD_HASH_INT(wchar_t); -#endif - ROBIN_HOOD_HASH_INT(short); - ROBIN_HOOD_HASH_INT(unsigned short); - ROBIN_HOOD_HASH_INT(int); - ROBIN_HOOD_HASH_INT(unsigned int); - ROBIN_HOOD_HASH_INT(long); - ROBIN_HOOD_HASH_INT(long long); - ROBIN_HOOD_HASH_INT(unsigned long); - ROBIN_HOOD_HASH_INT(unsigned long long); -#if defined(__GNUC__) && !defined(__clang__) -# pragma GCC diagnostic pop -#endif - namespace detail { - - template - struct void_type { - using type = void; - }; - - template - struct has_is_transparent : public std::false_type {}; - - template - struct has_is_transparent::type> - : public std::true_type {}; - - // using wrapper classes for hash and key_equal prevents the diamond problem when the same type - // is used. see https://stackoverflow.com/a/28771920/48181 - template - struct WrapHash : public T { - WrapHash() = default; - explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval()))) - : T(o) {} - }; - - template - struct WrapKeyEqual : public T { - WrapKeyEqual() = default; - explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval()))) - : T(o) {} - }; - - // A highly optimized hashmap implementation, using the Robin Hood algorithm. - // - // In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but - // be about 2x faster in most cases and require much less allocations. - // - // This implementation uses the following memory layout: - // - // [Node, Node, ... Node | info, info, ... infoSentinel ] - // - // * Node: either a DataNode that directly has the std::pair as member, - // or a DataNode with a pointer to std::pair. Which DataNode representation to use - // depends on how fast the swap() operation is. Heuristically, this is automatically choosen - // based on sizeof(). there are always 2^n Nodes. - // - // * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes. - // Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the - // corresponding node contains data. Set to 2 means the corresponding Node is filled, but it - // actually belongs to the previous position and was pushed out because that place is already - // taken. - // - // * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the - // need for a idx variable. - // - // According to STL, order of templates has effect on throughput. That's why I've moved the - // boolean to the front. - // https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/ - template - class Table - : public WrapHash, - public WrapKeyEqual, - detail::NodeAllocator< - typename std::conditional< - std::is_void::value, Key, - robin_hood::pair::type, T>>::type, - 4, 16384, IsFlat> { - public: - static constexpr bool is_flat = IsFlat; - static constexpr bool is_map = !std::is_void::value; - static constexpr bool is_set = !is_map; - static constexpr bool is_transparent = - has_is_transparent::value && has_is_transparent::value; - - using key_type = Key; - using mapped_type = T; - using value_type = typename std::conditional< - is_set, Key, - robin_hood::pair::type, T>>::type; - using size_type = size_t; - using hasher = Hash; - using key_equal = KeyEqual; - using Self = Table; - - private: - static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100, - "MaxLoadFactor100 needs to be >10 && < 100"); - - using WHash = WrapHash; - using WKeyEqual = WrapKeyEqual; - - // configuration defaults - - // make sure we have 8 elements, needed to quickly rehash mInfo - static constexpr size_t InitialNumElements = sizeof(uint64_t); - static constexpr uint32_t InitialInfoNumBits = 5; - static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits; - static constexpr size_t InfoMask = InitialInfoInc - 1U; - static constexpr uint8_t InitialInfoHashShift = 0; - using DataPool = detail::NodeAllocator; - - // type needs to be wider than uint8_t. - using InfoType = uint32_t; - - // DataNode //////////////////////////////////////////////////////// - - // Primary template for the data node. We have special implementations for small and big - // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these - // on the heap so swap merely swaps a pointer. - template - class DataNode {}; - - // Small: just allocate on the stack. - template - class DataNode final { - public: - template - explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept( - noexcept(value_type(std::forward(args)...))) - : mData(std::forward(args)...) {} - - DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept( - std::is_nothrow_move_constructible::value) - : mData(std::move(n.mData)) {} - - // doesn't do anything - void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {} - void destroyDoNotDeallocate() noexcept {} - - value_type const* operator->() const noexcept { - return &mData; - } - value_type* operator->() noexcept { - return &mData; - } - - const value_type& operator*() const noexcept { - return mData; - } - - value_type& operator*() noexcept { - return mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return mData.first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type - getFirst() const noexcept { - return mData.first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() const noexcept { - return mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() noexcept { - return mData.second; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() const noexcept { - return mData.second; - } - - void swap(DataNode& o) noexcept( - noexcept(std::declval().swap(std::declval()))) { - mData.swap(o.mData); - } - - private: - value_type mData; - }; - - // big object: allocate on heap. - template - class DataNode { - public: - template - explicit DataNode(M& map, Args&&... args) - : mData(map.allocate()) { - ::new (static_cast(mData)) value_type(std::forward(args)...); - } - - DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept - : mData(std::move(n.mData)) {} - - void destroy(M& map) noexcept { - // don't deallocate, just put it into list of datapool. - mData->~value_type(); - map.deallocate(mData); - } - - void destroyDoNotDeallocate() noexcept { - mData->~value_type(); - } - - value_type const* operator->() const noexcept { - return mData; - } - - value_type* operator->() noexcept { - return mData; - } - - const value_type& operator*() const { - return *mData; - } - - value_type& operator*() { - return *mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return mData->first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return *mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type - getFirst() const noexcept { - return mData->first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() const noexcept { - return *mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() noexcept { - return mData->second; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() const noexcept { - return mData->second; - } - - void swap(DataNode& o) noexcept { - using std::swap; - swap(mData, o.mData); - } - - private: - value_type* mData; - }; - - using Node = DataNode; - - // helpers for insertKeyPrepareEmptySpot: extract first entry (only const required) - ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(Node const& n) const noexcept { - return n.getFirst(); - } - - // in case we have void mapped_type, we are not using a pair, thus we just route k through. - // No need to disable this because it's just not used if not applicable. - ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(key_type const& k) const noexcept { - return k; - } - - // in case we have non-void mapped_type, we have a standard robin_hood::pair - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::value, key_type const&>::type - getFirstConst(value_type const& vt) const noexcept { - return vt.first; - } - - // Cloner ////////////////////////////////////////////////////////// - - template - struct Cloner; - - // fast path: Just copy data, without allocating anything. - template - struct Cloner { - void operator()(M const& source, M& target) const { - auto const* const src = reinterpret_cast(source.mKeyVals); - auto* tgt = reinterpret_cast(target.mKeyVals); - auto const numElementsWithBuffer = target.calcNumElementsWithBuffer(target.mMask + 1); - std::copy(src, src + target.calcNumBytesTotal(numElementsWithBuffer), tgt); - } - }; - - template - struct Cloner { - void operator()(M const& s, M& t) const { - auto const numElementsWithBuffer = t.calcNumElementsWithBuffer(t.mMask + 1); - std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(numElementsWithBuffer), t.mInfo); - - for (size_t i = 0; i < numElementsWithBuffer; ++i) { - if (t.mInfo[i]) { - ::new (static_cast(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]); - } - } - } - }; - - // Destroyer /////////////////////////////////////////////////////// - - template - struct Destroyer {}; - - template - struct Destroyer { - void nodes(M& m) const noexcept { - m.mNumElements = 0; - } - - void nodesDoNotDeallocate(M& m) const noexcept { - m.mNumElements = 0; - } - }; - - template - struct Destroyer { - void nodes(M& m) const noexcept { - m.mNumElements = 0; - // clear also resets mInfo to 0, that's sometimes not necessary. - auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); - - for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { - if (0 != m.mInfo[idx]) { - Node& n = m.mKeyVals[idx]; - n.destroy(m); - n.~Node(); - } - } - } - - void nodesDoNotDeallocate(M& m) const noexcept { - m.mNumElements = 0; - // clear also resets mInfo to 0, that's sometimes not necessary. - auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); - for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { - if (0 != m.mInfo[idx]) { - Node& n = m.mKeyVals[idx]; - n.destroyDoNotDeallocate(); - n.~Node(); - } - } - } - }; - - // Iter //////////////////////////////////////////////////////////// - - struct fast_forward_tag {}; - - // generic iterator for both const_iterator and iterator. - template - // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions) - class Iter { - private: - using NodePtr = typename std::conditional::type; - - public: - using difference_type = std::ptrdiff_t; - using value_type = typename Self::value_type; - using reference = typename std::conditional::type; - using pointer = typename std::conditional::type; - using iterator_category = std::forward_iterator_tag; - - // default constructed iterator can be compared to itself, but WON'T return true when - // compared to end(). - Iter() = default; - - // Rule of zero: nothing specified. The conversion constructor is only enabled for - // iterator to const_iterator, so it doesn't accidentally work as a copy ctor. - - // Conversion constructor from iterator to const_iterator. - template ::type> - // NOLINTNEXTLINE(hicpp-explicit-conversions) - Iter(Iter const& other) noexcept - : mKeyVals(other.mKeyVals) - , mInfo(other.mInfo) {} - - Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept - : mKeyVals(valPtr) - , mInfo(infoPtr) {} - - Iter(NodePtr valPtr, uint8_t const* infoPtr, - fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept - : mKeyVals(valPtr) - , mInfo(infoPtr) { - fastForward(); - } - - template ::type> - Iter& operator=(Iter const& other) noexcept { - mKeyVals = other.mKeyVals; - mInfo = other.mInfo; - return *this; - } - - // prefix increment. Undefined behavior if we are at end()! - Iter& operator++() noexcept { - mInfo++; - mKeyVals++; - fastForward(); - return *this; - } - - Iter operator++(int) noexcept { - Iter tmp = *this; - ++(*this); - return tmp; - } - - reference operator*() const { - return **mKeyVals; - } - - pointer operator->() const { - return &**mKeyVals; - } - - template - bool operator==(Iter const& o) const noexcept { - return mKeyVals == o.mKeyVals; - } - - template - bool operator!=(Iter const& o) const noexcept { - return mKeyVals != o.mKeyVals; - } - - private: - // fast forward to the next non-free info byte - // I've tried a few variants that don't depend on intrinsics, but unfortunately they are - // quite a bit slower than this one. So I've reverted that change again. See map_benchmark. - void fastForward() noexcept { - size_t n = 0; - while (0U == (n = detail::unaligned_load(mInfo))) { - mInfo += sizeof(size_t); - mKeyVals += sizeof(size_t); - } -#if defined(ROBIN_HOOD_DISABLE_INTRINSICS) - // we know for certain that within the next 8 bytes we'll find a non-zero one. - if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { - mInfo += 4; - mKeyVals += 4; - } - if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { - mInfo += 2; - mKeyVals += 2; - } - if (ROBIN_HOOD_UNLIKELY(0U == *mInfo)) { - mInfo += 1; - mKeyVals += 1; - } -#else -# if ROBIN_HOOD(LITTLE_ENDIAN) - auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8; -# else - auto inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8; -# endif - mInfo += inc; - mKeyVals += inc; -#endif - } - - friend class Table; - NodePtr mKeyVals{ nullptr }; - uint8_t const* mInfo{ nullptr }; - }; - - //////////////////////////////////////////////////////////////////// - - // highly performance relevant code. - // Lower bits are used for indexing into the array (2^n size) - // The upper 1-5 bits need to be a reasonable good hash, to save comparisons. - template - void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const { - // In addition to whatever hash is used, add another mul & shift so we get better hashing. - // This serves as a bad hash prevention, if the given data is - // badly mixed. - auto h = static_cast(WHash::operator()(key)); - - h *= mHashMultiplier; - h ^= h >> 33U; - - // the lower InitialInfoNumBits are reserved for info. - *info = mInfoInc + static_cast((h & InfoMask) >> mInfoHashShift); - *idx = (static_cast(h) >> InitialInfoNumBits) & mMask; - } - - // forwards the index by one, wrapping around at the end - void next(InfoType* info, size_t* idx) const noexcept { - *idx = *idx + 1; - *info += mInfoInc; - } - - void nextWhileLess(InfoType* info, size_t* idx) const noexcept { - // unrolling this by hand did not bring any speedups. - while (*info < mInfo[*idx]) { - next(info, idx); - } - } - - // Shift everything up by one element. Tries to move stuff around. - void - shiftUp(size_t startIdx, - size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable::value) { - auto idx = startIdx; - ::new (static_cast(mKeyVals + idx)) Node(std::move(mKeyVals[idx - 1])); - while (--idx != insertion_idx) { - mKeyVals[idx] = std::move(mKeyVals[idx - 1]); - } - - idx = startIdx; - while (idx != insertion_idx) { - ROBIN_HOOD_COUNT(shiftUp) - mInfo[idx] = static_cast(mInfo[idx - 1] + mInfoInc); - if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) { - mMaxNumElementsAllowed = 0; - } - --idx; - } - } - - void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable::value) { - // until we find one that is either empty or has zero offset. - // TODO(martinus) we don't need to move everything, just the last one for the same - // bucket. - mKeyVals[idx].destroy(*this); - - // until we find one that is either empty or has zero offset. - while (mInfo[idx + 1] >= 2 * mInfoInc) { - ROBIN_HOOD_COUNT(shiftDown) - mInfo[idx] = static_cast(mInfo[idx + 1] - mInfoInc); - mKeyVals[idx] = std::move(mKeyVals[idx + 1]); - ++idx; - } - - mInfo[idx] = 0; - // don't destroy, we've moved it - // mKeyVals[idx].destroy(*this); - mKeyVals[idx].~Node(); - } - - // copy of find(), except that it returns iterator instead of const_iterator. - template - ROBIN_HOOD(NODISCARD) - size_t findIdx(Other const& key) const { - size_t idx{}; - InfoType info{}; - keyToIdx(key, &idx, &info); - - do { - // unrolling this twice gives a bit of a speedup. More unrolling did not help. - if (info == mInfo[idx] && - ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { - return idx; - } - next(&info, &idx); - if (info == mInfo[idx] && - ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { - return idx; - } - next(&info, &idx); - } while (info <= mInfo[idx]); - - // nothing found! - return mMask == 0 ? 0 - : static_cast(std::distance( - mKeyVals, reinterpret_cast_no_cast_align_warning(mInfo))); - } - - void cloneData(const Table& o) { - Cloner()(o, *this); - } - - // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized. - // @return True on success, false if something went wrong - void insert_move(Node&& keyval) { - // we don't retry, fail if overflowing - // don't need to check max num elements - if (0 == mMaxNumElementsAllowed && !try_increase_info()) { - throwOverflowError(); - } - - size_t idx{}; - InfoType info{}; - keyToIdx(keyval.getFirst(), &idx, &info); - - // skip forward. Use <= because we are certain that the element is not there. - while (info <= mInfo[idx]) { - idx = idx + 1; - info += mInfoInc; - } - - // key not found, so we are now exactly where we want to insert it. - auto const insertion_idx = idx; - auto const insertion_info = static_cast(info); - if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { - mMaxNumElementsAllowed = 0; - } - - // find an empty spot - while (0 != mInfo[idx]) { - next(&info, &idx); - } - - auto& l = mKeyVals[insertion_idx]; - if (idx == insertion_idx) { - ::new (static_cast(&l)) Node(std::move(keyval)); - } - else { - shiftUp(idx, insertion_idx); - l = std::move(keyval); - } - - // put at empty spot - mInfo[insertion_idx] = insertion_info; - - ++mNumElements; - } - - public: - using iterator = Iter; - using const_iterator = Iter; - - Table() noexcept(noexcept(Hash()) && noexcept(KeyEqual())) - : WHash() - , WKeyEqual() { - ROBIN_HOOD_TRACE(this) - } - - // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. - // This tremendously speeds up ctor & dtor of a map that never receives an element. The - // penalty is payed at the first insert, and not before. Lookup of this empty map works - // because everybody points to DummyInfoByte::b. parameter bucket_count is dictated by the - // standard, but we can ignore it. - explicit Table( - size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/, const Hash& h = Hash{}, - const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && noexcept(KeyEqual(equal))) - : WHash(h) - , WKeyEqual(equal) { - ROBIN_HOOD_TRACE(this) - } - - template - Table(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, - const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) - : WHash(h) - , WKeyEqual(equal) { - ROBIN_HOOD_TRACE(this) - insert(first, last); - } - - Table(std::initializer_list initlist, - size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{}, - const KeyEqual& equal = KeyEqual{}) - : WHash(h) - , WKeyEqual(equal) { - ROBIN_HOOD_TRACE(this) - insert(initlist.begin(), initlist.end()); - } - - Table(Table&& o) noexcept - : WHash(std::move(static_cast(o))) - , WKeyEqual(std::move(static_cast(o))) - , DataPool(std::move(static_cast(o))) { - ROBIN_HOOD_TRACE(this) - if (o.mMask) { - mHashMultiplier = std::move(o.mHashMultiplier); - mKeyVals = std::move(o.mKeyVals); - mInfo = std::move(o.mInfo); - mNumElements = std::move(o.mNumElements); - mMask = std::move(o.mMask); - mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); - mInfoInc = std::move(o.mInfoInc); - mInfoHashShift = std::move(o.mInfoHashShift); - // set other's mask to 0 so its destructor won't do anything - o.init(); - } - } - - Table& operator=(Table&& o) noexcept { - ROBIN_HOOD_TRACE(this) - if (&o != this) { - if (o.mMask) { - // only move stuff if the other map actually has some data - destroy(); - mHashMultiplier = std::move(o.mHashMultiplier); - mKeyVals = std::move(o.mKeyVals); - mInfo = std::move(o.mInfo); - mNumElements = std::move(o.mNumElements); - mMask = std::move(o.mMask); - mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); - mInfoInc = std::move(o.mInfoInc); - mInfoHashShift = std::move(o.mInfoHashShift); - WHash::operator=(std::move(static_cast(o))); - WKeyEqual::operator=(std::move(static_cast(o))); - DataPool::operator=(std::move(static_cast(o))); - - o.init(); - - } - else { - // nothing in the other map => just clear us. - clear(); - } - } - return *this; - } - - Table(const Table& o) - : WHash(static_cast(o)) - , WKeyEqual(static_cast(o)) - , DataPool(static_cast(o)) { - ROBIN_HOOD_TRACE(this) - if (!o.empty()) { - // not empty: create an exact copy. it is also possible to just iterate through all - // elements and insert them, but copying is probably faster. - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); - auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); - - ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" - << numElementsWithBuffer << ")") - mHashMultiplier = o.mHashMultiplier; - mKeyVals = static_cast( - detail::assertNotNull(std::malloc(numBytesTotal))); - // no need for calloc because clonData does memcpy - mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); - mNumElements = o.mNumElements; - mMask = o.mMask; - mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; - mInfoInc = o.mInfoInc; - mInfoHashShift = o.mInfoHashShift; - cloneData(o); - } - } - - // Creates a copy of the given map. Copy constructor of each entry is used. - // Not sure why clang-tidy thinks this doesn't handle self assignment, it does - // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) - Table& operator=(Table const& o) { - ROBIN_HOOD_TRACE(this) - if (&o == this) { - // prevent assigning of itself - return *this; - } - - // we keep using the old allocator and not assign the new one, because we want to keep - // the memory available. when it is the same size. - if (o.empty()) { - if (0 == mMask) { - // nothing to do, we are empty too - return *this; - } - - // not empty: destroy what we have there - // clear also resets mInfo to 0, that's sometimes not necessary. - destroy(); - init(); - WHash::operator=(static_cast(o)); - WKeyEqual::operator=(static_cast(o)); - DataPool::operator=(static_cast(o)); - - return *this; - } - - // clean up old stuff - Destroyer::value>{}.nodes(*this); - - if (mMask != o.mMask) { - // no luck: we don't have the same array size allocated, so we need to realloc. - if (0 != mMask) { - // only deallocate if we actually have data! - ROBIN_HOOD_LOG("std::free") - std::free(mKeyVals); - } - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); - auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); - ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" - << numElementsWithBuffer << ")") - mKeyVals = static_cast( - detail::assertNotNull(std::malloc(numBytesTotal))); - - // no need for calloc here because cloneData performs a memcpy. - mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); - // sentinel is set in cloneData - } - WHash::operator=(static_cast(o)); - WKeyEqual::operator=(static_cast(o)); - DataPool::operator=(static_cast(o)); - mHashMultiplier = o.mHashMultiplier; - mNumElements = o.mNumElements; - mMask = o.mMask; - mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; - mInfoInc = o.mInfoInc; - mInfoHashShift = o.mInfoHashShift; - cloneData(o); - - return *this; - } - - // Swaps everything between the two maps. - void swap(Table& o) { - ROBIN_HOOD_TRACE(this) - using std::swap; - swap(o, *this); - } - - // Clears all data, without resizing. - void clear() { - ROBIN_HOOD_TRACE(this) - if (empty()) { - // don't do anything! also important because we don't want to write to - // DummyInfoByte::b, even though we would just write 0 to it. - return; - } - - Destroyer::value>{}.nodes(*this); - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); - // clear everything, then set the sentinel again - uint8_t const z = 0; - std::fill(mInfo, mInfo + calcNumBytesInfo(numElementsWithBuffer), z); - mInfo[numElementsWithBuffer] = 1; - - mInfoInc = InitialInfoInc; - mInfoHashShift = InitialInfoHashShift; - } - - // Destroys the map and all it's contents. - ~Table() { - ROBIN_HOOD_TRACE(this) - destroy(); - } - - // Checks if both tables contain the same entries. Order is irrelevant. - bool operator==(const Table& other) const { - ROBIN_HOOD_TRACE(this) - if (other.size() != size()) { - return false; - } - for (auto const& otherEntry : other) { - if (!has(otherEntry)) { - return false; - } - } - - return true; - } - - bool operator!=(const Table& other) const { - ROBIN_HOOD_TRACE(this) - return !operator==(other); - } - - template - typename std::enable_if::value, Q&>::type operator[](const key_type& key) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) - Node(*this, std::piecewise_construct, std::forward_as_tuple(key), - std::forward_as_tuple()); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, - std::forward_as_tuple(key), std::forward_as_tuple()); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - } - - return mKeyVals[idxAndState.first].getSecond(); - } - - template - typename std::enable_if::value, Q&>::type operator[](key_type&& key) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) - Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), - std::forward_as_tuple()); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = - Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), - std::forward_as_tuple()); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - } - - return mKeyVals[idxAndState.first].getSecond(); - } - - template - void insert(Iter first, Iter last) { - for (; first != last; ++first) { - // value_type ctor needed because this might be called with std::pair's - insert(value_type(*first)); - } - } - - void insert(std::initializer_list ilist) { - for (auto&& vt : ilist) { - insert(std::move(vt)); - } - } - - template - std::pair emplace(Args&&... args) { - ROBIN_HOOD_TRACE(this) - Node n { - *this, std::forward(args)... - }; - auto idxAndState = insertKeyPrepareEmptySpot(getFirstConst(n)); - switch (idxAndState.second) { - case InsertionState::key_found: - n.destroy(*this); - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) Node(*this, std::move(n)); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = std::move(n); - break; - - case InsertionState::overflow_error: - n.destroy(*this); - throwOverflowError(); - break; - } - - return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), - InsertionState::key_found != idxAndState.second); - } - - template - iterator emplace_hint(const_iterator position, Args&&... args) { - (void)position; - return emplace(std::forward(args)...).first; - } - - template - std::pair try_emplace(const key_type& key, Args&&... args) { - return try_emplace_impl(key, std::forward(args)...); - } - - template - std::pair try_emplace(key_type&& key, Args&&... args) { - return try_emplace_impl(std::move(key), std::forward(args)...); - } - - template - iterator try_emplace(const_iterator hint, const key_type& key, Args&&... args) { - (void)hint; - return try_emplace_impl(key, std::forward(args)...).first; - } - - template - iterator try_emplace(const_iterator hint, key_type&& key, Args&&... args) { - (void)hint; - return try_emplace_impl(std::move(key), std::forward(args)...).first; - } - - template - std::pair insert_or_assign(const key_type& key, Mapped&& obj) { - return insertOrAssignImpl(key, std::forward(obj)); - } - - template - std::pair insert_or_assign(key_type&& key, Mapped&& obj) { - return insertOrAssignImpl(std::move(key), std::forward(obj)); - } - - template - iterator insert_or_assign(const_iterator hint, const key_type& key, Mapped&& obj) { - (void)hint; - return insertOrAssignImpl(key, std::forward(obj)).first; - } - - template - iterator insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) { - (void)hint; - return insertOrAssignImpl(std::move(key), std::forward(obj)).first; - } - - std::pair insert(const value_type& keyval) { - ROBIN_HOOD_TRACE(this) - return emplace(keyval); - } - - iterator insert(const_iterator hint, const value_type& keyval) { - (void)hint; - return emplace(keyval).first; - } - - std::pair insert(value_type&& keyval) { - return emplace(std::move(keyval)); - } - - iterator insert(const_iterator hint, value_type&& keyval) { - (void)hint; - return emplace(std::move(keyval)).first; - } - - // Returns 1 if key is found, 0 otherwise. - size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { - return 1; - } - return 0; - } - - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::type count(const OtherKey& key) const { - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { - return 1; - } - return 0; - } - - bool contains(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - return 1U == count(key); - } - - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::type contains(const OtherKey& key) const { - return 1U == count(key); - } - - // Returns a reference to the value found for key. - // Throws std::out_of_range if element cannot be found - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::value, Q&>::type at(key_type const& key) { - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { - doThrow("key not found"); - } - return kv->getSecond(); - } - - // Returns a reference to the value found for key. - // Throws std::out_of_range if element cannot be found - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::value, Q const&>::type at(key_type const& key) const { - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { - doThrow("key not found"); - } - return kv->getSecond(); - } - - const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return const_iterator{ mKeyVals + idx, mInfo + idx }; - } - - template - const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return const_iterator{ mKeyVals + idx, mInfo + idx }; - } - - template - typename std::enable_if::type // NOLINT(modernize-use-nodiscard) - find(const OtherKey& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return const_iterator{ mKeyVals + idx, mInfo + idx }; - } - - iterator find(const key_type& key) { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return iterator{ mKeyVals + idx, mInfo + idx }; - } - - template - iterator find(const OtherKey& key, is_transparent_tag /*unused*/) { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return iterator{ mKeyVals + idx, mInfo + idx }; - } - - template - typename std::enable_if::type find(const OtherKey& key) { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return iterator{ mKeyVals + idx, mInfo + idx }; - } - - iterator begin() { - ROBIN_HOOD_TRACE(this) - if (empty()) { - return end(); - } - return iterator(mKeyVals, mInfo, fast_forward_tag{}); - } - const_iterator begin() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return cbegin(); - } - const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - if (empty()) { - return cend(); - } - return const_iterator(mKeyVals, mInfo, fast_forward_tag{}); - } - - iterator end() { - ROBIN_HOOD_TRACE(this) - // no need to supply valid info pointer: end() must not be dereferenced, and only node - // pointer is compared. - return iterator{ reinterpret_cast_no_cast_align_warning(mInfo), nullptr }; - } - const_iterator end() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return cend(); - } - const_iterator cend() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return const_iterator{ reinterpret_cast_no_cast_align_warning(mInfo), nullptr }; - } - - iterator erase(const_iterator pos) { - ROBIN_HOOD_TRACE(this) - // its safe to perform const cast here - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) - return erase(iterator{ const_cast(pos.mKeyVals), const_cast(pos.mInfo) }); - } - - // Erases element at pos, returns iterator to the next element. - iterator erase(iterator pos) { - ROBIN_HOOD_TRACE(this) - // we assume that pos always points to a valid entry, and not end(). - auto const idx = static_cast(pos.mKeyVals - mKeyVals); - - shiftDown(idx); - --mNumElements; - - if (*pos.mInfo) { - // we've backward shifted, return this again - return pos; - } - - // no backward shift, return next element - return ++pos; - } - - size_t erase(const key_type& key) { - ROBIN_HOOD_TRACE(this) - size_t idx {}; - InfoType info{}; - keyToIdx(key, &idx, &info); - - // check while info matches with the source idx - do { - if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { - shiftDown(idx); - --mNumElements; - return 1; - } - next(&info, &idx); - } while (info <= mInfo[idx]); - - // nothing found to delete - return 0; - } - - // reserves space for the specified number of elements. Makes sure the old data fits. - // exactly the same as reserve(c). - void rehash(size_t c) { - // forces a reserve - reserve(c, true); - } - - // reserves space for the specified number of elements. Makes sure the old data fits. - // Exactly the same as rehash(c). Use rehash(0) to shrink to fit. - void reserve(size_t c) { - // reserve, but don't force rehash - reserve(c, false); - } - - // If possible reallocates the map to a smaller one. This frees the underlying table. - // Does not do anything if load_factor is too large for decreasing the table's size. - void compact() { - ROBIN_HOOD_TRACE(this) - auto newSize = InitialNumElements; - while (calcMaxNumElementsAllowed(newSize) < mNumElements && newSize != 0) { - newSize *= 2; - } - if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { - throwOverflowError(); - } - - ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") - - // only actually do anything when the new size is bigger than the old one. This prevents to - // continuously allocate for each reserve() call. - if (newSize < mMask + 1) { - rehashPowerOfTwo(newSize, true); - } - } - - size_type size() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return mNumElements; - } - - size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return static_cast(-1); - } - - ROBIN_HOOD(NODISCARD) bool empty() const noexcept { - ROBIN_HOOD_TRACE(this) - return 0 == mNumElements; - } - - float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return MaxLoadFactor100 / 100.0F; - } - - // Average number of elements per bucket. Since we allow only 1 per bucket - float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return static_cast(size()) / static_cast(mMask + 1); - } - - ROBIN_HOOD(NODISCARD) size_t mask() const noexcept { - ROBIN_HOOD_TRACE(this) - return mMask; - } - - ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept { - if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits::max)() / 100)) { - return maxElements * MaxLoadFactor100 / 100; - } - - // we might be a bit inprecise, but since maxElements is quite large that doesn't matter - return (maxElements / 100) * MaxLoadFactor100; - } - - ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const noexcept { - // we add a uint64_t, which houses the sentinel (first byte) and padding so we can load - // 64bit types. - return numElements + sizeof(uint64_t); - } - - ROBIN_HOOD(NODISCARD) - size_t calcNumElementsWithBuffer(size_t numElements) const noexcept { - auto maxNumElementsAllowed = calcMaxNumElementsAllowed(numElements); - return numElements + (std::min)(maxNumElementsAllowed, (static_cast(0xFF))); - } - - // calculation only allowed for 2^n values - ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const { -#if ROBIN_HOOD(BITNESS) == 64 - return numElements * sizeof(Node) + calcNumBytesInfo(numElements); -#else - // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows. - auto const ne = static_cast(numElements); - auto const s = static_cast(sizeof(Node)); - auto const infos = static_cast(calcNumBytesInfo(numElements)); - - auto const total64 = ne * s + infos; - auto const total = static_cast(total64); - - if (ROBIN_HOOD_UNLIKELY(static_cast(total) != total64)) { - throwOverflowError(); - } - return total; -#endif - } - - private: - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::value, bool>::type has(const value_type& e) const { - ROBIN_HOOD_TRACE(this) - auto it = find(e.first); - return it != end() && it->second == e.second; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::value, bool>::type has(const value_type& e) const { - ROBIN_HOOD_TRACE(this) - return find(e) != end(); - } - - void reserve(size_t c, bool forceRehash) { - ROBIN_HOOD_TRACE(this) - auto const minElementsAllowed = (std::max)(c, mNumElements); - auto newSize = InitialNumElements; - while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) { - newSize *= 2; - } - if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { - throwOverflowError(); - } - - ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") - - // only actually do anything when the new size is bigger than the old one. This prevents to - // continuously allocate for each reserve() call. - if (forceRehash || newSize > mMask + 1) { - rehashPowerOfTwo(newSize, false); - } - } - - // reserves space for at least the specified number of elements. - // only works if numBuckets if power of two - // True on success, false otherwise - void rehashPowerOfTwo(size_t numBuckets, bool forceFree) { - ROBIN_HOOD_TRACE(this) - - Node* const oldKeyVals = mKeyVals; - uint8_t const* const oldInfo = mInfo; - - const size_t oldMaxElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); - - // resize operation: move stuff - initData(numBuckets); - if (oldMaxElementsWithBuffer > 1) { - for (size_t i = 0; i < oldMaxElementsWithBuffer; ++i) { - if (oldInfo[i] != 0) { - // might throw an exception, which is really bad since we are in the middle of - // moving stuff. - insert_move(std::move(oldKeyVals[i])); - // destroy the node but DON'T destroy the data. - oldKeyVals[i].~Node(); - } - } - - // this check is not necessary as it's guarded by the previous if, but it helps - // silence g++'s overeager "attempt to free a non-heap object 'map' - // [-Werror=free-nonheap-object]" warning. - if (oldKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { - // don't destroy old data: put it into the pool instead - if (forceFree) { - std::free(oldKeyVals); - } - else { - DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer)); - } - } - } - } - - ROBIN_HOOD(NOINLINE) void throwOverflowError() const { -#if ROBIN_HOOD(HAS_EXCEPTIONS) - throw std::overflow_error("robin_hood::map overflow"); -#else - abort(); -#endif - } - - template - std::pair try_emplace_impl(OtherKey&& key, Args&&... args) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) Node( - *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(args)...)); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(args)...)); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - break; - } - - return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), - InsertionState::key_found != idxAndState.second); - } - - template - std::pair insertOrAssignImpl(OtherKey&& key, Mapped&& obj) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - mKeyVals[idxAndState.first].getSecond() = std::forward(obj); - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) Node( - *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(obj))); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(obj))); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - break; - } - - return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), - InsertionState::key_found != idxAndState.second); - } - - void initData(size_t max_elements) { - mNumElements = 0; - mMask = max_elements - 1; - mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements); - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements); - - // malloc & zero mInfo. Faster than calloc everything. - auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); - ROBIN_HOOD_LOG("std::calloc " << numBytesTotal << " = calcNumBytesTotal(" - << numElementsWithBuffer << ")") - mKeyVals = reinterpret_cast( - detail::assertNotNull(std::malloc(numBytesTotal))); - mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); - std::memset(mInfo, 0, numBytesTotal - numElementsWithBuffer * sizeof(Node)); - - // set sentinel - mInfo[numElementsWithBuffer] = 1; - - mInfoInc = InitialInfoInc; - mInfoHashShift = InitialInfoHashShift; - } - - enum class InsertionState { overflow_error, key_found, new_node, overwrite_node }; - - // Finds key, and if not already present prepares a spot where to pot the key & value. - // This potentially shifts nodes out of the way, updates mInfo and number of inserted - // elements, so the only operation left to do is create/assign a new node at that spot. - template - std::pair insertKeyPrepareEmptySpot(OtherKey&& key) { - for (int i = 0; i < 256; ++i) { - size_t idx{}; - InfoType info{}; - keyToIdx(key, &idx, &info); - nextWhileLess(&info, &idx); - - // while we potentially have a match - while (info == mInfo[idx]) { - if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { - // key already exists, do NOT insert. - // see http://en.cppreference.com/w/cpp/container/unordered_map/insert - return std::make_pair(idx, InsertionState::key_found); - } - next(&info, &idx); - } - - // unlikely that this evaluates to true - if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { - if (!increase_size()) { - return std::make_pair(size_t(0), InsertionState::overflow_error); - } - continue; - } - - // key not found, so we are now exactly where we want to insert it. - auto const insertion_idx = idx; - auto const insertion_info = info; - if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { - mMaxNumElementsAllowed = 0; - } - - // find an empty spot - while (0 != mInfo[idx]) { - next(&info, &idx); - } - - if (idx != insertion_idx) { - shiftUp(idx, insertion_idx); - } - // put at empty spot - mInfo[insertion_idx] = static_cast(insertion_info); - ++mNumElements; - return std::make_pair(insertion_idx, idx == insertion_idx - ? InsertionState::new_node - : InsertionState::overwrite_node); - } - - // enough attempts failed, so finally give up. - return std::make_pair(size_t(0), InsertionState::overflow_error); - } - - bool try_increase_info() { - ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements - << ", maxNumElementsAllowed=" - << calcMaxNumElementsAllowed(mMask + 1)) - if (mInfoInc <= 2) { - // need to be > 2 so that shift works (otherwise undefined behavior!) - return false; - } - // we got space left, try to make info smaller - mInfoInc = static_cast(mInfoInc >> 1U); - - // remove one bit of the hash, leaving more space for the distance info. - // This is extremely fast because we can operate on 8 bytes at once. - ++mInfoHashShift; - auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); - - for (size_t i = 0; i < numElementsWithBuffer; i += 8) { - auto val = unaligned_load(mInfo + i); - val = (val >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f); - std::memcpy(mInfo + i, &val, sizeof(val)); - } - // update sentinel, which might have been cleared out! - mInfo[numElementsWithBuffer] = 1; - - mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); - return true; - } - - // True if resize was possible, false otherwise - bool increase_size() { - // nothing allocated yet? just allocate InitialNumElements - if (0 == mMask) { - initData(InitialNumElements); - return true; - } - - auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); - if (mNumElements < maxNumElementsAllowed && try_increase_info()) { - return true; - } - - ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed=" - << maxNumElementsAllowed << ", load=" - << (static_cast(mNumElements) * 100.0 / - (static_cast(mMask) + 1))) - - if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) { - // we have to resize, even though there would still be plenty of space left! - // Try to rehash instead. Delete freed memory so we don't steadyily increase mem in case - // we have to rehash a few times - nextHashMultiplier(); - rehashPowerOfTwo(mMask + 1, true); - } - else { - // we've reached the capacity of the map, so the hash seems to work nice. Keep using it. - rehashPowerOfTwo((mMask + 1) * 2, false); - } - return true; - } - - void nextHashMultiplier() { - // adding an *even* number, so that the multiplier will always stay odd. This is necessary - // so that the hash stays a mixing function (and thus doesn't have any information loss). - mHashMultiplier += UINT64_C(0xc4ceb9fe1a85ec54); - } - - void destroy() { - if (0 == mMask) { - // don't deallocate! - return; - } - - Destroyer::value>{} - .nodesDoNotDeallocate(*this); - - // This protection against not deleting mMask shouldn't be needed as it's sufficiently - // protected with the 0==mMask check, but I have this anyways because g++ 7 otherwise - // reports a compile error: attempt to free a non-heap object 'fm' - // [-Werror=free-nonheap-object] - if (mKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { - ROBIN_HOOD_LOG("std::free") - std::free(mKeyVals); - } - } - - void init() noexcept { - mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); - mInfo = reinterpret_cast(&mMask); - mNumElements = 0; - mMask = 0; - mMaxNumElementsAllowed = 0; - mInfoInc = InitialInfoInc; - mInfoHashShift = InitialInfoHashShift; - } - - // members are sorted so no padding occurs - uint64_t mHashMultiplier = UINT64_C(0xc4ceb9fe1a85ec53); // 8 byte 8 - Node* mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); // 8 byte 16 - uint8_t* mInfo = reinterpret_cast(&mMask); // 8 byte 24 - size_t mNumElements = 0; // 8 byte 32 - size_t mMask = 0; // 8 byte 40 - size_t mMaxNumElementsAllowed = 0; // 8 byte 48 - InfoType mInfoInc = InitialInfoInc; // 4 byte 52 - InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 56 - // 16 byte 56 if NodeAllocator - }; - - } // namespace detail - - // map - - template , - typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> - using unordered_flat_map = detail::Table; - - template , - typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> - using unordered_node_map = detail::Table; - - template , - typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> - using unordered_map = - detail::Table) <= sizeof(size_t) * 6 && - std::is_nothrow_move_constructible>::value && - std::is_nothrow_move_assignable>::value, - MaxLoadFactor100, Key, T, Hash, KeyEqual>; - - // set - - template , typename KeyEqual = std::equal_to, - size_t MaxLoadFactor100 = 80> - using unordered_flat_set = detail::Table; - - template , typename KeyEqual = std::equal_to, - size_t MaxLoadFactor100 = 80> - using unordered_node_set = detail::Table; - - template , typename KeyEqual = std::equal_to, - size_t MaxLoadFactor100 = 80> - using unordered_set = detail::Table::value && - std::is_nothrow_move_assignable::value, - MaxLoadFactor100, Key, void, Hash, KeyEqual>; - -} // namespace robin_hood - -#endif diff --git a/Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj b/Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj index 5625f0ae..b4b313fa 100644 --- a/Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj +++ b/Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj @@ -37,7 +37,7 @@ DynamicLibrary false - v143 + v141 true Unicode false @@ -176,6 +176,7 @@ $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;%(PreprocessorDefinitions) true + Speed Windows @@ -192,6 +193,7 @@ + diff --git a/Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj.filters b/Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj.filters index 0f18c73d..dc489c45 100644 --- a/Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj.filters +++ b/Profiler_Cpp_Test/Profiler_Cpp_Test.vcxproj.filters @@ -24,5 +24,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/Profiler_Cpp_Test/tests/FunctionIDSetTest.cpp b/Profiler_Cpp_Test/tests/FunctionIDSetTest.cpp new file mode 100644 index 00000000..ef59284d --- /dev/null +++ b/Profiler_Cpp_Test/tests/FunctionIDSetTest.cpp @@ -0,0 +1,35 @@ +#include +#include "CppUnitTest.h" +#include +#include +#include +#include "utils/functionID_set/functionID_set.h" +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +TEST_CLASS(FunctionIDSetTest) +{ +public: + TEST_METHOD(SetPerformanceTest) + { + functionID_set testSet; + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + for (unsigned int i = 0; i < 100'000'000; i++) { + testSet.insert(std::rand()); + } + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + std::string message = "Time difference = " + std::to_string(std::chrono::duration_cast(end - begin).count()) + "[mikrosekunden]"; + Logger::WriteMessage(message.c_str()); + + std::set testSet2; + std::chrono::steady_clock::time_point begin2 = std::chrono::steady_clock::now(); + for (unsigned int i = 0; i < 100'000'000; i++) { + testSet2.insert(std::rand()); + } + std::chrono::steady_clock::time_point end2 = std::chrono::steady_clock::now(); + std::string message2 = "Time difference = " + std::to_string(std::chrono::duration_cast(end2 - begin2).count()) + "[mikrosekunden]"; + Logger::WriteMessage(message2.c_str()); + } +}; From b3d64f212ed9c85d5e31a82d7afbb78e10ebf7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Wed, 1 Jun 2022 15:57:55 +0200 Subject: [PATCH 110/174] Fix minor issue --- Profiler_Test/Tia/TiaProfilerTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Profiler_Test/Tia/TiaProfilerTest.cs b/Profiler_Test/Tia/TiaProfilerTest.cs index 40c21a89..9dbcec47 100644 --- a/Profiler_Test/Tia/TiaProfilerTest.cs +++ b/Profiler_Test/Tia/TiaProfilerTest.cs @@ -136,13 +136,13 @@ public void NoIpcRunning() profilerIpc = null; } + [Ignore("TODO: No idea why this is not working... manual tests have no problem with this.")] [Test] public void IpcStartedAfterStartup() { RecordingProfilerIpc oldProfilerIpc = profilerIpc; - //oldProfilerIpc.StartTest("should not be triggered"); - //oldProfilerIpc.EndTest(ETestExecutionResult.PASSED, ""); - //oldProfilerIpc.Dispose(); + oldProfilerIpc.StartTest("should not be triggered"); + oldProfilerIpc.EndTest(TestExecutionResult.Passed, ""); profilerIpc.Dispose(); TesteeProcess testeeProcess = Start(testee, profilerUnderTest); @@ -175,7 +175,7 @@ private static void RunTestCase(string testCaseName, TesteeProcess process, Reco Assert.That(process.Output.ReadLine(), Is.EqualTo(testCaseName)); Thread.Sleep(TimeSpan.FromMilliseconds(20)); // wait shortly - profilerIpc.EndTest(ETestExecutionResult.PASSED); + profilerIpc.EndTest(TestExecutionResult.Passed); Thread.Sleep(TimeSpan.FromMilliseconds(20)); // wait shortly } From 122a357ee80c2c464b5656cfde021865225686c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Tue, 7 Jun 2022 17:38:41 +0200 Subject: [PATCH 111/174] Rework --- Profiler/CProfilerWorker.cpp | 7 +-- Profiler/CProfilerWorker.h | 3 +- Profiler/Profiler.vcxproj | 3 + Profiler/Profiler.vcxproj.filters | 3 + Profiler/utils/atomic_queue/LICENSE | 21 +++++++ .../utils/functionID_set/functionId_set.h | 62 ++++++++++--------- 6 files changed, 65 insertions(+), 34 deletions(-) create mode 100644 Profiler/utils/atomic_queue/LICENSE diff --git a/Profiler/CProfilerWorker.cpp b/Profiler/CProfilerWorker.cpp index 211fd295..4f27dc03 100644 --- a/Profiler/CProfilerWorker.cpp +++ b/Profiler/CProfilerWorker.cpp @@ -6,7 +6,7 @@ CProfilerWorker::CProfilerWorker(Config* config, TraceLog* traceLog, functionID_ this->methodSetSynchronization = methodSetSynchronization; setCriticalSection(methodSetSynchronization); setCalledMethodsSet(calledMethodIds); - setMethodIdQueue(methodIdQueue); + setMethodIdQueue(&methodIdQueue); this->workerThread = new std::thread(&CProfilerWorker::methodIdThreadLoop, this); } @@ -15,12 +15,11 @@ CProfilerWorker::~CProfilerWorker() { if (this->workerThread->joinable()) { this->workerThread->join(); } - delete methodIdQueue; } void CProfilerWorker::methodIdThreadLoop() { while (!this->shutdown) { - if (methodIdQueue->was_empty()) { + if (methodIdQueue.was_empty()) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } EnterCriticalSection(methodSetSynchronization); @@ -31,7 +30,7 @@ void CProfilerWorker::methodIdThreadLoop() { void CProfilerWorker::transferMethodIds() { FunctionID i; - while (methodIdQueue->try_pop(i)) { + while (methodIdQueue.try_pop(i)) { calledMethodIds->insert(i); } } diff --git a/Profiler/CProfilerWorker.h b/Profiler/CProfilerWorker.h index bb4d91a0..7cca516a 100644 --- a/Profiler/CProfilerWorker.h +++ b/Profiler/CProfilerWorker.h @@ -19,9 +19,10 @@ class CProfilerWorker TraceLog* traceLog = NULL; bool shutdown = false; + // Warning from the use of alignas in the Atomic Queue. #pragma warning( disable : 4316) functionID_set* calledMethodIds; - Queue* methodIdQueue = new Queue(65'536); + Queue methodIdQueue = Queue(65'536); CRITICAL_SECTION* methodSetSynchronization; diff --git a/Profiler/Profiler.vcxproj b/Profiler/Profiler.vcxproj index afcadc99..c0ce4092 100644 --- a/Profiler/Profiler.vcxproj +++ b/Profiler/Profiler.vcxproj @@ -325,6 +325,9 @@ + + + diff --git a/Profiler/Profiler.vcxproj.filters b/Profiler/Profiler.vcxproj.filters index 8b719805..56013e99 100644 --- a/Profiler/Profiler.vcxproj.filters +++ b/Profiler/Profiler.vcxproj.filters @@ -309,4 +309,7 @@ Resource Files + + + \ No newline at end of file diff --git a/Profiler/utils/atomic_queue/LICENSE b/Profiler/utils/atomic_queue/LICENSE new file mode 100644 index 00000000..04b5fb17 --- /dev/null +++ b/Profiler/utils/atomic_queue/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Maxim Egorushkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Profiler/utils/functionID_set/functionId_set.h b/Profiler/utils/functionID_set/functionId_set.h index fd9e8d1a..d807a441 100644 --- a/Profiler/utils/functionID_set/functionId_set.h +++ b/Profiler/utils/functionID_set/functionId_set.h @@ -10,26 +10,29 @@ class functionID_set { private: const unsigned int default_size = 2'097'152; - static const unsigned int rotation_mask = (CHAR_BIT * sizeof(default_size) - 1); + static const unsigned int rotation_mask = (-1) & (CHAR_BIT * sizeof(default_size) - 1); unsigned int current_size = default_size; unsigned int num_elements = 0; unsigned int max_elements = default_size / 2; + unsigned int modulo_mask = current_size - 1; FunctionID* set = new FunctionID[default_size]{ 0 }; // Predetermined values for xor operation to place in different spots unsigned int xor_values[10] = { 2134038170, 2362107340, 3229546752, 1302939050, 200405764, 79516981, 2052331209, 3415124361, 940592490, 430981309 }; + const unsigned int num_xor_values = sizeof(xor_values) / sizeof(FunctionID); /// /// Updates the size of the array and reinserts the element into the new array. /// void adjust_size() { + max_elements = current_size; current_size *= 2; - max_elements = current_size / 2; + modulo_mask = current_size - 1; FunctionID* old_set = set; set = new FunctionID[current_size]{ 0 }; - for (unsigned int i = 0; i < current_size / 2; i++) { + for (unsigned int i = 0; i < max_elements; i++) { if (old_set[i] != 0) { insert(old_set[i]); } @@ -40,8 +43,19 @@ class functionID_set /// /// Circular Shift/Rotate integer by one to the right. /// - static inline FunctionID rotr32(FunctionID n) { - return (n >> 1) | (n << ((-1) & rotation_mask)); + static inline FunctionID rotr(FunctionID n) { + return (n >> 1) | (n << rotation_mask); + } + + inline bool tryInsert(unsigned int position, const FunctionID f) { + if (set[position] == f) { + return true; + } + if (set[position] == 0) { + set[position] = f; + return true; + } + return false; } public: @@ -80,7 +94,7 @@ class functionID_set /// bool contains(FunctionID f) { // Check the number modulo the size of the set first - unsigned int position = f & (current_size - 1); + unsigned int position = f & modulo_mask; if (set[position] == 0) { return false; } @@ -89,8 +103,8 @@ class functionID_set } // Then rotate bits and xor to try and find a new position - for (int i = 0; i < 10; i++) { - position = (rotr32(f) ^ xor_values[0]) & (current_size - 1); + for (unsigned int i = 0; i < num_xor_values; i++) { + position = (rotr(f) ^ xor_values[i]) & modulo_mask; if (set[position] == 0) { return false; } @@ -100,16 +114,18 @@ class functionID_set } // If no position was found, just go to the next position with +1 - position = (f + 1) & (current_size - 1); + position = (f + 1) & modulo_mask; while (set[position] != f) { if (set[position] == 0) { return false; } - position = (position + 1) & (current_size - 1); + position = (position + 1) & modulo_mask; } return set[position]; } + + /// /// Inserts FunctionID f into the set. /// @@ -118,32 +134,20 @@ class functionID_set if (num_elements > max_elements) { adjust_size(); } - unsigned int position = f & (current_size - 1); - if (set[position] == f) { - return; - } - if (set[position] == 0) { - set[position] = f; - return; - } + unsigned int position = f & modulo_mask; + if (tryInsert(position, f)) return; - for (int i = 0; i < 10; i++) { - position = (f ^ xor_values[0]) & (current_size - 1); - if (set[position] == f) { - return; - } - if (set[position] == 0) { - set[position] = f; - return; - } + for (unsigned int i = 0; i < num_xor_values; i++) { + position = (rotr(f) ^ xor_values[i]) & modulo_mask; + if (tryInsert(position, f)) return; } - position = (f + 1) & (current_size - 1); + position = (f + 1) & modulo_mask; while (set[position] != 0) { if (set[position] == f) { return; } - position = (position + 1) & (current_size - 1); + position = (position + 1) & modulo_mask; } set[position] = f; } From 3e0225eee5e4e22c1092ec6f601b60b6e70474a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Mon, 4 Jul 2022 12:07:44 +0200 Subject: [PATCH 112/174] Fix issue in functionId_set --- .../utils/functionID_set/functionId_set.h | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Profiler/utils/functionID_set/functionId_set.h b/Profiler/utils/functionID_set/functionId_set.h index d807a441..60fbe43b 100644 --- a/Profiler/utils/functionID_set/functionId_set.h +++ b/Profiler/utils/functionID_set/functionId_set.h @@ -10,7 +10,7 @@ class functionID_set { private: const unsigned int default_size = 2'097'152; - static const unsigned int rotation_mask = (-1) & (CHAR_BIT * sizeof(default_size) - 1); + static const FunctionID rotation_mask = (-1) & (CHAR_BIT * sizeof(FunctionID) - 1); unsigned int current_size = default_size; unsigned int num_elements = 0; @@ -18,9 +18,15 @@ class functionID_set unsigned int modulo_mask = current_size - 1; FunctionID* set = new FunctionID[default_size]{ 0 }; + bool resizing = false; + // Predetermined values for xor operation to place in different spots - unsigned int xor_values[10] = { 2134038170, 2362107340, 3229546752, 1302939050, 200405764, 79516981, 2052331209, 3415124361, 940592490, 430981309 }; +#ifdef _WIN64 + FunctionID xor_values[10] = { 14653048717414601650, 10827059576848633699, 18275351206681430557, 15388157215360276699, 5625538261940547542, 17184451615065543950, 12483508915876842786, 7009288139810362683, 8675975670288616007, 11886397353506492918 }; +#else + FunctionID xor_values[10] = { 987205454, 278240680, 4130902882, 445831414, 3235577889, 1789497761, 205336377, 2382455698, 1977849072, 966072234 }; +#endif const unsigned int num_xor_values = sizeof(xor_values) / sizeof(FunctionID); /// @@ -30,6 +36,7 @@ class functionID_set max_elements = current_size; current_size *= 2; modulo_mask = current_size - 1; + num_elements = 0; FunctionID* old_set = set; set = new FunctionID[current_size]{ 0 }; for (unsigned int i = 0; i < max_elements; i++) { @@ -43,8 +50,8 @@ class functionID_set /// /// Circular Shift/Rotate integer by one to the right. /// - static inline FunctionID rotr(FunctionID n) { - return (n >> 1) | (n << rotation_mask); + static inline FunctionID rotr(FunctionID f) { + return (f >> 1) | (f << rotation_mask); } inline bool tryInsert(unsigned int position, const FunctionID f) { @@ -52,6 +59,11 @@ class functionID_set return true; } if (set[position] == 0) { + num_elements++; + if (num_elements > max_elements) { + adjust_size(); + } + set[position] = f; return true; } @@ -130,25 +142,20 @@ class functionID_set /// Inserts FunctionID f into the set. /// void insert(FunctionID f) { - num_elements++; - if (num_elements > max_elements) { - adjust_size(); - } + // Try insertion at the number modulo the size of the set first unsigned int position = f & modulo_mask; if (tryInsert(position, f)) return; + // Then rotate bits and xor to try and find a new position for (unsigned int i = 0; i < num_xor_values; i++) { position = (rotr(f) ^ xor_values[i]) & modulo_mask; if (tryInsert(position, f)) return; } + // If no position was found, just go to the next position with +1 position = (f + 1) & modulo_mask; - while (set[position] != 0) { - if (set[position] == f) { - return; - } + while (!tryInsert(position, f)) { position = (position + 1) & modulo_mask; } - set[position] = f; } }; From b09bc743ec5d3afdc742222aae53a2e5d9a59ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmann?= Date: Sat, 9 Jul 2022 00:18:00 +0200 Subject: [PATCH 113/174] update bummer/update output path --- Commander.Cli/Commander.Cli.csproj | 3 ++- Commander.Server/Commander.Server.csproj | 1 + UploadDaemon/packages.config | 2 +- UploadDaemon_Test/packages.config | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Commander.Cli/Commander.Cli.csproj b/Commander.Cli/Commander.Cli.csproj index 5e5ecf74..f99fee58 100644 --- a/Commander.Cli/Commander.Cli.csproj +++ b/Commander.Cli/Commander.Cli.csproj @@ -1,10 +1,11 @@ - + Exe net6.0 enable enable + ..\Profiler\bin\Release\Commander\Cli diff --git a/Commander.Server/Commander.Server.csproj b/Commander.Server/Commander.Server.csproj index 180c30a1..7ff2b40a 100644 --- a/Commander.Server/Commander.Server.csproj +++ b/Commander.Server/Commander.Server.csproj @@ -5,6 +5,7 @@ enable enable Cqse.Teamscale.Profiler.Commander.Server + ..\Profiler\bin\Release\Commander\Server diff --git a/UploadDaemon/packages.config b/UploadDaemon/packages.config index 545c7b61..0069fa23 100644 --- a/UploadDaemon/packages.config +++ b/UploadDaemon/packages.config @@ -1,6 +1,6 @@  - + diff --git a/UploadDaemon_Test/packages.config b/UploadDaemon_Test/packages.config index 66483c0d..b568855f 100644 --- a/UploadDaemon_Test/packages.config +++ b/UploadDaemon_Test/packages.config @@ -1,7 +1,7 @@  - + From 04d198095d5188f9d48eb104850cdfe077652d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmann?= Date: Thu, 14 Jul 2022 08:00:59 +0200 Subject: [PATCH 114/174] bummer update --- UploadDaemon/UploadDaemon.csproj | 22 +++++++++++++++++++--- UploadDaemon/packages.config | 7 ++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/UploadDaemon/UploadDaemon.csproj b/UploadDaemon/UploadDaemon.csproj index 0bc545f1..f49cf441 100644 --- a/UploadDaemon/UploadDaemon.csproj +++ b/UploadDaemon/UploadDaemon.csproj @@ -37,8 +37,8 @@ - - ..\packages\Cqse.Bummer.1.0.2\lib\netstandard2.0\bummer.dll + + ..\packages\Cqse.Bummer.1.0.3\lib\netstandard2.0\bummer.dll ..\packages\Microsoft.Azure.Storage.Common.9.4.0\lib\net452\Microsoft.Azure.Storage.Common.dll @@ -65,14 +65,30 @@ ..\packages\NLog.4.5.11\lib\net45\NLog.dll + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + ..\packages\System.Collections.Immutable.5.0.0\lib\net461\System.Collections.Immutable.dll + ..\packages\System.IO.Abstractions.3.0.10\lib\net40\System.IO.Abstractions.dll + + ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Reflection.Metadata.5.0.0\lib\net461\System.Reflection.Metadata.dll + - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll diff --git a/UploadDaemon/packages.config b/UploadDaemon/packages.config index 0069fa23..66f131e1 100644 --- a/UploadDaemon/packages.config +++ b/UploadDaemon/packages.config @@ -7,8 +7,13 @@ + + - + + + + From 6aeea2cfd587841be0ec105b42bf0efedad03635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmann?= Date: Thu, 14 Jul 2022 08:03:34 +0200 Subject: [PATCH 115/174] fix tests --- .../Report/TestwiseCoverageReportTest.cs | 14 +++++++------- UploadDaemon_Test/Scanning/TraceFileTest.cs | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/UploadDaemon_Test/Report/TestwiseCoverageReportTest.cs b/UploadDaemon_Test/Report/TestwiseCoverageReportTest.cs index c2936957..30034fb4 100644 --- a/UploadDaemon_Test/Report/TestwiseCoverageReportTest.cs +++ b/UploadDaemon_Test/Report/TestwiseCoverageReportTest.cs @@ -15,7 +15,7 @@ public void MergesDifferentTests() TestwiseCoverageReport mergedReport = report1.Union(report2) as TestwiseCoverageReport; - Assert.That(mergedReport.Tests, Has.Count.EqualTo(2)); + Assert.That(mergedReport.Tests, Has.Length.EqualTo(2)); Assert.That(mergedReport.Tests[0].UniformPath, Is.EqualTo("Test1")); Assert.That(mergedReport.Tests[1].UniformPath, Is.EqualTo("Test2")); } @@ -29,12 +29,12 @@ public void MergesSameTestsRuntime() TestwiseCoverageReport mergedReport = report1.Union(report2) as TestwiseCoverageReport; - Assert.That(mergedReport.Tests, Has.Count.EqualTo(1)); + Assert.That(mergedReport.Tests, Has.Length.EqualTo(1)); Assert.That(mergedReport.Tests[0].Duration, Is.EqualTo(9)); mergedReport = report2.Union(report1) as TestwiseCoverageReport; - Assert.That(mergedReport.Tests, Has.Count.EqualTo(1)); + Assert.That(mergedReport.Tests, Has.Length.EqualTo(1)); Assert.That(mergedReport.Tests[0].Duration, Is.EqualTo(9)); } @@ -47,12 +47,12 @@ public void MergesSameTestsResult() TestwiseCoverageReport mergedReport = report2.Union(report1) as TestwiseCoverageReport; - Assert.That(mergedReport.Tests, Has.Count.EqualTo(1)); + Assert.That(mergedReport.Tests, Has.Length.EqualTo(1)); Assert.That(mergedReport.Tests[0].Result, Is.EqualTo("PASSED")); mergedReport = report1.Union(report2) as TestwiseCoverageReport; - Assert.That(mergedReport.Tests, Has.Count.EqualTo(1)); + Assert.That(mergedReport.Tests, Has.Length.EqualTo(1)); Assert.That(mergedReport.Tests[0].Result, Is.EqualTo("PASSED")); } @@ -64,7 +64,7 @@ public void MergesSameTestsCoverageInDifferentFiles() TestwiseCoverageReport mergedReport = report1.Union(report2) as TestwiseCoverageReport; - Assert.That(mergedReport.Tests, Has.Count.EqualTo(1)); + Assert.That(mergedReport.Tests, Has.Length.EqualTo(1)); Assert.That(mergedReport.Tests[0].UniformPath, Is.EqualTo("Test1")); Assert.That(mergedReport.Tests[0].CoverageByPath[0].Files, Has.Count.EqualTo(2)); Assert.That(mergedReport.Tests[0].CoverageByPath[0].Files[0].FileName, Is.EqualTo("file1.cs")); @@ -79,7 +79,7 @@ public void MergesSameTestsCoverageInSameFiles() TestwiseCoverageReport mergedReport = report1.Union(report2) as TestwiseCoverageReport; - Assert.That(mergedReport.Tests, Has.Count.EqualTo(1)); + Assert.That(mergedReport.Tests, Has.Length.EqualTo(1)); Assert.That(mergedReport.Tests[0].UniformPath, Is.EqualTo("Test1")); Assert.That(mergedReport.Tests[0].CoverageByPath[0].Files, Has.Count.EqualTo(1)); Assert.That(mergedReport.Tests[0].CoverageByPath[0].Files[0].FileName, Is.EqualTo("file1.cs")); diff --git a/UploadDaemon_Test/Scanning/TraceFileTest.cs b/UploadDaemon_Test/Scanning/TraceFileTest.cs index a1c665df..7e73e739 100644 --- a/UploadDaemon_Test/Scanning/TraceFileTest.cs +++ b/UploadDaemon_Test/Scanning/TraceFileTest.cs @@ -129,7 +129,7 @@ public void ConvertsToTestwiseCoverageReport() Assert.That(report, Is.InstanceOf()); TestwiseCoverageReport testwiseReport = (TestwiseCoverageReport)report; - Assert.That(testwiseReport.Tests, Has.Count.EqualTo(1)); + Assert.That(testwiseReport.Tests, Has.Length.EqualTo(1)); Assert.That(testwiseReport.Tests.First().UniformPath, Is.EqualTo("TestCase1")); Assert.That(testwiseReport.Tests.First().Duration, Is.EqualTo(1.333d)); Assert.That(testwiseReport.Tests.First().Result, Is.EqualTo("PASSED")); @@ -175,7 +175,7 @@ public void ConvertsToTestwiseCoverageReportMultipleTests() Assert.That(report, Is.InstanceOf()); TestwiseCoverageReport testwiseReport = (TestwiseCoverageReport)report; - Assert.That(testwiseReport.Tests, Has.Count.EqualTo(2)); + Assert.That(testwiseReport.Tests, Has.Length.EqualTo(2)); Assert.That(testwiseReport.Tests.First().UniformPath, Is.EqualTo("TestCase1")); Assert.That(testwiseReport.Tests.First().Duration, Is.EqualTo(1)); Assert.That(testwiseReport.Tests.First().Result, Is.EqualTo("PASSED")); @@ -229,7 +229,7 @@ public void ConsidersTestWithoutEnd() Assert.That(report, Is.InstanceOf()); TestwiseCoverageReport testwiseReport = (TestwiseCoverageReport)report; - Assert.That(testwiseReport.Tests, Has.Count.EqualTo(1)); + Assert.That(testwiseReport.Tests, Has.Length.EqualTo(1)); Assert.That(testwiseReport.Tests[0].UniformPath, Is.EqualTo("TestCase1")); Assert.That(testwiseReport.Tests[0].Result, Is.EqualTo("SKIPPED")); Assert.That(testwiseReport.Tests[0].Duration, Is.EqualTo(2)); @@ -261,7 +261,7 @@ public void ConsidersNoTestTrace() Assert.That(report, Is.InstanceOf()); TestwiseCoverageReport testwiseReport = (TestwiseCoverageReport)report; - Assert.That(testwiseReport.Tests, Has.Count.EqualTo(3)); + Assert.That(testwiseReport.Tests, Has.Length.EqualTo(3)); Assert.That(testwiseReport.Tests[2].UniformPath, Is.EqualTo("No Test")); Assert.That(testwiseReport.Tests[2].Result, Is.EqualTo("SKIPPED")); Assert.That(testwiseReport.Tests[2].Duration, Is.EqualTo(6)); From ec9d95f4736b32b9e2dda24787d605381d5f3ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Mon, 1 Aug 2022 16:53:22 +0200 Subject: [PATCH 116/174] Use newer libzmq commands for context shutdown and add timeout to subscribe socket --- Profiler/utils/Ipc.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Profiler/utils/Ipc.cpp b/Profiler/utils/Ipc.cpp index afbb88b9..4170cd19 100644 --- a/Profiler/utils/Ipc.cpp +++ b/Profiler/utils/Ipc.cpp @@ -1,6 +1,5 @@ #include "Ipc.h" - -#include +#include "zmq.h" #define IPC_TIMEOUT_MS 250 #define IPC_BUFFER_SIZE 255 @@ -32,14 +31,16 @@ Ipc::~Ipc() if (this->zmqRequestSocket != NULL) { zmq_close(this->zmqRequestSocket); } - - zmq_ctx_destroy(this->zmqContext); + zmq_ctx_shutdown(this->zmqContext); + zmq_ctx_term(this->zmqContext); } void Ipc::handlerThreadLoop() { this->zmqSubscribeSocket = zmq_socket(this->zmqContext, ZMQ_SUB); zmq_setsockopt(this->zmqSubscribeSocket, ZMQ_SUBSCRIBE, "test:", 0); zmq_setsockopt(this->zmqSubscribeSocket, ZMQ_RCVTIMEO, &zmqTimeout, sizeof(zmqTimeout)); + zmq_setsockopt(this->zmqSubscribeSocket, ZMQ_LINGER, &zmqTimeout, sizeof(zmqTimeout)); + if (!!zmq_connect(this->zmqSubscribeSocket, config->getTiaSubscribeSocket().c_str())) { zmq_close(this->zmqSubscribeSocket); this->zmqSubscribeSocket = NULL; From 68bd98feeacb3525c47493c969d0ac25a420dc9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=B6hlmann?= Date: Wed, 10 Aug 2022 08:29:15 +0200 Subject: [PATCH 117/174] pre-initialize profiler ipc --- Commander.Server/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Commander.Server/Program.cs b/Commander.Server/Program.cs index 2a731307..22cb65b2 100644 --- a/Commander.Server/Program.cs +++ b/Commander.Server/Program.cs @@ -1,8 +1,8 @@ using Cqse.Teamscale.Profiler.Commons.Ipc; var builder = WebApplication.CreateBuilder(args); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); +// explicitly initialize the Ipc, otherwise it might be created upon 1st request and we miss the start event in the profiler +builder.Services.AddSingleton(new ProfilerIpc(new IpcConfig())); builder.Services.AddControllers(); var app = builder.Build(); From 37db7ce142161d42070b19d10eb11077b87f57e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Wed, 18 Jan 2023 18:09:35 +0100 Subject: [PATCH 118/174] Add documentation for TIA mode catch in shutdown --- Profiler/CProfilerCallback.cpp | 7 +++++- documentation/userguide.md | 41 +++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/Profiler/CProfilerCallback.cpp b/Profiler/CProfilerCallback.cpp index 5f6ad91a..560bc31b 100644 --- a/Profiler/CProfilerCallback.cpp +++ b/Profiler/CProfilerCallback.cpp @@ -38,7 +38,12 @@ class ShutdownGuard { void shutdownInstance(bool clrIsAvailable) { EnterCriticalSection(§ion); if (instance != NULL) { - instance->ShutdownOnce(clrIsAvailable); + try { + instance->ShutdownOnce(clrIsAvailable); + } + catch (...) { + Debug::getInstance().logErrorWithStracktrace("Shutdown was interrupted. Likely due to an exception in zeromq. This is expected in some cases."); + } instance = NULL; } LeaveCriticalSection(§ion); diff --git a/documentation/userguide.md b/documentation/userguide.md index f564662e..91a323fc 100644 --- a/documentation/userguide.md +++ b/documentation/userguide.md @@ -149,7 +149,9 @@ The profiler has several configuration options that can either be set as environ | COR_PROFILER_PROCESS | String (optional) | A (case-insensitive) suffix of the path to the executable that should be profiled, e.g. `w3wp.exe`. All other executables will be ignored. This option is deprecated. It is recommended that you use the mechanisms of the configuration file instead. | | COR_PROFILER_DUMP_ENVIRONMENT | `1` or `0`, default `0` | Print all environment variables of the profiled process in the trace file. | | COR_PROFILER_IGNORE_EXCEPTIONS | `1` or `0`, default `0` | Causes all exceptions in the profiler code to be swallowed. For debugging only. | - +| COR_PROFILER_TGA | `1` or `0`, default `1` | Activates regular test coverage collection. This means, method coverage will be collected at all times. | +| COR_PROFILER_TIA | `1` or `0`, default `0` | Activates TIA coverage mode which means coverage can be collected per test case. | +| COR_PROFILER_TIA_SUBSCRIBE_SOCKET | Address, default `tcp://127.0.0.1:7145` | Socket address used for communicating test events to the profiler. | Please note that the profiler is **also** configured with variables starting with the `COR_PROFILER_` prefix in case of .NET Core applications. ## Configuration file @@ -248,6 +250,43 @@ to debug profiler crashes with WinDbg. To enable mini dumps, run the following a A `.dmp` file will be generated in `C:\Users\Public`. You can set `DumpType` to `2` to get a full dump instead. This may, however, be a rather large file since the entire program's heap will be dumped. +# Generating Testwise Coverage in TIA Mode + +Besides the regular coverage collection that is active by default and set by the `TGA` Parameter, the profiler also supports per test coverage collection. +This can be activated with the parameter `TIA`. Per test coverage can be useful for getting additional information on individual test cases but it can also be used for additional analyses in Teamscale such as Test Impact Analysis or Pareto Testing. + +## Starting and Ending Test Cases + +For Testwise Coverage to work, the Profiler needs to know when a test case is started and ended. The profiler comes with multiple options to achieve this. +The different options can be found in the Commander directory. Each of them provides a different way of starting and ending test cases. + +### GUI + +This is a simple gui application that allows a user to start and stop test cases. The primary use case of this is manual testing. It requires no further setup and test can easily be triggered with the according buttons. + +### CLI + +The command line client is intended for use with for example a CI build to automatically start and stop test cases. It provides an interactive interface that expects test start and stops interchanging. +The available Commands are: + +| Command | Description | | +|------------------|---------------------------------------------------------------------------------------------------------------------------------|---| +| start {testName} | Starts profiling for the test with the given name. | | +| stop {result} | Stops profiling for the active test with the given result. Possible result values are passed, ignored, skipped, failure, error. | | +| exit | stops the CLI | | + +### Server + +The server is a simple web server that accepts HTTP Requests and forwards the commands to the profiler accordingly. +It provides the following endpoints: + +| Endpoint | Method | Description | +|-----------------------|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| test | GET | Gets the currently active test | +| test/start/{testName} | POST | Starts the test with the given name | +| test/stop/{result} | POST | Stops the currently active test with the given result. Possible values are Passed, Ignored, Skipped, Failure, Error | +| test/end/{testName} | POST | Stops the test with the given name if it is currently active. This is a legacy endpoint and the test/stop endpoint should be preferred. Expects a test result in the body with key Result. | + # Automatic Trace Upload From 18334135a387ae868deb413fa487a16b326834f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Wed, 8 Feb 2023 15:27:03 +0100 Subject: [PATCH 119/174] Fix Test cases --- Profiler_Test/ProfilerTest.cs | 1 - Profiler_Test/Tia/TiaProfilerTest.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Profiler_Test/ProfilerTest.cs b/Profiler_Test/ProfilerTest.cs index f187ba26..9db1ef4e 100644 --- a/Profiler_Test/ProfilerTest.cs +++ b/Profiler_Test/ProfilerTest.cs @@ -121,7 +121,6 @@ public void TestTiaConfig() new Testee(GetTestProgram("ProfilerTestee.exe")).Run(arguments: "none", profiler); string[] lines = profiler.GetSingleTrace(); - Assert.That(lines, Has.None.Matches("^(Inlines|Jitted)")); Assert.That(lines, Has.Some.Matches("^(Called)")); } diff --git a/Profiler_Test/Tia/TiaProfilerTest.cs b/Profiler_Test/Tia/TiaProfilerTest.cs index 9dbcec47..e2d8aa81 100644 --- a/Profiler_Test/Tia/TiaProfilerTest.cs +++ b/Profiler_Test/Tia/TiaProfilerTest.cs @@ -136,7 +136,6 @@ public void NoIpcRunning() profilerIpc = null; } - [Ignore("TODO: No idea why this is not working... manual tests have no problem with this.")] [Test] public void IpcStartedAfterStartup() { @@ -147,6 +146,8 @@ public void IpcStartedAfterStartup() TesteeProcess testeeProcess = Start(testee, profilerUnderTest); profilerIpc = CreateProfilerIpc(profilerIpc.Config); + // Wait until the IPC server has started up. Not ideal but it fixes the test case. + Thread.Sleep(500); RunTestCase("A", testeeProcess, profilerIpc); Stop(testeeProcess); @@ -154,7 +155,6 @@ public void IpcStartedAfterStartup() Assert.That(profilerIpc.ReceivedRequests, Is.EquivalentTo(new[] { "profiler_disconnected" })); TiaTestResult testResult = profilerUnderTest.Result; Assert.That(testResult.TestCaseNames, Is.EquivalentTo(new[] { string.Empty, "A" })); - Assert.That(testResult.TestCases[0].TraceLines, Has.None.Matches("^(Inlines|Jitted|Called)")); Assert.That(testResult.TestCases[1].TraceLines, Has.Some.Matches("^(Inlines|Jitted|Called)")); } From 6f5659f3fa127aeddab0a3a26e44d0e7d1b0c232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Fri, 19 May 2023 15:37:18 +0200 Subject: [PATCH 120/174] Consider testwise coverage for artifactory uploads --- UploadDaemon/Upload/ArtifactoryUpload.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/UploadDaemon/Upload/ArtifactoryUpload.cs b/UploadDaemon/Upload/ArtifactoryUpload.cs index faf3bb1b..0625d72e 100644 --- a/UploadDaemon/Upload/ArtifactoryUpload.cs +++ b/UploadDaemon/Upload/ArtifactoryUpload.cs @@ -55,13 +55,26 @@ public async Task UploadLineCoverageAsync(string originalTraceFilePath, IC } string[] branchAndTimestamp = revisionOrTimestamp.Value.Split(':'); string url = $"{artifactory.Url}/uploads/{branchAndTimestamp[0]}/{branchAndTimestamp[1]}"; - url = $"{url}/{artifactory.Partition}/simple"; + if (lineCoverageReport.UploadFormat == "SIMPLE") + { + url = $"{url}/{artifactory.Partition}/simple"; + } else + { + url = $"{url}/{artifactory.Partition}/testwise"; + } if (artifactory.PathSuffix != null) { string encodedPathSuffix = HttpUtility.UrlEncode(artifactory.PathSuffix); url = $"{url}/{encodedPathSuffix}"; } - url = $"{url}/report.simple"; + + if (lineCoverageReport.UploadFormat == "SIMPLE") + { + url = $"{url}/report.simple"; + } else + { + url = $"{url}/report.testwise"; + } logger.Debug("Uploading line coverage from {trace} to {artifactory} ({url})", originalTraceFilePath, artifactory.ToString(), url); From 0866873454794d36b0d1f15f34ff1c348fe4ff79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Fri, 16 Jun 2023 16:36:14 +0200 Subject: [PATCH 121/174] Change name for artifactory report upload --- Profiler/CProfilerWorker.cpp | 2 +- UploadDaemon/Upload/ArtifactoryUpload.cs | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Profiler/CProfilerWorker.cpp b/Profiler/CProfilerWorker.cpp index 4f27dc03..69d2f206 100644 --- a/Profiler/CProfilerWorker.cpp +++ b/Profiler/CProfilerWorker.cpp @@ -20,7 +20,7 @@ CProfilerWorker::~CProfilerWorker() { void CProfilerWorker::methodIdThreadLoop() { while (!this->shutdown) { if (methodIdQueue.was_empty()) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::this_thread::sleep_for(std::chrono::milliseconds(40)); } EnterCriticalSection(methodSetSynchronization); transferMethodIds(); diff --git a/UploadDaemon/Upload/ArtifactoryUpload.cs b/UploadDaemon/Upload/ArtifactoryUpload.cs index 0625d72e..4041f7d4 100644 --- a/UploadDaemon/Upload/ArtifactoryUpload.cs +++ b/UploadDaemon/Upload/ArtifactoryUpload.cs @@ -67,14 +67,15 @@ public async Task UploadLineCoverageAsync(string originalTraceFilePath, IC string encodedPathSuffix = HttpUtility.UrlEncode(artifactory.PathSuffix); url = $"{url}/{encodedPathSuffix}"; } - + String reportName = ""; if (lineCoverageReport.UploadFormat == "SIMPLE") { - url = $"{url}/report.simple"; + reportName = "report.simple"; } else { - url = $"{url}/report.testwise"; + reportName = "report.testwise"; } + url = $"{url}/{reportName}"; logger.Debug("Uploading line coverage from {trace} to {artifactory} ({url})", originalTraceFilePath, artifactory.ToString(), url); @@ -83,7 +84,7 @@ public async Task UploadLineCoverageAsync(string originalTraceFilePath, IC byte[] reportBytes = Encoding.UTF8.GetBytes(lineCoverageReport.ToString()); using (MemoryStream stream = new MemoryStream(reportBytes)) { - return await PerformLineCoverageUpload(originalTraceFilePath, revisionOrTimestamp.Value, url, stream); + return await PerformLineCoverageUpload(originalTraceFilePath, revisionOrTimestamp.Value, url, stream, reportName); } } catch (Exception e) @@ -94,9 +95,9 @@ public async Task UploadLineCoverageAsync(string originalTraceFilePath, IC } } - private async Task PerformLineCoverageUpload(string originalTraceFilePath, string timestampValue, string url, MemoryStream stream) + private async Task PerformLineCoverageUpload(string originalTraceFilePath, string timestampValue, string url, MemoryStream stream, String reportName) { - using (HttpResponseMessage response = await HttpClientUtils.UploadMultiPartPut(client, url, "report", stream, "report.simple")) + using (HttpResponseMessage response = await HttpClientUtils.UploadMultiPartPut(client, url, "report", stream, reportName)) { if (response.IsSuccessStatusCode) { From 732af2b6398469d1fd9eac5130f14197f1d86409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Fri, 23 Jun 2023 10:57:54 +0200 Subject: [PATCH 122/174] Fix Test after merge --- Profiler_Test/ProfilerTest.cs | 4 +- Profiler_Test/ProfilerTestBase.cs | 84 +++++++++++++++---------------- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/Profiler_Test/ProfilerTest.cs b/Profiler_Test/ProfilerTest.cs index 40257fb5..7981ba9a 100644 --- a/Profiler_Test/ProfilerTest.cs +++ b/Profiler_Test/ProfilerTest.cs @@ -2,6 +2,7 @@ using Cqse.Teamscale.Profiler.Dotnet.Proxies; using Cqse.Teamscale.Profiler.Dotnet.Tia; using NUnit.Framework; +using System.Collections.Generic; using System.IO; using System.Linq; @@ -66,7 +67,8 @@ public int TestConfigFile(string regex) "); var environment = new Dictionary { { "COR_PROFILER_CONFIG", configFile } }; - return RunProfiler("ProfilerTestee.exe", arguments: "none", lightMode: true, bitness: Bitness.x86, environment: environment).Count; + new Testee(GetTestProgram("ProfilerTestee.exe")).Run(arguments: "none", profiler); + return profiler.GetTraceFiles().Count; } /// diff --git a/Profiler_Test/ProfilerTestBase.cs b/Profiler_Test/ProfilerTestBase.cs index 1e46207e..6863b2b1 100644 --- a/Profiler_Test/ProfilerTestBase.cs +++ b/Profiler_Test/ProfilerTestBase.cs @@ -1,15 +1,15 @@ -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; - -namespace Cqse.Teamscale.Profiler.Dotnet +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace Cqse.Teamscale.Profiler.Dotnet { - /// - /// Base class for testing the .NET Profiler. - /// + /// + /// Base class for testing the .NET Profiler. + /// public abstract class ProfilerTestBase { /** Label with which jitted methods are prefixed */ @@ -32,9 +32,9 @@ public abstract class ProfilerTestBase private List startedProcesses = new List(); - /// - /// The directory containing profiler solution. - /// + /// + /// The directory containing profiler solution. + /// public static DirectoryInfo SolutionRoot => new DirectoryInfo(Path.Combine(TestContext.CurrentContext.TestDirectory, "../../../")); [TearDown] @@ -43,9 +43,9 @@ public void TearDown() startedProcesses.Where(process => !process.HasExited).ToList().ForEach(process => process.Kill()); } - /// - /// Asserts that the trace file written by the profiler has the same contents as the given reference trace, modulo some normalization. - /// + /// + /// Asserts that the trace file written by the profiler has the same contents as the given reference trace, modulo some normalization. + /// protected void AssertNormalizedTraceFileEqualsReference(FileInfo actual, int[] assembliesToCompare) { FileInfo referenceTraceFile = new FileInfo(GetTestDataPath("reference-traces", GetSanitizedTestName() + ".txt")); @@ -56,7 +56,7 @@ protected void AssertNormalizedTraceFileEqualsReference(FileInfo actual, int[] a "The normalized contents of the trace files did not match"); } - /// + /// /// Returns the absolute path to a test data file. /// protected static string GetTestDataPath(params string[] path) @@ -64,12 +64,12 @@ protected static string GetTestDataPath(params string[] path) /// /// An executabel file in the TestProgramsDirectory. - /// + /// protected FileInfo GetTestProgram(string executableName) => new FileInfo(Path.Combine(TestProgramsDirectory.FullName, executableName)); - /// + /// /// The directory containing the testee binaries. - /// + /// private static DirectoryInfo TestProgramsDirectory => new DirectoryInfo(GetTestDataPath("test-programs")); @@ -77,18 +77,18 @@ protected static string GetTestDataPath(params string[] path) /// Returns a test-specific directory for temp files /// protected static string TestTempDirectory => - Path.Combine(SolutionRoot.FullName, "test-tmp", GetSanitizedTestName()); + Path.Combine(SolutionRoot.FullName, "test-tmp", GetSanitizedTestName()); - /// + /// /// A temporary directory for a profiler to write trace files to. /// protected static DirectoryInfo TestTraceDirectory => new DirectoryInfo(TestTempDirectory).CreateSubdirectory("traces"); /// - /// Creates a unique (and empty) temporary test directory for storing output. - /// - [SetUp] + /// Creates a unique (and empty) temporary test directory for storing output. + /// + [SetUp] protected void CreateTemporaryTestDir() { var testDir = new DirectoryInfo(TestTempDirectory); @@ -100,10 +100,10 @@ protected void CreateTemporaryTestDir() testDir.Create(); } - /// - /// Returns a sanatized name for the test case that is valid for paths. - /// - /// + /// + /// Returns a sanatized name for the test case that is valid for paths. + /// + /// private static string GetSanitizedTestName() { var testDirName = TestContext.CurrentContext.Test.FullName; @@ -111,11 +111,11 @@ private static string GetSanitizedTestName() return string.Join("", testDirName.Split(invalidChars, StringSplitOptions.RemoveEmptyEntries)); } - /// - /// Returns the inlined and jitted methods for the testee assembly. All other - /// trace file content may vary across machines or versions of the.NET - /// framework, including the number of actually jitted methods(in mscorlib). - /// + /// + /// Returns the inlined and jitted methods for the testee assembly. All other + /// trace file content may vary across machines or versions of the.NET + /// framework, including the number of actually jitted methods(in mscorlib). + /// private static string ReadNormalizedTraceContent(FileInfo traceFile, HashSet assembliesToCompare) { string[] content = File.ReadAllLines(traceFile.FullName); @@ -131,9 +131,9 @@ private static string ReadNormalizedTraceContent(FileInfo traceFile, HashSet - /// Filters invoked methods, keeping only those that contain allowed assembly keys. Each line in the output corresponds to one method invocation. - /// + /// + /// Filters invoked methods, keeping only those that contain allowed assembly keys. Each line in the output corresponds to one method invocation. + /// private static IEnumerable FilterMethodInvocationsByAssemblyNumber(IEnumerable methodInvocations, HashSet assembliesToCompare) { @@ -151,12 +151,12 @@ private static IEnumerable FilterMethodInvocationsByAssemblyNumber(IEnum } } - /// - /// Parse trace into map from key to list of values. - /// + /// + /// Parse trace into map from key to list of values. + /// private static ILookup KeyValuesMapFor(string[] coverageReport) => coverageReport.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("//")) .Select(line => line.Split(new char[] { '=' }, 2)) .ToLookup(split => split[0], split => split[0]); - } + } } From df39b8dcd295cf96ec6cfcfab63d206a96e314be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Mon, 24 Jul 2023 17:57:42 +0200 Subject: [PATCH 123/174] Remove Worker thread due to issues with many processes --- Profiler/CProfilerCallback.cpp | 13 +- Profiler/CProfilerCallback.h | 3 - Profiler/CProfilerWorker.cpp | 41 -- Profiler/CProfilerWorker.h | 33 -- Profiler/utils/MethodEnter.cpp | 9 +- Profiler/utils/MethodEnter.h | 4 - Profiler/utils/atomic_queue/LICENSE | 21 - Profiler/utils/atomic_queue/atomic_queue.h | 615 --------------------- Profiler/utils/atomic_queue/defs.h | 92 --- 9 files changed, 5 insertions(+), 826 deletions(-) delete mode 100644 Profiler/CProfilerWorker.cpp delete mode 100644 Profiler/CProfilerWorker.h delete mode 100644 Profiler/utils/atomic_queue/LICENSE delete mode 100644 Profiler/utils/atomic_queue/atomic_queue.h delete mode 100644 Profiler/utils/atomic_queue/defs.h diff --git a/Profiler/CProfilerCallback.cpp b/Profiler/CProfilerCallback.cpp index 9b22301e..935edef4 100644 --- a/Profiler/CProfilerCallback.cpp +++ b/Profiler/CProfilerCallback.cpp @@ -171,7 +171,8 @@ HRESULT CProfilerCallback::InitializeImplementation(IUnknown* pICorProfilerInfoU this->ipc = new Ipc(&this->config, testStartCallback, testEndCallback, errorCallback); std::string testName = this->ipc->getCurrentTestName(); - worker = new CProfilerWorker(&config, &traceLog, &calledMethodIds, &methodSetSynchronization); + setCriticalSection(&methodSetSynchronization); + setCalledMethodsSet(&calledMethodIds); if (!testName.empty()) { setTestCaseRecording(true); traceLog.startTestCase(testName); @@ -242,9 +243,6 @@ void CProfilerCallback::ShutdownOnce(bool clrIsAvailable) { } EnterCriticalSection(&callbackSynchronization); EnterCriticalSection(&methodSetSynchronization); - if (config.isTiaEnabled()) { - worker->transferMethodIds(); - } writeFunctionInfosToLog(); LeaveCriticalSection(&methodSetSynchronization); LeaveCriticalSection(&callbackSynchronization); @@ -257,10 +255,6 @@ void CProfilerCallback::ShutdownOnce(bool clrIsAvailable) { delete ipc; ipc = NULL; } - if (this->worker != NULL) { - delete worker; - worker = NULL; - } if (config.shouldStartUploadDaemon()) { createDaemon().notifyShutdown(); } @@ -572,7 +566,6 @@ void CProfilerCallback::onTestStart(std::string testName) if (!testName.empty()) { setTestCaseRecording(true); } - LeaveCriticalSection(&methodSetSynchronization); } } @@ -582,9 +575,7 @@ void CProfilerCallback::onTestEnd(std::string result, std::string message) if (config.isProfilingEnabled() && config.isTiaEnabled()) { EnterCriticalSection(&methodSetSynchronization); setTestCaseRecording(false); - worker->transferMethodIds(); writeFunctionInfosToLog(); - traceLog.endTestCase(result, message); LeaveCriticalSection(&methodSetSynchronization); diff --git a/Profiler/CProfilerCallback.h b/Profiler/CProfilerCallback.h index f19f1a58..d93f1bd0 100644 --- a/Profiler/CProfilerCallback.h +++ b/Profiler/CProfilerCallback.h @@ -10,7 +10,6 @@ #include #include #include -#include "CProfilerWorker.h" #include "UploadDaemon.h" #include "utils/Ipc.h" /** @@ -116,8 +115,6 @@ class CProfilerCallback : public CProfilerCallbackBase { */ functionID_set calledMethodIds; - CProfilerWorker* worker = NULL; - /** * Keeps track of called methods. * We use the vector to uniquely store the information about called methods. diff --git a/Profiler/CProfilerWorker.cpp b/Profiler/CProfilerWorker.cpp deleted file mode 100644 index 69d2f206..00000000 --- a/Profiler/CProfilerWorker.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "CProfilerWorker.h" - -CProfilerWorker::CProfilerWorker(Config* config, TraceLog* traceLog, functionID_set* calledMethodIds, CRITICAL_SECTION* methodSetSynchronization) { - this->traceLog = traceLog; - this->calledMethodIds = calledMethodIds; - this->methodSetSynchronization = methodSetSynchronization; - setCriticalSection(methodSetSynchronization); - setCalledMethodsSet(calledMethodIds); - setMethodIdQueue(&methodIdQueue); - this->workerThread = new std::thread(&CProfilerWorker::methodIdThreadLoop, this); -} - -CProfilerWorker::~CProfilerWorker() { - this->shutdown = true; - if (this->workerThread->joinable()) { - this->workerThread->join(); - } -} - -void CProfilerWorker::methodIdThreadLoop() { - while (!this->shutdown) { - if (methodIdQueue.was_empty()) { - std::this_thread::sleep_for(std::chrono::milliseconds(40)); - } - EnterCriticalSection(methodSetSynchronization); - transferMethodIds(); - LeaveCriticalSection(methodSetSynchronization); - } -} - -void CProfilerWorker::transferMethodIds() { - FunctionID i; - while (methodIdQueue.try_pop(i)) { - calledMethodIds->insert(i); - } -} - -void CProfilerWorker::logError(std::string message) { - std::string error = message; - traceLog->info(error); -} diff --git a/Profiler/CProfilerWorker.h b/Profiler/CProfilerWorker.h deleted file mode 100644 index 48fe9c72..00000000 --- a/Profiler/CProfilerWorker.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#include "config/Config.h" -#include -#include "log/TraceLog.h" -#include -#include "utils/MethodEnter.h" -#include -#include - -class CProfilerWorker -{ -public: - CProfilerWorker(Config*, TraceLog*, functionID_set*, CRITICAL_SECTION*); - virtual ~CProfilerWorker(); - void transferMethodIds(); -private: - - // Variables - std::thread* workerThread = NULL; - TraceLog* traceLog = NULL; - bool shutdown = false; - - // Warning from the use of alignas in the Atomic Queue. -#pragma warning( disable : 4316) - functionID_set* calledMethodIds; - Queue methodIdQueue = Queue(65'536); - - CRITICAL_SECTION* methodSetSynchronization; - - // Methods - void methodIdThreadLoop(); - void logError(std::string); -}; diff --git a/Profiler/utils/MethodEnter.cpp b/Profiler/utils/MethodEnter.cpp index a06211c0..a86f3afe 100644 --- a/Profiler/utils/MethodEnter.cpp +++ b/Profiler/utils/MethodEnter.cpp @@ -6,12 +6,13 @@ namespace { functionID_set* calledFunctionSet; bool isTestCaseRecording = false; CRITICAL_SECTION* methodSetSynchronization; - Queue* methodQueue; } extern "C" void _stdcall EnterCpp(FunctionIDOrClientID funcId) { if (isTestCaseRecording && !calledFunctionSet->contains(funcId.functionID)) { - methodQueue->push(funcId.functionID); + EnterCriticalSection(methodSetSynchronization); + calledFunctionSet->insert(funcId.functionID); + LeaveCriticalSection(methodSetSynchronization); } } @@ -23,10 +24,6 @@ void setCriticalSection(CRITICAL_SECTION* methodSetSync) { methodSetSynchronization = methodSetSync; } -void setMethodIdQueue(Queue* methodIdQueue) { - methodQueue = methodIdQueue; -} - void setTestCaseRecording(bool testCaseRecording) { isTestCaseRecording = testCaseRecording; } diff --git a/Profiler/utils/MethodEnter.h b/Profiler/utils/MethodEnter.h index 6953e8ee..3c395949 100644 --- a/Profiler/utils/MethodEnter.h +++ b/Profiler/utils/MethodEnter.h @@ -5,10 +5,8 @@ #include #include #include -#include FunctionID constexpr NIL = static_cast(-1); -using Queue = atomic_queue::AtomicQueueB, NIL>; /** * Sets the vector to be filled with methodIds from called methods at this time. @@ -17,8 +15,6 @@ void setCalledMethodsSet(functionID_set*); void setCriticalSection(CRITICAL_SECTION*); -void setMethodIdQueue(Queue*); - /** * Sets the state of test case recording i.e. whether a test case is currently in progress or not. */ diff --git a/Profiler/utils/atomic_queue/LICENSE b/Profiler/utils/atomic_queue/LICENSE deleted file mode 100644 index 04b5fb17..00000000 --- a/Profiler/utils/atomic_queue/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Maxim Egorushkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/Profiler/utils/atomic_queue/atomic_queue.h b/Profiler/utils/atomic_queue/atomic_queue.h deleted file mode 100644 index 84281c9b..00000000 --- a/Profiler/utils/atomic_queue/atomic_queue.h +++ /dev/null @@ -1,615 +0,0 @@ -/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ -#ifndef ATOMIC_QUEUE_ATOMIC_QUEUE_H_INCLUDED -#define ATOMIC_QUEUE_ATOMIC_QUEUE_H_INCLUDED - -// Copyright (c) 2019 Maxim Egorushkin. MIT License. See the full licence in file LICENSE. - -#include "defs.h" - -#include -#include -#include -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace atomic_queue { - using std::uint32_t; - using std::uint64_t; - using std::uint8_t; - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - namespace details { - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - template struct GetCacheLineIndexBits { static int constexpr value = 0; }; - template<> struct GetCacheLineIndexBits<256> { static int constexpr value = 8; }; - template<> struct GetCacheLineIndexBits<128> { static int constexpr value = 7; }; - template<> struct GetCacheLineIndexBits< 64> { static int constexpr value = 6; }; - template<> struct GetCacheLineIndexBits< 32> { static int constexpr value = 5; }; - template<> struct GetCacheLineIndexBits< 16> { static int constexpr value = 4; }; - template<> struct GetCacheLineIndexBits< 8> { static int constexpr value = 3; }; - template<> struct GetCacheLineIndexBits< 4> { static int constexpr value = 2; }; - template<> struct GetCacheLineIndexBits< 2> { static int constexpr value = 1; }; - - template - struct GetIndexShuffleBits { - static int constexpr bits = GetCacheLineIndexBits::value; - static unsigned constexpr min_size = 1u << (bits * 2); - static int constexpr value = array_size < min_size ? 0 : bits; - }; - - template - struct GetIndexShuffleBits { - static int constexpr value = 0; - }; - - // Multiple writers/readers contend on the same cache line when storing/loading elements at - // subsequent indexes, aka false sharing. For power of 2 ring buffer size it is possible to re-map - // the index in such a way that each subsequent element resides on another cache line, which - // minimizes contention. This is done by swapping the lowest order N bits (which are the index of - // the element within the cache line) with the next N bits (which are the index of the cache line) - // of the element index. - template - constexpr unsigned remap_index_with_mix(unsigned index, unsigned mix) { - return index ^ mix ^ (mix << BITS); - } - - template - constexpr unsigned remap_index(unsigned index) noexcept { - return remap_index_with_mix(index, (index ^ (index >> BITS)) & ((1u << BITS) - 1)); - } - - template<> - constexpr unsigned remap_index<0>(unsigned index) noexcept { - return index; - } - - template - constexpr T& map(T* elements, unsigned index) noexcept { - return elements[remap_index(index)]; - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Implement a "bit-twiddling hack" for finding the next power of 2 in either 32 bits or 64 bits - // in C++11 compatible constexpr functions. The library no longer maintains C++11 compatibility. - - // "Runtime" version for 32 bits - // --a; - // a |= a >> 1; - // a |= a >> 2; - // a |= a >> 4; - // a |= a >> 8; - // a |= a >> 16; - // ++a; - - template - constexpr T decrement(T x) noexcept { - return x - 1; - } - - template - constexpr T increment(T x) noexcept { - return x + 1; - } - - template - constexpr T or_equal(T x, unsigned u) noexcept { - return x | x >> u; - } - - template - constexpr T or_equal(T x, unsigned u, Args... rest) noexcept { - return or_equal(or_equal(x, u), rest...); - } - - constexpr uint32_t round_up_to_power_of_2(uint32_t a) noexcept { - return increment(or_equal(decrement(a), 1, 2, 4, 8, 16)); - } - - constexpr uint64_t round_up_to_power_of_2(uint64_t a) noexcept { - return increment(or_equal(decrement(a), 1, 2, 4, 8, 16, 32)); - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - } // namespace details - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - template - class AtomicQueueCommon { - protected: - // Put these on different cache lines to avoid false sharing between readers and writers. - alignas(CACHE_LINE_SIZE) std::atomic head_ = {}; - alignas(CACHE_LINE_SIZE) std::atomic tail_ = {}; - - // The special member functions are not thread-safe. - - AtomicQueueCommon() noexcept = default; - - AtomicQueueCommon(AtomicQueueCommon const& b) noexcept - : head_(b.head_.load(X)) - , tail_(b.tail_.load(X)) {} - - AtomicQueueCommon& operator=(AtomicQueueCommon const& b) noexcept { - head_.store(b.head_.load(X), X); - tail_.store(b.tail_.load(X), X); - return *this; - } - - void swap(AtomicQueueCommon& b) noexcept { - unsigned h = head_.load(X); - unsigned t = tail_.load(X); - head_.store(b.head_.load(X), X); - tail_.store(b.tail_.load(X), X); - b.head_.store(h, X); - b.tail_.store(t, X); - } - - template - static T do_pop_atomic(std::atomic& q_element) noexcept { - if (Derived::spsc_) { - for (;;) { - T element = q_element.load(X); - if (ATOMIC_QUEUE_LIKELY(element != NIL)) { - q_element.store(NIL, R); - return element; - } - if (Derived::maximize_throughput_) - spin_loop_pause(); - } - } - else { - for (;;) { - T element = q_element.exchange(NIL, R); // (2) The store to wait for. - if (ATOMIC_QUEUE_LIKELY(element != NIL)) - return element; - // Do speculative loads while busy-waiting to avoid broadcasting RFO messages. - do - spin_loop_pause(); - while (Derived::maximize_throughput_ && q_element.load(X) == NIL); - } - } - } - - template - static void do_push_atomic(T element, std::atomic& q_element) noexcept { - assert(element != NIL); - if (Derived::spsc_) { - while (ATOMIC_QUEUE_UNLIKELY(q_element.load(X) != NIL)) - if (Derived::maximize_throughput_) - spin_loop_pause(); - q_element.store(element, R); - } - else { - for (T expected = NIL; ATOMIC_QUEUE_UNLIKELY(!q_element.compare_exchange_strong(expected, element, R, X)); expected = NIL) { - do - spin_loop_pause(); // (1) Wait for store (2) to complete. - while (Derived::maximize_throughput_ && q_element.load(X) != NIL); - } - } - } - - enum State : unsigned char { EMPTY, STORING, STORED, LOADING }; - - template - static T do_pop_any(std::atomic& state, T& q_element) noexcept { - if (Derived::spsc_) { - while (ATOMIC_QUEUE_UNLIKELY(state.load(A) != STORED)) - if (Derived::maximize_throughput_) - spin_loop_pause(); - T element{ std::move(q_element) }; - state.store(EMPTY, R); - return element; - } - else { - for (;;) { - unsigned char expected = STORED; - if (ATOMIC_QUEUE_LIKELY(state.compare_exchange_strong(expected, LOADING, A, X))) { - T element{ std::move(q_element) }; - state.store(EMPTY, R); - return element; - } - // Do speculative loads while busy-waiting to avoid broadcasting RFO messages. - do - spin_loop_pause(); - while (Derived::maximize_throughput_ && state.load(X) != STORED); - } - } - } - - template - static void do_push_any(U&& element, std::atomic& state, T& q_element) noexcept { - if (Derived::spsc_) { - while (ATOMIC_QUEUE_UNLIKELY(state.load(A) != EMPTY)) - if (Derived::maximize_throughput_) - spin_loop_pause(); - q_element = std::forward(element); - state.store(STORED, R); - } - else { - for (;;) { - unsigned char expected = EMPTY; - if (ATOMIC_QUEUE_LIKELY(state.compare_exchange_strong(expected, STORING, A, X))) { - q_element = std::forward(element); - state.store(STORED, R); - return; - } - // Do speculative loads while busy-waiting to avoid broadcasting RFO messages. - do - spin_loop_pause(); - while (Derived::maximize_throughput_ && state.load(X) != EMPTY); - } - } - } - - public: - template - bool try_push(T&& element) noexcept { - auto head = head_.load(X); - if (Derived::spsc_) { - if (static_cast(head - tail_.load(X)) >= static_cast(static_cast(*this).size_)) - return false; - head_.store(head + 1, X); - } - else { - do { - if (static_cast(head - tail_.load(X)) >= static_cast(static_cast(*this).size_)) - return false; - } while (ATOMIC_QUEUE_UNLIKELY(!head_.compare_exchange_strong(head, head + 1, A, X))); // This loop is not FIFO. - } - - static_cast(*this).do_push(std::forward(element), head); - return true; - } - - template - bool try_pop(T& element) noexcept { - auto tail = tail_.load(X); - if (Derived::spsc_) { - if (static_cast(head_.load(X) - tail) <= 0) - return false; - tail_.store(tail + 1, X); - } - else { - do { - if (static_cast(head_.load(X) - tail) <= 0) - return false; - } while (ATOMIC_QUEUE_UNLIKELY(!tail_.compare_exchange_strong(tail, tail + 1, A, X))); // This loop is not FIFO. - } - - element = static_cast(*this).do_pop(tail); - return true; - } - - template - void push(T&& element) noexcept { - unsigned head; - if (Derived::spsc_) { - head = head_.load(X); - head_.store(head + 1, X); - } - else { - constexpr auto memory_order = Derived::total_order_ ? std::memory_order_seq_cst : std::memory_order_acquire; - head = head_.fetch_add(1, memory_order); // FIFO and total order on Intel regardless, as of 2019. - } - static_cast(*this).do_push(std::forward(element), head); - } - - auto pop() noexcept { - unsigned tail; - if (Derived::spsc_) { - tail = tail_.load(X); - tail_.store(tail + 1, X); - } - else { - constexpr auto memory_order = Derived::total_order_ ? std::memory_order_seq_cst : std::memory_order_acquire; - tail = tail_.fetch_add(1, memory_order); // FIFO and total order on Intel regardless, as of 2019. - } - return static_cast(*this).do_pop(tail); - } - - bool was_empty() const noexcept { - return !was_size(); - } - - bool was_full() const noexcept { - return was_size() >= static_cast(static_cast(*this).size_); - } - - unsigned was_size() const noexcept { - // tail_ can be greater than head_ because of consumers doing pop, rather that try_pop, when the queue is empty. - return (std::max)(static_cast(head_.load(X) - tail_.load(X)), 0); - } - - unsigned capacity() const noexcept { - return static_cast(*this).size_; - } - }; - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - template - class AtomicQueue : public AtomicQueueCommon> { - using Base = AtomicQueueCommon>; - friend Base; - - static constexpr unsigned size_ = MINIMIZE_CONTENTION ? details::round_up_to_power_of_2(SIZE) : SIZE; - static constexpr int SHUFFLE_BITS = details::GetIndexShuffleBits)>::value; - static constexpr bool total_order_ = TOTAL_ORDER; - static constexpr bool spsc_ = SPSC; - static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; - - alignas(CACHE_LINE_SIZE) std::atomic elements_[size_] = {}; // Empty elements are NIL. - - T do_pop(unsigned tail) noexcept { - std::atomic& q_element = details::map(elements_, tail % size_); - return Base::template do_pop_atomic(q_element); - } - - void do_push(T element, unsigned head) noexcept { - std::atomic& q_element = details::map(elements_, head % size_); - Base::template do_push_atomic(element, q_element); - } - - public: - using value_type = T; - - AtomicQueue() noexcept { - assert(std::atomic{NIL}.is_lock_free()); // This queue is for atomic elements only. AtomicQueue2 is for non-atomic ones. - if (T{} != NIL) - for (auto& element : elements_) - element.store(NIL, X); - } - - AtomicQueue(AtomicQueue const&) = delete; - AtomicQueue& operator=(AtomicQueue const&) = delete; - }; - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - template - class AtomicQueue2 : public AtomicQueueCommon> { - using Base = AtomicQueueCommon>; - using State = typename Base::State; - friend Base; - - static constexpr unsigned size_ = MINIMIZE_CONTENTION ? details::round_up_to_power_of_2(SIZE) : SIZE; - static constexpr int SHUFFLE_BITS = details::GetIndexShuffleBits::value; - static constexpr bool total_order_ = TOTAL_ORDER; - static constexpr bool spsc_ = SPSC; - static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; - - alignas(CACHE_LINE_SIZE) std::atomic states_[size_] = {}; - alignas(CACHE_LINE_SIZE) T elements_[size_] = {}; - - T do_pop(unsigned tail) noexcept { - unsigned index = details::remap_index(tail % size_); - return Base::template do_pop_any(states_[index], elements_[index]); - } - - template - void do_push(U&& element, unsigned head) noexcept { - unsigned index = details::remap_index(head % size_); - Base::template do_push_any(std::forward(element), states_[index], elements_[index]); - } - - public: - using value_type = T; - - AtomicQueue2() noexcept = default; - AtomicQueue2(AtomicQueue2 const&) = delete; - AtomicQueue2& operator=(AtomicQueue2 const&) = delete; - }; - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - template, T NIL = T{}, bool MAXIMIZE_THROUGHPUT = true, bool TOTAL_ORDER = false, bool SPSC = false > - class AtomicQueueB : public AtomicQueueCommon>, - private std::allocator_traits::template rebind_alloc> { - using Base = AtomicQueueCommon>; - friend Base; - - static constexpr bool total_order_ = TOTAL_ORDER; - static constexpr bool spsc_ = SPSC; - static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; - - using AllocatorElements = typename std::allocator_traits::template rebind_alloc>; - - static constexpr auto ELEMENTS_PER_CACHE_LINE = CACHE_LINE_SIZE / sizeof(std::atomic); - static_assert(ELEMENTS_PER_CACHE_LINE, "Unexpected ELEMENTS_PER_CACHE_LINE."); - - static constexpr auto SHUFFLE_BITS = details::GetCacheLineIndexBits::value; - static_assert(SHUFFLE_BITS, "Unexpected SHUFFLE_BITS."); - - // AtomicQueueCommon members are stored into by readers and writers. - // Allocate these immutable members on another cache line which never gets invalidated by stores. - alignas(CACHE_LINE_SIZE) unsigned size_; - std::atomic* elements_; - - T do_pop(unsigned tail) noexcept { - std::atomic& q_element = details::map(elements_, tail & (size_ - 1)); - return Base::template do_pop_atomic(q_element); - } - - void do_push(T element, unsigned head) noexcept { - std::atomic& q_element = details::map(elements_, head & (size_ - 1)); - Base::template do_push_atomic(element, q_element); - } - - public: - using value_type = T; - - // The special member functions are not thread-safe. - - AtomicQueueB(unsigned size) - : size_((std::max)(details::round_up_to_power_of_2(size), 1u << (SHUFFLE_BITS * 2))) - , elements_(AllocatorElements::allocate(size_)) { - assert(std::atomic{NIL}.is_lock_free()); // This queue is for atomic elements only. AtomicQueueB2 is for non-atomic ones. - for (auto p = elements_, q = elements_ + size_; p < q; ++p) - p->store(NIL, X); - } - - AtomicQueueB(AtomicQueueB&& b) noexcept - : Base(static_cast(b)) - , AllocatorElements(static_cast(b)) // TODO: This must be noexcept, static_assert that. - , size_(b.size_) - , elements_(b.elements_) { - b.size_ = 0; - b.elements_ = 0; - } - - AtomicQueueB& operator=(AtomicQueueB&& b) noexcept { - b.swap(*this); - return *this; - } - - ~AtomicQueueB() noexcept { - if (elements_) - AllocatorElements::deallocate(elements_, size_); // TODO: This must be noexcept, static_assert that. - } - - void swap(AtomicQueueB& b) noexcept { - using std::swap; - this->Base::swap(b); - swap(static_cast(*this), static_cast(b)); - swap(size_, b.size_); - swap(elements_, b.elements_); - } - - friend void swap(AtomicQueueB& a, AtomicQueueB& b) { - a.swap(b); - } - }; - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - template, bool MAXIMIZE_THROUGHPUT = true, bool TOTAL_ORDER = false, bool SPSC = false> - class AtomicQueueB2 : public AtomicQueueCommon>, - private A, - private std::allocator_traits::template rebind_alloc> { - using Base = AtomicQueueCommon>; - using State = typename Base::State; - friend Base; - - static constexpr bool total_order_ = TOTAL_ORDER; - static constexpr bool spsc_ = SPSC; - static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; - - using AllocatorElements = A; - using AllocatorStates = typename std::allocator_traits::template rebind_alloc>; - - // AtomicQueueCommon members are stored into by readers and writers. - // Allocate these immutable members on another cache line which never gets invalidated by stores. - alignas(CACHE_LINE_SIZE) unsigned size_; - std::atomic* states_; - T* elements_; - - static constexpr auto STATES_PER_CACHE_LINE = CACHE_LINE_SIZE / sizeof(State); - static_assert(STATES_PER_CACHE_LINE, "Unexpected STATES_PER_CACHE_LINE."); - - static constexpr auto SHUFFLE_BITS = details::GetCacheLineIndexBits::value; - static_assert(SHUFFLE_BITS, "Unexpected SHUFFLE_BITS."); - - T do_pop(unsigned tail) noexcept { - unsigned index = details::remap_index(tail & (size_ - 1)); - return Base::template do_pop_any(states_[index], elements_[index]); - } - - template - void do_push(U&& element, unsigned head) noexcept { - unsigned index = details::remap_index(head & (size_ - 1)); - Base::template do_push_any(std::forward(element), states_[index], elements_[index]); - } - - public: - using value_type = T; - - // The special member functions are not thread-safe. - - AtomicQueueB2(unsigned size) - : size_((std::max)(details::round_up_to_power_of_2(size), 1u << (SHUFFLE_BITS * 2))) - , states_(AllocatorStates::allocate(size_)) - , elements_(AllocatorElements::allocate(size_)) { - for (auto p = states_, q = states_ + size_; p < q; ++p) - p->store(Base::EMPTY, X); - - AllocatorElements& ae = *this; - for (auto p = elements_, q = elements_ + size_; p < q; ++p) - std::allocator_traits::construct(ae, p); - } - - AtomicQueueB2(AtomicQueueB2&& b) noexcept - : Base(static_cast(b)) - , AllocatorElements(static_cast(b)) // TODO: This must be noexcept, static_assert that. - , AllocatorStates(static_cast(b)) // TODO: This must be noexcept, static_assert that. - , size_(b.size_) - , states_(b.states_) - , elements_(b.elements_) { - b.size_ = 0; - b.states_ = 0; - b.elements_ = 0; - } - - AtomicQueueB2& operator=(AtomicQueueB2&& b) noexcept { - b.swap(*this); - return *this; - } - - ~AtomicQueueB2() noexcept { - if (elements_) { - AllocatorElements& ae = *this; - for (auto p = elements_, q = elements_ + size_; p < q; ++p) - std::allocator_traits::destroy(ae, p); - AllocatorElements::deallocate(elements_, size_); // TODO: This must be noexcept, static_assert that. - AllocatorStates::deallocate(states_, size_); // TODO: This must be noexcept, static_assert that. - } - } - - void swap(AtomicQueueB2& b) noexcept { - using std::swap; - this->Base::swap(b); - swap(static_cast(*this), static_cast(b)); - swap(static_cast(*this), static_cast(b)); - swap(size_, b.size_); - swap(states_, b.states_); - swap(elements_, b.elements_); - } - - friend void swap(AtomicQueueB2& a, AtomicQueueB2& b) noexcept { - a.swap(b); - } - }; - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - template - struct RetryDecorator : Queue { - using T = typename Queue::value_type; - - using Queue::Queue; - - void push(T element) noexcept { - while (!this->try_push(element)) - spin_loop_pause(); - } - - T pop() noexcept { - T element; - while (!this->try_pop(element)) - spin_loop_pause(); - return element; - } - }; - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -} // namespace atomic_queue - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#endif // ATOMIC_QUEUE_ATOMIC_QUEUE_H_INCLUDED diff --git a/Profiler/utils/atomic_queue/defs.h b/Profiler/utils/atomic_queue/defs.h deleted file mode 100644 index c1e8e6e2..00000000 --- a/Profiler/utils/atomic_queue/defs.h +++ /dev/null @@ -1,92 +0,0 @@ -/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ -#ifndef ATOMIC_QUEUE_DEFS_H_INCLUDED -#define ATOMIC_QUEUE_DEFS_H_INCLUDED - -// Copyright (c) 2019 Maxim Egorushkin. MIT License. See the full licence in file LICENSE. - -#include - -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) -#include -namespace atomic_queue { -constexpr int CACHE_LINE_SIZE = 64; -static inline void spin_loop_pause() noexcept { - _mm_pause(); -} -} // namespace atomic_queue -#elif defined(__arm__) || defined(__aarch64__) -namespace atomic_queue { -constexpr int CACHE_LINE_SIZE = 64; -static inline void spin_loop_pause() noexcept { -#if (defined(__ARM_ARCH_6K__) || \ - defined(__ARM_ARCH_6Z__) || \ - defined(__ARM_ARCH_6ZK__) || \ - defined(__ARM_ARCH_6T2__) || \ - defined(__ARM_ARCH_7__) || \ - defined(__ARM_ARCH_7A__) || \ - defined(__ARM_ARCH_7R__) || \ - defined(__ARM_ARCH_7M__) || \ - defined(__ARM_ARCH_7S__) || \ - defined(__ARM_ARCH_8A__) || \ - defined(__aarch64__)) - asm volatile ("yield" ::: "memory"); -#else - asm volatile ("nop" ::: "memory"); -#endif -} -} // namespace atomic_queue -#elif defined(__ppc64__) || defined(__powerpc64__) -namespace atomic_queue { -constexpr int CACHE_LINE_SIZE = 128; // TODO: Review that this is the correct value. -static inline void spin_loop_pause() noexcept { - asm volatile("or 31,31,31 # very low priority"); // TODO: Review and benchmark that this is the right instruction. -} -} // namespace atomic_queue -#elif defined(__s390x__) -namespace atomic_queue { -constexpr int CACHE_LINE_SIZE = 256; // TODO: Review that this is the correct value. -static inline void spin_loop_pause() noexcept {} // TODO: Find the right instruction to use here, if any. -} // namespace atomic_queue -#elif defined(__riscv) -namespace atomic_queue { -constexpr int CACHE_LINE_SIZE = 64; -static inline void spin_loop_pause() noexcept { - asm volatile (".insn i 0x0F, 0, x0, x0, 0x010"); -} -} // namespace atomic_queue -#else -#warning "Unknown CPU architecture. Using L1 cache line size of 64 bytes and no spinloop pause instruction." -namespace atomic_queue { -constexpr int CACHE_LINE_SIZE = 64; // TODO: Review that this is the correct value. -static inline void spin_loop_pause() noexcept {} -} // namespace atomic_queue -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace atomic_queue { - -#if defined(__GNUC__) || defined(__clang__) -#define ATOMIC_QUEUE_LIKELY(expr) __builtin_expect(static_cast(expr), 1) -#define ATOMIC_QUEUE_UNLIKELY(expr) __builtin_expect(static_cast(expr), 0) -#define ATOMIC_QUEUE_NOINLINE __attribute__((noinline)) -#else -#define ATOMIC_QUEUE_LIKELY(expr) (expr) -#define ATOMIC_QUEUE_UNLIKELY(expr) (expr) -#define ATOMIC_QUEUE_NOINLINE -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -auto constexpr A = std::memory_order_acquire; -auto constexpr R = std::memory_order_release; -auto constexpr X = std::memory_order_relaxed; -auto constexpr C = std::memory_order_seq_cst; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -} // namespace atomic_queue - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#endif // ATOMIC_QUEUE_DEFS_H_INCLUDED From 34570e42ad768f9c04a79bb0ce3150a8e0793e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Sun, 30 Jul 2023 13:48:08 +0200 Subject: [PATCH 124/174] Add Zipping step to Artifactory Upload --- Profiler/Profiler.vcxproj | 7 ------- Profiler/Profiler.vcxproj.filters | 15 --------------- UploadDaemon/App.config | 12 ++++++------ UploadDaemon/Upload/ArtifactoryUpload.cs | 23 ++++++++++++++++++++++- UploadDaemon/UploadDaemon.csproj | 3 ++- UploadDaemon/packages.config | 4 ++-- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Profiler/Profiler.vcxproj b/Profiler/Profiler.vcxproj index c0ce4092..8e553c6f 100644 --- a/Profiler/Profiler.vcxproj +++ b/Profiler/Profiler.vcxproj @@ -223,7 +223,6 @@ - @@ -267,7 +266,6 @@ - @@ -299,8 +297,6 @@ - - @@ -325,9 +321,6 @@ - - - diff --git a/Profiler/Profiler.vcxproj.filters b/Profiler/Profiler.vcxproj.filters index 56013e99..e3eed45d 100644 --- a/Profiler/Profiler.vcxproj.filters +++ b/Profiler/Profiler.vcxproj.filters @@ -156,9 +156,6 @@ Source Files - - Source Files - @@ -281,15 +278,6 @@ Header Files - - Header Files - - - Header Files - - - Header Files - Header Files @@ -309,7 +297,4 @@ Resource Files - - - \ No newline at end of file diff --git a/UploadDaemon/App.config b/UploadDaemon/App.config index 4fb45cb0..9a0b6843 100644 --- a/UploadDaemon/App.config +++ b/UploadDaemon/App.config @@ -1,7 +1,7 @@ - + - + - + - + diff --git a/UploadDaemon/Upload/ArtifactoryUpload.cs b/UploadDaemon/Upload/ArtifactoryUpload.cs index 4041f7d4..c64d1c56 100644 --- a/UploadDaemon/Upload/ArtifactoryUpload.cs +++ b/UploadDaemon/Upload/ArtifactoryUpload.cs @@ -8,6 +8,7 @@ using UploadDaemon.SymbolAnalysis; using UploadDaemon.Configuration; using UploadDaemon.Report; +using System.IO.Compression; namespace UploadDaemon.Upload { @@ -81,7 +82,7 @@ public async Task UploadLineCoverageAsync(string originalTraceFilePath, IC try { - byte[] reportBytes = Encoding.UTF8.GetBytes(lineCoverageReport.ToString()); + byte[] reportBytes = CreateZipFile(lineCoverageReport.ToString()); using (MemoryStream stream = new MemoryStream(reportBytes)) { return await PerformLineCoverageUpload(originalTraceFilePath, revisionOrTimestamp.Value, url, stream, reportName); @@ -115,6 +116,26 @@ private async Task PerformLineCoverageUpload(string originalTraceFilePath, } } + private static byte[] CreateZipFile(string lineCoverageReport) + { + byte[] compressedBytes; + byte[] reportBytes = Encoding.UTF8.GetBytes(lineCoverageReport); + using (var outStream = new MemoryStream()) + { + using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true)) + { + var fileInArchive = archive.CreateEntry("coverage.txt"); + using (var entryStream = fileInArchive.Open()) + using (var fileToCompressStream = new MemoryStream(reportBytes)) + { + fileToCompressStream.CopyTo(entryStream); + } + } + compressedBytes = outStream.ToArray(); + } + return compressedBytes; + } + /// public object GetTargetId() { diff --git a/UploadDaemon/UploadDaemon.csproj b/UploadDaemon/UploadDaemon.csproj index db846fbc..b6e8eb0d 100644 --- a/UploadDaemon/UploadDaemon.csproj +++ b/UploadDaemon/UploadDaemon.csproj @@ -8,9 +8,10 @@ WinExe UploadDaemon UploadDaemon - v4.6.1 + v4.8 512 true + AnyCPU diff --git a/UploadDaemon/packages.config b/UploadDaemon/packages.config index e52978e7..56b12a4f 100644 --- a/UploadDaemon/packages.config +++ b/UploadDaemon/packages.config @@ -15,6 +15,6 @@ - + - + \ No newline at end of file From eb09de39e307dff67d699c7be765452d0b22375c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Sun, 30 Jul 2023 22:28:13 +0200 Subject: [PATCH 125/174] Fix Artifactory Upload --- UploadDaemon/Upload/ArtifactoryUpload.cs | 20 +++++--------------- UploadDaemon/Upload/HttpClientUtils.cs | 8 +++----- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/UploadDaemon/Upload/ArtifactoryUpload.cs b/UploadDaemon/Upload/ArtifactoryUpload.cs index c64d1c56..ed6bfe76 100644 --- a/UploadDaemon/Upload/ArtifactoryUpload.cs +++ b/UploadDaemon/Upload/ArtifactoryUpload.cs @@ -58,24 +58,17 @@ public async Task UploadLineCoverageAsync(string originalTraceFilePath, IC string url = $"{artifactory.Url}/uploads/{branchAndTimestamp[0]}/{branchAndTimestamp[1]}"; if (lineCoverageReport.UploadFormat == "SIMPLE") { - url = $"{url}/{artifactory.Partition}/simple"; + url = $"{url}/{artifactory.Partition}/SIMPLE"; } else { - url = $"{url}/{artifactory.Partition}/testwise"; + url = $"{url}/{artifactory.Partition}/TESTWISE_COVERAGE"; } if (artifactory.PathSuffix != null) { string encodedPathSuffix = HttpUtility.UrlEncode(artifactory.PathSuffix); url = $"{url}/{encodedPathSuffix}"; } - String reportName = ""; - if (lineCoverageReport.UploadFormat == "SIMPLE") - { - reportName = "report.simple"; - } else - { - reportName = "report.testwise"; - } + String reportName = "report.zip"; url = $"{url}/{reportName}"; logger.Debug("Uploading line coverage from {trace} to {artifactory} ({url})", originalTraceFilePath, artifactory.ToString(), url); @@ -83,10 +76,7 @@ public async Task UploadLineCoverageAsync(string originalTraceFilePath, IC try { byte[] reportBytes = CreateZipFile(lineCoverageReport.ToString()); - using (MemoryStream stream = new MemoryStream(reportBytes)) - { - return await PerformLineCoverageUpload(originalTraceFilePath, revisionOrTimestamp.Value, url, stream, reportName); - } + return await PerformLineCoverageUpload(originalTraceFilePath, revisionOrTimestamp.Value, url, reportBytes, reportName); } catch (Exception e) { @@ -96,7 +86,7 @@ public async Task UploadLineCoverageAsync(string originalTraceFilePath, IC } } - private async Task PerformLineCoverageUpload(string originalTraceFilePath, string timestampValue, string url, MemoryStream stream, String reportName) + private async Task PerformLineCoverageUpload(string originalTraceFilePath, string timestampValue, string url, byte[] stream, String reportName) { using (HttpResponseMessage response = await HttpClientUtils.UploadMultiPartPut(client, url, "report", stream, reportName)) { diff --git a/UploadDaemon/Upload/HttpClientUtils.cs b/UploadDaemon/Upload/HttpClientUtils.cs index 672cbed9..c4fe6aa8 100644 --- a/UploadDaemon/Upload/HttpClientUtils.cs +++ b/UploadDaemon/Upload/HttpClientUtils.cs @@ -96,17 +96,15 @@ public static async Task UploadMultiPart(HttpClient client, } /// - /// Uploads the given file in a multi-part request. + /// Uploads the given file in a put request. /// /// The HTTP response. The caller must dispose of it. /// In case there are network or file system errors. /// In case there are network errors. - public static async Task UploadMultiPartPut(HttpClient client, string url, string multipartParameterName, Stream stream, string fileName) + public static async Task UploadMultiPartPut(HttpClient client, string url, string multipartParameterName, byte[] stream, string fileName) { - using (MultipartFormDataContent content = new MultipartFormDataContent("Upload----" + DateTime.Now.Ticks.ToString("x"))) + using (ByteArrayContent content = new ByteArrayContent(stream)) { - content.Add(new StreamContent(stream), multipartParameterName, fileName); - return await client.PutAsync(url, content); } } From 7e6b4ee09b13f371377516f7d368f88fe3e74670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20N=C3=B6mmer?= Date: Sun, 30 Jul 2023 22:53:18 +0200 Subject: [PATCH 126/174] Improve artifactory branches --- UploadDaemon/Upload/ArtifactoryUpload.cs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/UploadDaemon/Upload/ArtifactoryUpload.cs b/UploadDaemon/Upload/ArtifactoryUpload.cs index ed6bfe76..8d3da7f4 100644 --- a/UploadDaemon/Upload/ArtifactoryUpload.cs +++ b/UploadDaemon/Upload/ArtifactoryUpload.cs @@ -9,6 +9,7 @@ using UploadDaemon.Configuration; using UploadDaemon.Report; using System.IO.Compression; +using UploadDaemon.Report.Testwise; namespace UploadDaemon.Upload { @@ -56,13 +57,6 @@ public async Task UploadLineCoverageAsync(string originalTraceFilePath, IC } string[] branchAndTimestamp = revisionOrTimestamp.Value.Split(':'); string url = $"{artifactory.Url}/uploads/{branchAndTimestamp[0]}/{branchAndTimestamp[1]}"; - if (lineCoverageReport.UploadFormat == "SIMPLE") - { - url = $"{url}/{artifactory.Partition}/SIMPLE"; - } else - { - url = $"{url}/{artifactory.Partition}/TESTWISE_COVERAGE"; - } if (artifactory.PathSuffix != null) { string encodedPathSuffix = HttpUtility.UrlEncode(artifactory.PathSuffix); @@ -73,9 +67,19 @@ public async Task UploadLineCoverageAsync(string originalTraceFilePath, IC logger.Debug("Uploading line coverage from {trace} to {artifactory} ({url})", originalTraceFilePath, artifactory.ToString(), url); + string covFileName = ""; + if (lineCoverageReport.UploadFormat == "SIMPLE") + { + covFileName = $"{artifactory.Partition}/simple.txt"; + } + else + { + covFileName = $"{artifactory.Partition}/testwise.json"; + } + try { - byte[] reportBytes = CreateZipFile(lineCoverageReport.ToString()); + byte[] reportBytes = CreateZipFile(lineCoverageReport.ToString(), covFileName); return await PerformLineCoverageUpload(originalTraceFilePath, revisionOrTimestamp.Value, url, reportBytes, reportName); } catch (Exception e) @@ -106,7 +110,7 @@ private async Task PerformLineCoverageUpload(string originalTraceFilePath, } } - private static byte[] CreateZipFile(string lineCoverageReport) + private static byte[] CreateZipFile(string lineCoverageReport, string entryName) { byte[] compressedBytes; byte[] reportBytes = Encoding.UTF8.GetBytes(lineCoverageReport); @@ -114,7 +118,7 @@ private static byte[] CreateZipFile(string lineCoverageReport) { using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true)) { - var fileInArchive = archive.CreateEntry("coverage.txt"); + var fileInArchive = archive.CreateEntry(entryName); using (var entryStream = fileInArchive.Open()) using (var fileToCompressStream = new MemoryStream(reportBytes)) { From 13a4cc6bbf392322b764c7471cc2969e09b5bf1c Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 1 Aug 2023 07:08:14 -0700 Subject: [PATCH 127/174] add dialog for recording test duration --- Commander/App.xaml.cs | 4 +- Commander/Commander.csproj | 9 ++ Commander/TestDurationDialog.xaml | 17 ++++ Commander/TestDurationDialog.xaml.cs | 22 ++++ Commander/TestDurationDialogVM.cs | 94 +++++++++++++++++ CommanderTests/CommanderTests.csproj | 106 ++++++++++++++++++++ CommanderTests/Properties/AssemblyInfo.cs | 36 +++++++ CommanderTests/TestDurationDialogVMTests.cs | 53 ++++++++++ CommanderTests/packages.config | 5 + Commons/Ipc/ProfilerIpc.cs | 4 +- Cqse.Teamscale.Profiler.Dotnet.sln | 20 +++- Profiler/Profiler.vcxproj | 2 +- 12 files changed, 364 insertions(+), 8 deletions(-) create mode 100644 Commander/TestDurationDialog.xaml create mode 100644 Commander/TestDurationDialog.xaml.cs create mode 100644 Commander/TestDurationDialogVM.cs create mode 100644 CommanderTests/CommanderTests.csproj create mode 100644 CommanderTests/Properties/AssemblyInfo.cs create mode 100644 CommanderTests/TestDurationDialogVMTests.cs create mode 100644 CommanderTests/packages.config diff --git a/Commander/App.xaml.cs b/Commander/App.xaml.cs index c01aef84..85cd460d 100644 --- a/Commander/App.xaml.cs +++ b/Commander/App.xaml.cs @@ -20,9 +20,9 @@ protected override void OnExit(ExitEventArgs e) profilerIpc.Dispose(); } - internal void EndTest(TestExecutionResult result) + internal void EndTest(TestExecutionResult result, string message = "", long durationMs = 0) { - profilerIpc.EndTest(result); + profilerIpc.EndTest(result, message, durationMs); } internal void StartTest(string testName = null) diff --git a/Commander/Commander.csproj b/Commander/Commander.csproj index fabd10da..41c45702 100644 --- a/Commander/Commander.csproj +++ b/Commander/Commander.csproj @@ -95,6 +95,7 @@ ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + @@ -113,6 +114,14 @@ MSBuild:Compile Designer + + TestDurationDialog.xaml + + + + MSBuild:Compile + Designer + MSBuild:Compile Designer diff --git a/Commander/TestDurationDialog.xaml b/Commander/TestDurationDialog.xaml new file mode 100644 index 00000000..5c6a07bf --- /dev/null +++ b/Commander/TestDurationDialog.xaml @@ -0,0 +1,17 @@ + + +