diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc2cbadd..092d69b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,7 @@ jobs: - { name: Ruby, id: ruby, folder: Ruby } - { name: Lua, id: lua, folder: Lua } - { name: Python, id: python, folder: Python } + - { name: C++, id: cpp, folder: C++ } name: ${{ matrix.name }} steps: @@ -39,6 +40,14 @@ jobs: ~/.asdf/bin/asdf plugin add awfy https://github.com/smarr/asdf-awfy.git if: matrix.id == 'squeak' || matrix.id == 'pharo' + - name: Install Clang 17 + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + sudo add-apt-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main" + sudo apt-get update + sudo apt-get install -y clang-17 clang-format-17 clang-tidy-17 + if: matrix.id == 'cpp' + - name: Install Crystal if: matrix.id == 'crystal' uses: oprypin/install-crystal@v1 diff --git a/benchmarks/C++/.clang-format b/benchmarks/C++/.clang-format new file mode 100644 index 00000000..75121a6b --- /dev/null +++ b/benchmarks/C++/.clang-format @@ -0,0 +1,259 @@ +--- +Language: Cpp +# BasedOnStyle: Chromium +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: false +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAfterAttributes: Never +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: "^<.*" + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: ".*" + Priority: 3 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: "([-_](test|unittest))?$" +IncludeIsMainSourceRegex: "" +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: true +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: true +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: NextLine +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +PPIndentWidth: -1 +QualifierAlignment: Leave +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - "c++" + - "C++" + CanonicalDelimiter: "" + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + - ParseTestProto + - ParsePartialTestProto + CanonicalDelimiter: pb + BasedOnStyle: google +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +--- + diff --git a/benchmarks/C++/.clang-leak.txt b/benchmarks/C++/.clang-leak.txt new file mode 100644 index 00000000..aa89e219 --- /dev/null +++ b/benchmarks/C++/.clang-leak.txt @@ -0,0 +1,2 @@ +leak:_fetchInitializingClassList +leak:libobjc \ No newline at end of file diff --git a/benchmarks/C++/.clang-tidy b/benchmarks/C++/.clang-tidy new file mode 100644 index 00000000..abc54eb8 --- /dev/null +++ b/benchmarks/C++/.clang-tidy @@ -0,0 +1,65 @@ +--- +Checks: '*, + -modernize-use-trailing-return-type, + -altera-id-dependent-backward-branch, + -altera-unroll-loops, + -bugprone-easily-swappable-parameters, + -bugprone-exception-escape, + -concurrency-mt-unsafe, + -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-avoid-do-while, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-avoid-non-const-global-variables + -cppcoreguidelines-non-private-member-variables-in-classes, + -cppcoreguidelines-pro-bounds-constant-array-index, + -fuchsia-default-arguments-calls, + -fuchsia-default-arguments-declarations, + -fuchsia-statically-constructed-objects, + -fuchsia-virtual-inheritance, + -google-global-names-in-headers, + -hicpp-named-parameter, + -hicpp-use-auto, + -llvm-header-guard, + -llvm-include-order, + -llvmlibc-*, + -misc-no-recursion, + -misc-non-private-member-variables-in-classes, + -misc-use-anonymous-namespace, + -readability-delete-null-pointer, + -readability-function-cognitive-complexity, + -readability-convert-member-functions-to-static, + -readability-identifier-length, + -readability-magic-numbers, + -readability-named-parameter, + -readability-simplify-boolean-expr, + -cppcoreguidelines-special-member-functions, + -hicpp-special-member-functions, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-owning-memory, + ' +WarningsAsErrors: '' +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +FormatStyle: none +User: smarr +CheckOptions: + llvm-else-after-return.WarnOnConditionVariables: 'false' + modernize-loop-convert.MinConfidence: reasonable + modernize-replace-auto-ptr.IncludeStyle: llvm + cert-str34-c.DiagnoseSignedUnsignedCharComparisons: 'false' + google-readability-namespace-comments.SpacesBeforeComments: '2' + cert-err33-c.CheckedFunctions: '::aligned_alloc;::asctime_s;::at_quick_exit;::atexit;::bsearch;::bsearch_s;::btowc;::c16rtomb;::c32rtomb;::calloc;::clock;::cnd_broadcast;::cnd_init;::cnd_signal;::cnd_timedwait;::cnd_wait;::ctime_s;::fclose;::fflush;::fgetc;::fgetpos;::fgets;::fgetwc;::fopen;::fopen_s;::fprintf;::fprintf_s;::fputc;::fputs;::fputwc;::fputws;::fread;::freopen;::freopen_s;::fscanf;::fscanf_s;::fseek;::fsetpos;::ftell;::fwprintf;::fwprintf_s;::fwrite;::fwscanf;::fwscanf_s;::getc;::getchar;::getenv;::getenv_s;::gets_s;::getwc;::getwchar;::gmtime;::gmtime_s;::localtime;::localtime_s;::malloc;::mbrtoc16;::mbrtoc32;::mbsrtowcs;::mbsrtowcs_s;::mbstowcs;::mbstowcs_s;::memchr;::mktime;::mtx_init;::mtx_lock;::mtx_timedlock;::mtx_trylock;::mtx_unlock;::printf_s;::putc;::putwc;::raise;::realloc;::remove;::rename;::scanf;::scanf_s;::setlocale;::setvbuf;::signal;::snprintf;::snprintf_s;::sprintf;::sprintf_s;::sscanf;::sscanf_s;::strchr;::strerror_s;::strftime;::strpbrk;::strrchr;::strstr;::strtod;::strtof;::strtoimax;::strtok;::strtok_s;::strtol;::strtold;::strtoll;::strtoul;::strtoull;::strtoumax;::strxfrm;::swprintf;::swprintf_s;::swscanf;::swscanf_s;::thrd_create;::thrd_detach;::thrd_join;::thrd_sleep;::time;::timespec_get;::tmpfile;::tmpfile_s;::tmpnam;::tmpnam_s;::tss_create;::tss_get;::tss_set;::ungetc;::ungetwc;::vfprintf;::vfprintf_s;::vfscanf;::vfscanf_s;::vfwprintf;::vfwprintf_s;::vfwscanf;::vfwscanf_s;::vprintf_s;::vscanf;::vscanf_s;::vsnprintf;::vsnprintf_s;::vsprintf;::vsprintf_s;::vsscanf;::vsscanf_s;::vswprintf;::vswprintf_s;::vswscanf;::vswscanf_s;::vwprintf_s;::vwscanf;::vwscanf_s;::wcrtomb;::wcschr;::wcsftime;::wcspbrk;::wcsrchr;::wcsrtombs;::wcsrtombs_s;::wcsstr;::wcstod;::wcstof;::wcstoimax;::wcstok;::wcstok_s;::wcstol;::wcstold;::wcstoll;::wcstombs;::wcstombs_s;::wcstoul;::wcstoull;::wcstoumax;::wcsxfrm;::wctob;::wctrans;::wctype;::wmemchr;::wprintf_s;::wscanf;::wscanf_s;' + cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField: 'false' + cert-dcl16-c.NewSuffixes: 'L;LL;LU;LLU' + google-readability-braces-around-statements.ShortStatementLines: '1' + modernize-pass-by-value.IncludeStyle: llvm + google-readability-namespace-comments.ShortNamespaceLines: '10' + modernize-loop-convert.MaxCopySize: '16' + modernize-use-nullptr.NullMacros: 'NULL' + llvm-qualified-auto.AddConstToQualified: 'false' + cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: 'true' + modernize-loop-convert.NamingStyle: CamelCase + llvm-else-after-return.WarnOnUnfixable: 'false' + google-readability-function-size.StatementThreshold: '800' +... + diff --git a/benchmarks/C++/.gitignore b/benchmarks/C++/.gitignore new file mode 100644 index 00000000..eca8fb02 --- /dev/null +++ b/benchmarks/C++/.gitignore @@ -0,0 +1,2 @@ +.vscode +/harness* diff --git a/benchmarks/C++/build.sh b/benchmarks/C++/build.sh new file mode 100755 index 00000000..fde78e37 --- /dev/null +++ b/benchmarks/C++/build.sh @@ -0,0 +1,160 @@ +#!/bin/bash +SCRIPT_PATH="$(dirname "$0")" +source "$SCRIPT_PATH/../script.inc" + +if [ -z "$OPT" ]; then + OPT='-O3 -flto -march=native' +fi +NAME_OPT="${OPT//[[:blank:]]/}" +NAME_OPT="${NAME_OPT//flto/lto}" + +# start by trying to find a suitable clang +CMD_VERSION='-mp-17' + +if ! [ -x "$(command -v clang++$CMD_VERSION)" ]; then + CMD_VERSION='-17' + if ! [ -x "$(command -v clang++$CMD_VERSION)" ]; then + CMD_VERSION='-15' + fi +fi + +if [ -z "$CXX" ]; then + CMD="clang++$CMD_VERSION" + CXX="$CMD" + + # trying to avoid bugs, CXX should be used at this point + unset CMD +fi + +if [[ $CXX == *"clang"* ]]; then + COMPILER_NAME="clang" + WARNINGS="-Wall -Wextra -Wno-unused-private-field" +else + # we assume gcc + COMPILER_NAME="gcc" + WARNINGS="-Wall -Wextra" +fi +COMP_OPT="-ffp-contract=off -std=c++17" + +echo "Using compiler type: $COMPILER_NAME" + +SRC='src/harness.cpp src/deltablue.cpp src/memory/object_tracker.cpp src/richards.cpp' + +BENCHMARKS=("NBody 10 250000" + "Richards 10 100" + "DeltaBlue 10 1200" + "Mandelbrot 10 500" + "Queens 10 1000" + "Towers 10 600" + "Bounce 10 1500" + "CD 10 250" + "Json 10 100" + "List 10 1500" + "Storage 10 1000" + "Sieve 10 3000" + "Mandelbrot 10 500" + "Permute 10 1000" + "Bounce 10 1500" + "Mandelbrot 10 500" + "Havlak 10 1500") + +pushd "$SCRIPT_PATH" + +if [ "$1" = "style" ] +then + INFO Check Format + ./build.sh check-format + FMT_EXIT=$? + + INFO Run Lint + ./build.sh lint + LNT_EXIT=$? + exit $((FMT_EXIT + LNT_EXIT)) +fi + +if [ "$1" = "format" ] +then + CMD=clang-format$CMD_VERSION + type -P "$CMD" || CMD=clang-format + exec $CMD -i --style=file src/*.cpp src/*.h src/**/*.cpp src/**/*.h +fi + +if [ "$1" = "check-format" ] +then + CMD=clang-format$CMD_VERSION + type -P "$CMD" || CMD=clang-format + exec $CMD --style=file --dry-run --Werror src/*.cpp src/*.h src/**/*.cpp src/**/*.h +fi + +if [ "$1" = "lint" ] +then + CMD=clang-tidy$CMD_VERSION + type -P "$CMD" || CMD=clang-tidy + exec $CMD --config-file=.clang-tidy -header-filter=.* src/*.cpp +fi + +if [ "$1" = "leak-san" ] +then + echo Bulding with Leak Sanitizer enabled + # then run with LSAN_OPTIONS=suppressions=.clang-leak.txt ./harness Towers 20 1000 + SANATIZE='-g -fsanitize=leak' + OPT='-Og' +elif [ "$1" = "address-san" ] +then + echo Bulding with Address Sanitizer enabled + SANATIZE='-g -fsanitize=address' + OPT='-Og' +elif [ "$1" = "memory-san" ] +then + echo Bulding with Memory Sanitizer enabled + SANATIZE='-g -fsanitize=memory' + OPT='-Og' +elif [ "$1" = "undefined-san" ] +then + echo Bulding with Undefined Behavior Sanitizer enabled + SANATIZE='-g -fsanitize=undefined' + OPT='-Og' +elif [ "$1" = "pedantic" ] +then + DISABLED_WARNINGS='-Wno-poison-system-directories -Wno-c++98-compat + -Wno-shadow + -Wno-shadow-field-in-constructor -Wno-unused-private-field + -Wno-padded + -Wno-global-constructors + -Wno-exit-time-destructors + -Wno-float-equal + -Wno-sign-conversion + -Wno-unsafe-buffer-usage -Wno-weak-vtables' + SANATIZE="-Weverything -pedantic -Wall -Wextra $DISABLED_WARNINGS" + echo Bulding with pedantic warnings and $OPT optimizations +elif [ "$1" = "pgo" ] +then + echo Bulding with PGO optimizations + ORG_OPT="$OPT" + + OPT="$ORG_OPT -fprofile-generate" + $CXX $WARNINGS $SANATIZE $OPT $COMP_OPT $SRC -o harness-$CXX + + for b in "${BENCHMARKS[@]}"; do + LLVM_PROFILE_FILE="prof-%p.profraw" ./harness-$CXX $b + done + + if [ "$COMPILER_NAME" = "clang" ]; then + llvm-profdata$CMD_VERSION merge -output=prof.profdata prof-*.profraw + OPT="$ORG_OPT -fprofile-use=prof.profdata" + else + OPT="$ORG_OPT -fprofile-use" + fi + + $CXX $WARNINGS $SANATIZE $OPT $COMP_OPT $SRC -o harness-$CXX$NAME_OPT-pgo + EXIT_CODE=$? + rm -f *.profraw prof.profdata *.gcda + exit $EXIT_CODE +else + echo Bulding with $OPT optimizations + SANATIZE='' +fi + +echo Binary name: harness-$CXX$NAME_OPT +eval $CXX $WARNINGS $SANATIZE $OPT $COMP_OPT $SRC -o harness-$CXX$NAME_OPT +ln -sf harness-$CXX$NAME_OPT harness diff --git a/benchmarks/C++/src/benchmark.h b/benchmarks/C++/src/benchmark.h new file mode 100644 index 00000000..7d0f9c03 --- /dev/null +++ b/benchmarks/C++/src/benchmark.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +class Benchmark { + public: + virtual ~Benchmark() = default; + + virtual std::any benchmark() = 0; + virtual bool verify_result(std::any result) = 0; + + virtual bool inner_benchmark_loop(int32_t inner_iterations) { + for (int32_t i = 0; i < inner_iterations; i += 1) { + if (!verify_result(benchmark())) { + return false; + } + } + return true; + } +}; diff --git a/benchmarks/C++/src/bounce.h b/benchmarks/C++/src/bounce.h new file mode 100644 index 00000000..f2839daa --- /dev/null +++ b/benchmarks/C++/src/bounce.h @@ -0,0 +1,89 @@ +#pragma once + +#include "benchmark.h" +#include "som/random.h" + +#include +#include + +class Ball { + private: + int32_t x; + int32_t y; + int32_t x_vel; + int32_t y_vel; + + public: + Ball() = default; + + Ball(Ball&&) = default; + + ~Ball() = default; + + explicit Ball(Random& random) + : x(random.next() % 500), + y(random.next() % 500), + x_vel((random.next() % 300) - 150), + y_vel((random.next() % 300) - 150) {} + + Ball& operator=(const Ball&) = default; + + bool bounce() { + const int32_t x_limit = 500; + const int32_t y_limit = 500; + bool bounced = false; + + x += x_vel; + y += y_vel; + if (x > x_limit) { + x = x_limit; + x_vel = 0 - abs(x_vel); + bounced = true; + } + if (x < 0) { + x = 0; + x_vel = abs(x_vel); + bounced = true; + } + if (y > y_limit) { + y = y_limit; + y_vel = 0 - abs(y_vel); + bounced = true; + } + if (y < 0) { + y = 0; + y_vel = abs(y_vel); + bounced = true; + } + return bounced; + } +}; + +class Bounce : public Benchmark { + public: + std::any benchmark() override { + Random random; + + const int32_t ball_count = 100; + int32_t bounces = 0; + + std::array balls = {}; + + for (auto& ball : balls) { + ball = Ball(random); + } + + for (int32_t j = 0; j < 50; j += 1) { + for (auto& ball : balls) { + if (ball.bounce()) { + bounces += 1; + } + } + } + return bounces; + } + + bool verify_result(std::any result) override { + return 1331 == std::any_cast(result); + } +}; diff --git a/benchmarks/C++/src/cd.h b/benchmarks/C++/src/cd.h new file mode 100644 index 00000000..75525f15 --- /dev/null +++ b/benchmarks/C++/src/cd.h @@ -0,0 +1,952 @@ +#pragma once + +#include +#include +#include +#include + +#include "benchmark.h" +#include "som/error.h" +#include "som/vector.h" + +class Vector2D { + private: + static int32_t compareNumbers(double a, double b) { + if (a == b) { + return 0; + } + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + + // We say that NaN is smaller than non-NaN. + if (a == a) { + return 1; + } + return -1; + } + + public: + const double x; + const double y; + + constexpr Vector2D(double x, double y) : x(x), y(y) {} + + [[nodiscard]] Vector2D plus(const Vector2D& other) const { + return {x + other.x, y + other.y}; + } + + [[nodiscard]] Vector2D minus(const Vector2D& other) const { + return {x - other.x, y - other.y}; + } + + [[nodiscard]] int32_t compareTo(const Vector2D& other) const { + const int32_t result = compareNumbers(x, other.x); + + if (result != 0) { + return result; + } + + return compareNumbers(y, other.y); + } +}; + +class Vector3D { + public: + double x; + double y; + double z; + + constexpr Vector3D(double x, double y, double z) : x(x), y(y), z(z) {} + constexpr Vector3D(const Vector3D& other) = default; + + Vector3D() = default; + + Vector3D& operator=(const Vector3D& other) = default; + + [[nodiscard]] Vector3D plus(const Vector3D& other) const { + return {x + other.x, y + other.y, z + other.z}; + } + + [[nodiscard]] Vector3D minus(const Vector3D& other) const { + return {x - other.x, y - other.y, z - other.z}; + } + + [[nodiscard]] double dot(const Vector3D& other) const { + return x * other.x + y * other.y + z * other.z; + } + + [[nodiscard]] double squaredMagnitude() const { return dot(*this); } + [[nodiscard]] double magnitude() const { return sqrt(squaredMagnitude()); } + + [[nodiscard]] Vector3D times(double amount) const { + return {x * amount, y * amount, z * amount}; + } +}; + +template +class RedBlackTree { + private: + enum Color { RED = 0, BLACK = 1 }; + + class Node { + friend class RedBlackTree; + + private: + K _key; + V _value; + Node* _left{nullptr}; + Node* _right{nullptr}; + Node* _parent{nullptr}; + Color _color{RED}; + + Node* successor() { + Node* x = this; + if (x->_right != nullptr) { + return treeMinimum(x->_right); + } + Node* y = x->_parent; + while (y != nullptr && x == y->_right) { + x = y; + y = y->_parent; + } + return y; + } + + Node(K key, V value) : _key(key), _value(value) {} + ~Node() { + delete _left; + delete _right; + } + }; + + class InsertResult { + public: + const bool isNewEntry; + Node* const newNode; + V oldValue; + + InsertResult(bool isNewEntry, Node* newNode, V oldValue) + : isNewEntry(isNewEntry), newNode(newNode), oldValue(oldValue) {} + }; + + Node* _root{nullptr}; + + static Node* treeMinimum(Node* x) { + Node* current = x; + while (current->_left != nullptr) { + current = current->_left; + } + return current; + } + + InsertResult treeInsert(K key, V value) { + Node* y = nullptr; + Node* x = _root; + + while (x != nullptr) { + y = x; + const int32_t comparisonResult = key.compareTo(x->_key); + if (comparisonResult < 0) { + x = x->_left; + } else if (comparisonResult > 0) { + x = x->_right; + } else { + V oldValue = x->_value; + x->_value = value; + return InsertResult(false, nullptr, oldValue); + } + } + + Node* z = new Node(key, value); + z->_parent = y; + if (y == nullptr) { + _root = z; + } else { + if (key.compareTo(y->_key) < 0) { + y->_left = z; + } else { + y->_right = z; + } + } + return InsertResult(true, z, V()); + } + + Node* leftRotate(Node* x) { + Node* y = x->_right; + + // Turn y's left subtree into x's right subtree. + x->_right = y->_left; + if (y->_left != nullptr) { + y->_left->_parent = x; + } + + // Link x's parent to y + y->_parent = x->_parent; + if (x->_parent == nullptr) { + _root = y; + } else { + if (x == x->_parent->_left) { + x->_parent->_left = y; + } else { + x->_parent->_right = y; + } + } + + // Put x on y's left. + y->_left = x; + x->_parent = y; + + return y; + } + + Node* rightRotate(Node* y) { + Node* x = y->_left; + + // Turn x's right subtree into y's left subtree. + y->_left = x->_right; + if (x->_right != nullptr) { + x->_right->_parent = y; + } + + // Link y's parent to x; + x->_parent = y->_parent; + if (y->_parent == nullptr) { + _root = x; + } else { + if (y == y->_parent->_left) { + y->_parent->_left = x; + } else { + y->_parent->_right = x; + } + } + + x->_right = y; + y->_parent = x; + + return x; + } + + void removeFixup(Node* x, Node* xParent) { + while (x != _root && (x == nullptr || x->_color == BLACK)) { + if (x == xParent->_left) { + // Note: the text points out that w cannot be null-> The reason is not + // obvious from simply looking at the code; it comes about from the + // properties of the red-black tree. + Node* w = xParent->_right; + if (w->_color == RED) { + // Case 1 + w->_color = BLACK; + xParent->_color = RED; + leftRotate(xParent); + w = xParent->_right; + } + if ((w->_left == nullptr || w->_left->_color == BLACK) && + (w->_right == nullptr || w->_right->_color == BLACK)) { + // Case 2 + w->_color = RED; + x = xParent; + xParent = x->_parent; + } else { + if (w->_right == nullptr || w->_right->_color == BLACK) { + // Case 3 + w->_left->_color = BLACK; + w->_color = RED; + rightRotate(w); + w = xParent->_right; + } + // Case 4 + w->_color = xParent->_color; + xParent->_color = BLACK; + if (w->_right != nullptr) { + w->_right->_color = BLACK; + } + leftRotate(xParent); + x = _root; + xParent = x->_parent; + } + } else { + // Same as "then" clause with "right" and "left" exchanged. + Node* w = xParent->_left; + if (w->_color == RED) { + // Case 1 + w->_color = BLACK; + xParent->_color = RED; + rightRotate(xParent); + w = xParent->_left; + } + if ((w->_right == nullptr || w->_right->_color == BLACK) && + (w->_left == nullptr || w->_left->_color == BLACK)) { + // Case 2 + w->_color = RED; + x = xParent; + xParent = x->_parent; + } else { + if (w->_left == nullptr || w->_left->_color == BLACK) { + // Case 3 + w->_right->_color = BLACK; + w->_color = RED; + leftRotate(w); + w = xParent->_left; + } + // Case 4 + w->_color = xParent->_color; + xParent->_color = BLACK; + if (w->_left != nullptr) { + w->_left->_color = BLACK; + } + rightRotate(xParent); + x = _root; + xParent = x->_parent; + } + } + } + if (x != nullptr) { + x->_color = BLACK; + } + } + + public: + class Entry { + public: + K const key; + V const value; + + Entry(K key, V value) : key(key), value(value) {} + }; + + RedBlackTree() = default; + ~RedBlackTree() { delete _root; } + + std::optional put(K key, V value) { + InsertResult insertionResult = treeInsert(key, value); + + if (!insertionResult.isNewEntry) { + return insertionResult.oldValue; + } + Node* x = insertionResult.newNode; + + while (x != _root && x->_parent->_color == RED) { + if (x->_parent == x->_parent->_parent->_left) { + Node* y = x->_parent->_parent->_right; + if (y != nullptr && y->_color == RED) { + // Case 1 + x->_parent->_color = BLACK; + y->_color = BLACK; + x->_parent->_parent->_color = RED; + x = x->_parent->_parent; + } else { + if (x == x->_parent->_right) { + // Case 2 + x = x->_parent; + leftRotate(x); + } + // Case 3 + x->_parent->_color = BLACK; + x->_parent->_parent->_color = RED; + rightRotate(x->_parent->_parent); + } + } else { + // Same as "then" clause with "right" and "left" exchanged. + Node* y = x->_parent->_parent->_left; + if (y != nullptr && y->_color == RED) { + // Case 1 + x->_parent->_color = BLACK; + y->_color = BLACK; + x->_parent->_parent->_color = RED; + x = x->_parent->_parent; + } else { + if (x == x->_parent->_left) { + // Case 2 + x = x->_parent; + rightRotate(x); + } + // Case 3 + x->_parent->_color = BLACK; + x->_parent->_parent->_color = RED; + leftRotate(x->_parent->_parent); + } + } + } + _root->_color = BLACK; + return {}; + } + + std::optional remove(K key) { + Node* z = findNode(key); + if (z == nullptr) { + return {}; + } + + // Y is the node to be unlinked from the tree. + Node* y = nullptr; + if (z->_left == nullptr || z->_right == nullptr) { + y = z; + } else { + y = z->successor(); + } + + // Y is guaranteed to be non-null at this point. + Node* x = nullptr; + if (y->_left != nullptr) { + x = y->_left; + } else { + x = y->_right; + } + // X is the child of y which might potentially replace y in the tree. X + // might be null at this point. + Node* xParent = nullptr; + if (x != nullptr) { + x->_parent = y->_parent; + xParent = x->_parent; + } else { + xParent = y->_parent; + } + if (y->_parent == nullptr) { + _root = x; + } else { + if (y == y->_parent->_left) { + y->_parent->_left = x; + } else { + y->_parent->_right = x; + } + } + + if (y != z) { + if (y->_color == BLACK) { + removeFixup(x, xParent); + } + + y->_parent = z->_parent; + y->_color = z->_color; + y->_left = z->_left; + y->_right = z->_right; + + if (z->_left != nullptr) { + z->_left->_parent = y; + } + if (z->_right != nullptr) { + z->_right->_parent = y; + } + if (z->_parent != nullptr) { + if (z->_parent->_left == z) { + z->_parent->_left = y; + } else { + z->_parent->_right = y; + } + } else { + _root = y; + } + } else if (y->_color == BLACK) { + removeFixup(x, xParent); + } + + return z->_value; + } + + V* get(K key) { + Node* node = findNode(key); + if (node == nullptr) { + return nullptr; + } + return &node->_value; + } + + void forEach(std::function fn) { + if (_root == nullptr) { + return; + } + Node* current = treeMinimum(_root); + while (current != nullptr) { + Entry entry{current->_key, current->_value}; + fn(entry); + current = current->successor(); + } + } + + void destroyValues() { + if (_root == nullptr) { + return; + } + Node* current = treeMinimum(_root); + while (current != nullptr) { + Entry entry{current->_key, current->_value}; + delete entry.value; + current = current->successor(); + } + } + + Node* findNode(K key) { + Node* current = _root; + while (current != nullptr) { + const int32_t comparisonResult = key.compareTo(current->_key); + if (comparisonResult == 0) { + return current; + } + if (comparisonResult < 0) { + current = current->_left; + } else { + current = current->_right; + } + } + return nullptr; + } +}; + +class CallSign { + private: + int32_t _value; + + public: + explicit CallSign(int32_t value) : _value(value) {} + CallSign(const CallSign& other) = default; + + CallSign() = default; + + CallSign& operator=(const CallSign& other) = default; + + [[nodiscard]] int32_t compareTo(const CallSign& other) const { + return (_value == other._value) ? 0 : ((_value < other._value) ? -1 : 1); + } +}; + +class Collision { + public: + CallSign aircraftA; + CallSign aircraftB; + Vector3D position; + + Collision(const CallSign& aircraftA, + const CallSign& aircraftB, + const Vector3D& position) + : aircraftA(aircraftA), aircraftB(aircraftB), position(position) {} + + explicit Collision() = default; +}; + +class Constants { + public: + constexpr static const double MIN_X = 0.0; + constexpr static const double MIN_Y = 0.0; + constexpr static const double MAX_X = 1000.0; + constexpr static const double MAX_Y = 1000.0; + constexpr static const double MIN_Z = 0.0; + constexpr static const double MAX_Z = 10.0; + constexpr static const double PROXIMITY_RADIUS = 1.0; + constexpr static const double GOOD_VOXEL_SIZE = PROXIMITY_RADIUS * 2.0; +}; + +class Motion { + private: + [[nodiscard]] Vector3D delta() const { return posTwo.minus(posOne); } + + public: + CallSign callsign; + Vector3D posOne; + Vector3D posTwo; + + Motion(const CallSign& callsign, + const Vector3D& posOne, + const Vector3D& posTwo) + : callsign(callsign), posOne(posOne), posTwo(posTwo) {} + + explicit Motion() = default; + Motion(const Motion& other) = default; + Motion& operator=(const Motion& other) = default; + + [[nodiscard]] std::optional findIntersection( + const Motion& other) const { + const Vector3D& init1 = posOne; + const Vector3D& init2 = other.posOne; + const Vector3D vec1 = delta(); + const Vector3D vec2 = other.delta(); + const double radius = Constants::PROXIMITY_RADIUS; + + // this test is not geometrical 3-d intersection test, it takes the fact + // that the aircraft move into account ; so it is more like a 4d test (it + // assumes that both of the aircraft have a constant speed over the tested + // interval) + + // we thus have two points, each of them moving on its line segment at + // constant speed ; we are looking for times when the distance between these + // two points is smaller than r + + // vec1 is vector of aircraft 1 + // vec2 is vector of aircraft 2 + + // a = (V2 - V1)^T * (V2 - V1) + const double a = vec2.minus(vec1).squaredMagnitude(); + + if (a != 0.0) { + // we are first looking for instances of time when the planes are exactly + // r from each other at least one plane is moving ; if the planes are + // moving in parallel, they do not have constant speed + + // if the planes are moving in parallel, then + // if the faster starts behind the slower, we can have 2, 1, or 0 + // solutions if the faster plane starts in front of the slower, we can + // have 0 or 1 solutions + + // if the planes are not moving in parallel, then + + // point P1 = I1 + vV1 + // point P2 = I2 + vV2 + // - looking for v, such that dist(P1,P2) = || P1 - P2 || = r + + // it follows that || P1 - P2 || = sqrt( < P1-P2, P1-P2 > ) + // 0 = -r^2 + < P1 - P2, P1 - P2 > + // from properties of dot product + // 0 = -r^2 + + v * 2 + v^2 * + // so we calculate a, b, c - and solve the quadratic equation + // 0 = c + bv + av^2 + + // b = 2 * + const double b = 2.0 * init1.minus(init2).dot(vec1.minus(vec2)); + + // c = -r^2 + (I2 - I1)^T * (I2 - I1) + const double c = -radius * radius + init2.minus(init1).squaredMagnitude(); + + const double discr = b * b - 4.0 * a * c; + if (discr < 0.0) { + return {}; + } + + const double v1 = (-b - sqrt(discr)) / (2.0 * a); + const double v2 = (-b + sqrt(discr)) / (2.0 * a); + + if (v1 <= v2 && ((v1 <= 1.0 && 1.0 <= v2) || (v1 <= 0.0 && 0.0 <= v2) || + (0.0 <= v1 && v2 <= 1.0))) { + // Pick a good "time" at which to report the collision. + double v = 0.0; + if (v1 <= 0.0) { + // The collision started before this frame. Report it at the start of + // the frame. + v = 0.0; + } else { + // The collision started during this frame. Report it at that moment. + v = v1; + } + + const Vector3D result1 = init1.plus(vec1.times(v)); + const Vector3D result2 = init2.plus(vec2.times(v)); + + const Vector3D result = result1.plus(result2).times(0.5); + if (result.x >= Constants::MIN_X && result.x <= Constants::MAX_X && + result.y >= Constants::MIN_Y && result.y <= Constants::MAX_Y && + result.z >= Constants::MIN_Z && result.z <= Constants::MAX_Z) { + return result; + } + } + + return {}; + } + // the planes have the same speeds and are moving in parallel (or they are + // not moving at all) they thus have the same distance all the time ; we + // calculate it from the initial point + + // dist = || i2 - i1 || = sqrt( ( i2 - i1 )^T * ( i2 - i1 ) ) + const double dist = init2.minus(init1).magnitude(); + if (dist <= radius) { + return init1.plus(init2).times(0.5); + } + + return {}; + } +}; + +class Aircraft { + public: + CallSign callsign; + Vector3D position; + + Aircraft(const CallSign& callsign, const Vector3D& position) + : callsign(callsign), position(position) {} + + explicit Aircraft() = default; +}; + +class CollisionDetector { + private: + RedBlackTree _state{}; + constexpr static const Vector2D _horizontal{Constants::GOOD_VOXEL_SIZE, 0.0}; + constexpr static const Vector2D _vertical{0.0, Constants::GOOD_VOXEL_SIZE}; + + static bool isInVoxel(const Vector2D& voxel, const Motion& motion) { + if (voxel.x > Constants::MAX_X || voxel.x < Constants::MIN_X || + voxel.y > Constants::MAX_Y || voxel.y < Constants::MIN_Y) { + return false; + } + + const Vector3D& init = motion.posOne; + const Vector3D& fin = motion.posTwo; + + const double v_s = Constants::GOOD_VOXEL_SIZE; + const double r = Constants::PROXIMITY_RADIUS / 2.0; + + const double v_x = voxel.x; + const double x0 = init.x; + const double xv = fin.x - init.x; + + const double v_y = voxel.y; + const double y0 = init.y; + const double yv = fin.y - init.y; + + double low_x = (v_x - r - x0) / xv; + double high_x = (v_x + v_s + r - x0) / xv; + + if (xv < 0.0) { + const double tmp = low_x; + low_x = high_x; + high_x = tmp; + } + + double low_y = (v_y - r - y0) / yv; + double high_y = (v_y + v_s + r - y0) / yv; + + if (yv < 0.0) { + const double tmp = low_y; + low_y = high_y; + high_y = tmp; + } + + return ( + ((xv == 0.0 && v_x <= x0 + r && + x0 - r <= v_x + v_s) /* no motion in x */ + || (low_x <= 1.0 && 1.0 <= high_x) || + (low_x <= 0.0 && 0.0 <= high_x) || (0.0 <= low_x && high_x <= 1.0)) && + ((yv == 0.0 && v_y <= y0 + r && + y0 - r <= v_y + v_s) /* no motion in y */ + || + ((low_y <= 1.0 && 1.0 <= high_y) || (low_y <= 0.0 && 0.0 <= high_y) || + (0.0 <= low_y && high_y <= 1.0))) && + (xv == 0.0 || yv == 0.0 || /* no motion in x or y or both */ + (low_y <= high_x && high_x <= high_y) || + (low_y <= low_x && low_x <= high_y) || + (low_x <= low_y && high_y <= high_x))); + } + + static void putIntoMap(RedBlackTree*>& voxelMap, + const Vector2D& voxel, + const Motion& motion) { + Vector** array = voxelMap.get(voxel); + if (array == nullptr) { + auto* arr = new Vector(); + arr->append(motion); + voxelMap.put(voxel, arr); + } else { + (*array)->append(motion); + } + } + + void recurse(RedBlackTree*>& voxelMap, + RedBlackTree& seen, + const Vector2D& nextVoxel, + const Motion& motion) { + if (!isInVoxel(nextVoxel, motion)) { + return; + } + if (seen.put(nextVoxel, true) == true) { + return; + } + putIntoMap(voxelMap, nextVoxel, motion); + + recurse(voxelMap, seen, nextVoxel.minus(_horizontal), motion); + recurse(voxelMap, seen, nextVoxel.plus(_horizontal), motion); + recurse(voxelMap, seen, nextVoxel.minus(_vertical), motion); + recurse(voxelMap, seen, nextVoxel.plus(_vertical), motion); + recurse(voxelMap, seen, nextVoxel.minus(_horizontal).minus(_vertical), + motion); + recurse(voxelMap, seen, nextVoxel.minus(_horizontal).plus(_vertical), + motion); + recurse(voxelMap, seen, nextVoxel.plus(_horizontal).minus(_vertical), + motion); + recurse(voxelMap, seen, nextVoxel.plus(_horizontal).plus(_vertical), + motion); + } + + Vector*>* reduceCollisionSet(Vector& motions) { + RedBlackTree*> voxelMap{}; + motions.forEach([this, &voxelMap](const Motion& motion) -> void { + drawMotionOnVoxelMap(voxelMap, motion); + }); + + auto* result = new Vector*>(); + + voxelMap.forEach( + [result](RedBlackTree*>::Entry& e) -> void { + if (e.value->size() > 1) { + result->append(e.value); + } else { + delete e.value; + } + }); + return result; + } + + Vector2D voxelHash(Vector3D position) { + const auto xDiv = + static_cast(position.x / Constants::GOOD_VOXEL_SIZE); + const auto yDiv = + static_cast(position.y / Constants::GOOD_VOXEL_SIZE); + + double x = Constants::GOOD_VOXEL_SIZE * xDiv; + double y = Constants::GOOD_VOXEL_SIZE * yDiv; + + if (position.x < 0) { + x -= Constants::GOOD_VOXEL_SIZE; + } + if (position.y < 0) { + y -= Constants::GOOD_VOXEL_SIZE; + } + + return {x, y}; + } + + void drawMotionOnVoxelMap(RedBlackTree*>& voxelMap, + const Motion motion) { + RedBlackTree seen{}; + recurse(voxelMap, seen, voxelHash(motion.posOne), motion); + } + + public: + CollisionDetector() = default; + + Vector* handleNewFrame(Vector& frame) { + Vector motions{}; + RedBlackTree seen{}; + + frame.forEach([this, &seen, &motions](const Aircraft& aircraft) -> void { + std::optional oldPosition = + _state.put(aircraft.callsign, aircraft.position); + const Vector3D newPosition = aircraft.position; + seen.put(aircraft.callsign, true); + if (!oldPosition.has_value()) { + // Treat newly introduced aircraft as if they were stationary. + oldPosition = newPosition; + } + motions.append(Motion(aircraft.callsign, *oldPosition, newPosition)); + }); + + Vector toRemove{}; + _state.forEach( + [&seen, + &toRemove](const RedBlackTree::Entry& e) -> void { + bool* const wasSeen = seen.get(e.key); + if (wasSeen == nullptr || !*wasSeen) { + toRemove.append(e.key); + } + }); + + toRemove.forEach([this](const CallSign& e) -> void { _state.remove(e); }); + + Vector*>* allReduced = reduceCollisionSet(motions); + auto* collisions = new Vector(); + + allReduced->forEach( + [collisions](const Vector* const& reduced) -> void { + for (size_t i = 0; i < reduced->size(); i += 1) { + const Motion* motion1 = reduced->at(i); + for (size_t j = i + 1; j < reduced->size(); j += 1) { + const Motion* motion2 = reduced->at(j); + std::optional collision = + motion1->findIntersection(*motion2); + if (collision.has_value()) { + collisions->append(Collision(motion1->callsign, + motion2->callsign, *collision)); + } + } + } + }); + allReduced->destroyValues(); + delete allReduced; + return collisions; + } +}; + +class Simulator { + private: + Vector _aircraft{}; + + public: + explicit Simulator(int32_t numAircraft) { + for (int32_t i = 0; i < numAircraft; i += 1) { + _aircraft.append(CallSign(i)); + } + } + + Vector simulate(double time) { + Vector frame{}; + + for (size_t i = 0; i < _aircraft.size(); i += 2) { + frame.append(Aircraft( + *_aircraft.at(i), + Vector3D(time, cos(time) * 2 + static_cast(i) * 3, 10))); + frame.append(Aircraft( + *_aircraft.at(i + 1), + Vector3D(time, sin(time) * 2 + static_cast(i) * 3, 10))); + } + return frame; + } +}; + +class CD : public Benchmark { + private: + static size_t benchmark(int32_t numAircrafts) { + const int32_t numFrames = 200; + + Simulator simulator{numAircrafts}; + CollisionDetector detector{}; + size_t actualCollisions = 0; + + for (int32_t i = 0; i < numFrames; i += 1) { + const double time = i / 10.0; + Vector frame = simulator.simulate(time); + Vector* collisions = detector.handleNewFrame(frame); + actualCollisions += collisions->size(); + delete collisions; + } + + return actualCollisions; + } + + public: + bool inner_benchmark_loop(int32_t innerIterations) override { + return verify_result(benchmark(innerIterations), innerIterations); + } + + static bool verify_result(size_t actualCollisions, int32_t numAircrafts) { + if (numAircrafts == 1000) { + return actualCollisions == 14484; + } + if (numAircrafts == 500) { + return actualCollisions == 14484; + } + if (numAircrafts == 250) { + return actualCollisions == 10830; + } + if (numAircrafts == 200) { + return actualCollisions == 8655; + } + if (numAircrafts == 100) { + return actualCollisions == 4305; + } + if (numAircrafts == 10) { + return actualCollisions == 390; + } + if (numAircrafts == 2) { + return actualCollisions == 42; + } + + std::cout << "No verification result for " << numAircrafts << " found\n"; + std::cout << "Result is: " << actualCollisions << "\n"; + return false; + } + + std::any benchmark() override { throw Error("Should never be reached"); } + bool verify_result(std::any) override { + throw Error("Should never be reached"); + } +}; diff --git a/benchmarks/C++/src/deltablue.cpp b/benchmarks/C++/src/deltablue.cpp new file mode 100644 index 00000000..304c66f1 --- /dev/null +++ b/benchmarks/C++/src/deltablue.cpp @@ -0,0 +1,121 @@ +#include "deltablue.h" +#include "som/dictionary.h" +#include "som/error.h" +#include "som/identity_dictionary.h" + +#include + +// Set static member variable +const Sym Strength::ABSOLUTE_STRONGEST(0); +const Sym Strength::REQUIRED(1); +const Sym Strength::STRONG_PREFERRED(2); +const Sym Strength::PREFERRED(3); +const Sym Strength::STRONG_DEFAULT(4); +const Sym Strength::DEFAULT(5); +const Sym Strength::WEAK_DEFAULT(6); +const Sym Strength::ABSOLUTE_WEAKEST(7); + +IdentityDictionary* Strength::_strengthTable; +IdentityDictionary* Strength::_strengthConstant; +const Strength* Strength::_absoluteWeakest; +const Strength* Strength::_required; + +const Strength* Strength::absoluteWeakest() { + return _absoluteWeakest; +} + +const Strength* Strength::required() { + return _required; +} + +IdentityDictionary* Strength::createStrengthTable() { + auto* strengthTable = new IdentityDictionary(); + strengthTable->atPut(&Strength::ABSOLUTE_STRONGEST, -10000); + strengthTable->atPut(&Strength::REQUIRED, -800); + strengthTable->atPut(&Strength::STRONG_PREFERRED, -600); + strengthTable->atPut(&Strength::PREFERRED, -400); + strengthTable->atPut(&Strength::STRONG_DEFAULT, -200); + strengthTable->atPut(&Strength::DEFAULT, 0); + strengthTable->atPut(&Strength::WEAK_DEFAULT, 500); + strengthTable->atPut(&Strength::ABSOLUTE_WEAKEST, 10000); + return strengthTable; +} + +IdentityDictionary* +Strength::createStrengthConstants() { + auto* strengthConstant = new IdentityDictionary(); + auto* keys = _strengthTable->getKeys(); + keys->forEach([&strengthConstant](const Sym* const key) -> void { + const Sym* keySym = dynamic_cast(key); + strengthConstant->atPut(keySym, new Strength(keySym)); + }); + delete keys; + + return strengthConstant; +} + +void Strength::initializeConstants() { + _strengthTable = createStrengthTable(); + _strengthConstant = createStrengthConstants(); + _absoluteWeakest = of(&Strength::ABSOLUTE_WEAKEST); + _required = of(&Strength::REQUIRED); +} + +void Strength::releaseStrengthConstants() { + _strengthConstant->destroyValues(); + delete _strengthConstant; +} + +void Strength::releaseConstants() { + delete _strengthTable; + Strength::releaseStrengthConstants(); + _absoluteWeakest = nullptr; + _required = nullptr; +} + +void AbstractConstraint::addConstraint(Planner* planner) { + addToGraph(); + planner->incrementalAdd(this); +} + +void AbstractConstraint::destroyConstraint(Planner* planner) { + if (isSatisfied()) { + planner->incrementalRemove(this); + } + removeFromGraph(); +} + +bool AbstractConstraint::inputsKnown(int32_t mark) { + return !inputsHasOne([mark](Variable* v) -> bool { + return !(v->getMark() == mark || v->getStay() || + v->getDeterminedBy() == nullptr); + }); +} + +AbstractConstraint* AbstractConstraint::satisfy(int32_t mark, + Planner* planner) { + AbstractConstraint* overridden = nullptr; + + chooseMethod(mark); + + if (isSatisfied()) { + inputsDo([mark](Variable* in) -> void { in->setMark(mark); }); + + Variable* out = getOutput(); + overridden = out->getDeterminedBy(); + if (overridden != nullptr) { + overridden->markUnsatisfied(); + } + out->setDeterminedBy(this); + if (!planner->addPropagate(this, mark)) { + throw Error("Cycle encountered"); + } + out->setMark(mark); + } else { + overridden = nullptr; + if (_strength->sameAs(Strength::required())) { + throw Error("Could not satisfy a required constraint"); + } + } + return overridden; +} diff --git a/benchmarks/C++/src/deltablue.h b/benchmarks/C++/src/deltablue.h new file mode 100644 index 00000000..feffebd8 --- /dev/null +++ b/benchmarks/C++/src/deltablue.h @@ -0,0 +1,720 @@ +#pragma once + +#include + +#include "benchmark.h" +#include "memory/object_tracker.h" +#include "som/error.h" +#include "som/identity_dictionary.h" +#include "som/vector.h" + +enum Direction { FORWARD, BACKWARD, NONE }; + +class Sym { + private: + uint32_t hash; + + public: + explicit constexpr Sym(uint32_t hash_value) noexcept : hash(hash_value) {} + + [[nodiscard]] uint32_t customHash() const { return hash; } +}; + +class Strength { + public: + static const Sym ABSOLUTE_STRONGEST; + static const Sym REQUIRED; + static const Sym STRONG_PREFERRED; + static const Sym PREFERRED; + static const Sym STRONG_DEFAULT; + static const Sym DEFAULT; + static const Sym WEAK_DEFAULT; + static const Sym ABSOLUTE_WEAKEST; + + private: + int32_t const _arithmeticValue; + const Sym* const _symbolicValue; + + static IdentityDictionary* createStrengthTable(); + static IdentityDictionary* + createStrengthConstants(); + static void releaseStrengthConstants(); + + public: + explicit Strength(const Sym* const symbolicValue) + : _arithmeticValue(*_strengthTable->at(symbolicValue)), + _symbolicValue(symbolicValue) {} + + bool sameAs(const Strength* const s) const { + return _arithmeticValue == s->_arithmeticValue; + } + + bool stronger(const Strength* const s) const { + return _arithmeticValue < s->_arithmeticValue; + } + + bool weaker(const Strength* const s) const { + return _arithmeticValue > s->_arithmeticValue; + } + + const Strength* strongest(const Strength* const s) const { + return s->stronger(this) ? s : this; + } + + const Strength* weakest(const Strength* const s) const { + return s->weaker(this) ? s : this; + } + + [[nodiscard]] int32_t getArithmeticValue() const { return _arithmeticValue; } + + static const Strength* of(const Sym* const strength) { + return *_strengthConstant->at(strength); + } + static const Strength* absoluteWeakest(); + static const Strength* required(); + static void initializeConstants(); + static void releaseConstants(); + + private: + static const Strength* _absoluteWeakest; + static const Strength* _required; + + static IdentityDictionary* _strengthTable; + static IdentityDictionary* _strengthConstant; +}; + +class Planner; +class Variable; + +class AbstractConstraint : public TrackedObject { + protected: + const Strength* const _strength; + Planner* const _planer{nullptr}; + + public: + explicit AbstractConstraint(const Sym* strength) + : _strength(Strength::of(strength)) {} + + [[nodiscard]] const Strength* getStrength() { return _strength; } + + virtual bool isInput() { return false; } + void addConstraint(Planner* planner); + void destroyConstraint(Planner* planner); + bool inputsKnown(int32_t mark); + AbstractConstraint* satisfy(int32_t mark, Planner* planner); + + virtual bool isSatisfied() = 0; + virtual void addToGraph() = 0; + virtual void removeFromGraph() = 0; + virtual Direction chooseMethod(int32_t mark) = 0; + virtual void execute() = 0; + virtual void inputsDo(std::function fn) = 0; + virtual bool inputsHasOne(std::function fn) = 0; + virtual void markUnsatisfied() = 0; + virtual Variable* getOutput() = 0; + virtual void recalculate() = 0; +}; + +class Variable : public TrackedObject { + private: + int32_t _value{0}; + Vector* _constraints; + AbstractConstraint* _determinedBy{nullptr}; + int32_t _mark{0}; + const Strength* _walkStrength; + bool _stay{true}; + + public: + Variable() + : _constraints(new Vector(2)), + _walkStrength(Strength::absoluteWeakest()) {} + + ~Variable() override { delete _constraints; } + + static Variable* value(int32_t aValue) { + auto* v = new Variable(); + v->setValue(aValue); + return v; + } + + void addConstraint(AbstractConstraint* c) { _constraints->append(c); } + + [[nodiscard]] Vector* getConstraints() const { + return _constraints; + } + + [[nodiscard]] AbstractConstraint* getDeterminedBy() const { + return _determinedBy; + } + + void setDeterminedBy(AbstractConstraint* c) { _determinedBy = c; } + + [[nodiscard]] int32_t getMark() const { return _mark; } + + void setMark(int32_t markValue) { _mark = markValue; } + + void removeConstraint(AbstractConstraint* c) { + _constraints->remove(c); + if (_determinedBy == c) { + _determinedBy = nullptr; + } + } + + [[nodiscard]] bool getStay() const { return _stay; } + void setStay(bool v) { _stay = v; } + + [[nodiscard]] int32_t getValue() const { return _value; } + + void setValue(int32_t value) { _value = value; } + + [[nodiscard]] const Strength* getWalkStrength() const { + return _walkStrength; + } + + void setWalkStrength(const Strength* strength) { _walkStrength = strength; } +}; + +class BinaryConstraint : public AbstractConstraint { + protected: + Variable* _v1; + Variable* _v2; + Direction _direction{NONE}; + + public: + BinaryConstraint(Variable* var1, + Variable* var2, + const Sym* strength, + Planner*) + : AbstractConstraint(strength), _v1(var1), _v2(var2) {} + + // can't free _v1 and _v2 here, + // because they are shared with other constraints + ~BinaryConstraint() override = default; + + bool isSatisfied() override { return _direction != NONE; } + + void addToGraph() override { + _v1->addConstraint(this); + _v2->addConstraint(this); + _direction = NONE; + } + + void removeFromGraph() override { + _v1->removeConstraint(this); + _v2->removeConstraint(this); + _direction = NONE; + } + + Direction chooseMethod(int32_t mark) override { + if (_v1->getMark() == mark) { + if (_v2->getMark() != mark && + _strength->stronger(_v2->getWalkStrength())) { + _direction = FORWARD; + return _direction; + } + + _direction = NONE; + return _direction; + } + + if (_v2->getMark() == mark) { + if (_v1->getMark() != mark && + _strength->stronger(_v1->getWalkStrength())) { + _direction = BACKWARD; + return _direction; + } + + _direction = NONE; + return _direction; + } + + if (_v1->getWalkStrength()->weaker(_v2->getWalkStrength())) { + if (_strength->stronger(_v1->getWalkStrength())) { + _direction = BACKWARD; + return _direction; + } + + _direction = NONE; + return _direction; + } + + if (_strength->stronger(_v2->getWalkStrength())) { + _direction = FORWARD; + return _direction; + } + + _direction = NONE; + return _direction; + } + + void inputsDo(std::function fn) override { + if (_direction == FORWARD) { + fn(_v1); + } else { + fn(_v2); + } + } + + bool inputsHasOne(std::function fn) override { + if (_direction == FORWARD) { + return fn(_v1); + } + return fn(_v2); + } + + void markUnsatisfied() override { _direction = NONE; } + Variable* getOutput() override { return _direction == FORWARD ? _v2 : _v1; } + + void recalculate() override { + Variable* in = nullptr; + Variable* out = nullptr; + + if (_direction == FORWARD) { + in = _v1; + out = _v2; + } else { + in = _v2; + out = _v1; + } + + out->setWalkStrength(_strength->weakest(in->getWalkStrength())); + out->setStay(in->getStay()); + if (out->getStay()) { + execute(); + } + } +}; + +class UnaryConstraint : public AbstractConstraint { + protected: + Variable* _output; // possible output variable + bool _satisfied{false}; // true if I am currently satisfied + + public: + UnaryConstraint(Variable* v, const Sym* strength, Planner*) + : AbstractConstraint(strength), _output(v) { + // moved to subclass to avoid calling wrong method + // addConstraint(planner); + } + + // can't free _output here, because it is shared with other constraints + ~UnaryConstraint() override = default; + + bool isSatisfied() override { return _satisfied; } + + void addToGraph() override { + _output->addConstraint(this); + _satisfied = false; + } + + void removeFromGraph() override { + if (_output != nullptr) { + _output->removeConstraint(this); + } + _satisfied = false; + } + + Direction chooseMethod(int32_t mark) override { + _satisfied = (_output->getMark() != mark) && + _strength->stronger(_output->getWalkStrength()); + return NONE; + } + + void inputsDo(std::function) override {} + + bool inputsHasOne(std::function) override { return false; } + + void markUnsatisfied() override { _satisfied = false; } + Variable* getOutput() override { return _output; } + + void recalculate() override { + _output->setWalkStrength(_strength); + _output->setStay(!isInput()); + if (_output->getStay()) { + execute(); + } + } +}; + +class EditConstraint : public UnaryConstraint { + public: + EditConstraint(Variable* v, const Sym* strength, Planner* planner) + : UnaryConstraint(v, strength, planner) { + addConstraint(planner); // moved here from UnaryConstraint constructor to + // make sure the right method is called + } + + bool isInput() override { return true; } + + void execute() override { + // Edit constraints do nothing. + } +}; + +class EqualityConstraint : public BinaryConstraint { + public: + EqualityConstraint(Variable* var1, + Variable* var2, + const Sym* strength, + Planner* planner) + : BinaryConstraint(var1, var2, strength, planner) { + addConstraint(planner); + } + + void execute() override { + if (_direction == Direction::FORWARD) { + _v2->setValue(_v1->getValue()); + } else { + _v1->setValue(_v2->getValue()); + } + } +}; + +class Plan : public Vector { + public: + Plan() : Vector(15) {} + + void execute() { + forEach([&](AbstractConstraint* c) -> void { c->execute(); }); + } +}; + +class ScaleConstraint : public BinaryConstraint { + private: + Variable* _scale; + Variable* _offset; + + public: + ScaleConstraint(Variable* src, + Variable* scale, + Variable* offset, + Variable* dest, + const Sym* strength, + Planner* planner) + : BinaryConstraint(src, dest, strength, planner), + _scale(scale), + _offset(offset) { + addConstraint(planner); + } + + void addToGraph() override { + _v1->addConstraint(this); + _v2->addConstraint(this); + _scale->addConstraint(this); + _offset->addConstraint(this); + _direction = NONE; + } + + void removeFromGraph() override { + if (_v1 != nullptr) { + _v1->removeConstraint(this); + } + if (_v2 != nullptr) { + _v2->removeConstraint(this); + } + if (_scale != nullptr) { + _scale->removeConstraint(this); + } + if (_offset != nullptr) { + _offset->removeConstraint(this); + } + _direction = NONE; + } + + void execute() override { + if (_direction == FORWARD) { + _v2->setValue(_v1->getValue() * _scale->getValue() + _offset->getValue()); + } else { + _v1->setValue((_v2->getValue() - _offset->getValue()) / + _scale->getValue()); + } + } + + void inputsDo(std::function fn) override { + if (_direction == FORWARD) { + fn(_v1); + fn(_scale); + fn(_offset); + } else { + fn(_v2); + fn(_scale); + fn(_offset); + } + } + void recalculate() override { + Variable* in = nullptr; + Variable* out = nullptr; + + if (_direction == FORWARD) { + in = _v1; + out = _v2; + } else { + out = _v1; + in = _v2; + } + + out->setWalkStrength(_strength->weakest(in->getWalkStrength())); + out->setStay(in->getStay() && _scale->getStay() && _offset->getStay()); + if (out->getStay()) { + execute(); // stay optimization + } + } +}; + +class StayConstraint : public UnaryConstraint { + public: + StayConstraint(Variable* v, const Sym* strength, Planner* planner) + : UnaryConstraint(v, strength, planner) { + addConstraint(planner); // moved here from UnaryConstraint constructor to + // make sure the right method is called + } + + void execute() override { + // StayConstraints do nothing. + } +}; + +class Planner { + private: + int32_t _currentMark{1}; + + public: + Planner() = default; + + void incrementalAdd(AbstractConstraint* c) { + const int32_t mark = newMark(); + AbstractConstraint* overridden = c->satisfy(mark, this); + + while (overridden != nullptr) { + overridden = overridden->satisfy(mark, this); + } + } + + void incrementalRemove(AbstractConstraint* c) { + Variable* out = c->getOutput(); + c->markUnsatisfied(); + c->removeFromGraph(); + + Vector* unsatisfied = removePropagateFrom(out); + unsatisfied->forEach( + [this](AbstractConstraint* u) -> void { incrementalAdd(u); }); + delete unsatisfied; + } + + Plan* extractPlanFromConstraints(Vector* constraints) { + Vector sources = Vector(); + constraints->forEach([&](AbstractConstraint* c) -> void { + if (c->isInput() && c->isSatisfied()) { + sources.append(c); + } + }); + return makePlan(&sources); + } + + Plan* makePlan(Vector* sources) { + const int32_t mark = newMark(); + Plan* plan = new Plan(); + Vector* todo = sources; + + while (!todo->isEmpty()) { + AbstractConstraint* c = todo->removeFirst(); + + if (c->getOutput()->getMark() != mark && c->inputsKnown(mark)) { + plan->append(c); + c->getOutput()->setMark(mark); + addConstraintsConsumingTo(c->getOutput(), todo); + } + } + + return plan; + } + + void addConstraintsConsumingTo(Variable* v, + Vector* coll) { + AbstractConstraint* determiningC = v->getDeterminedBy(); + + v->getConstraints()->forEach( + [&coll, determiningC](AbstractConstraint* c) -> void { + if (c != determiningC && c->isSatisfied()) { + coll->append(c); + } + }); + } + + bool addPropagate(AbstractConstraint* c, int32_t mark) { + Vector* todo = Vector::with(c); + + while (!todo->isEmpty()) { + AbstractConstraint* d = todo->removeFirst(); + + if (d->getOutput()->getMark() == mark) { + incrementalRemove(c); + delete todo; + return false; + } + + d->recalculate(); + addConstraintsConsumingTo(d->getOutput(), todo); + } + + delete todo; + return true; + } + + void change(Variable* var, int32_t newValue) { + auto* editC = new EditConstraint(var, &Strength::PREFERRED, this); + auto* editV = Vector::with(editC); + Plan* plan = extractPlanFromConstraints(editV); + + for (int32_t i = 0; i < 10; i += 1) { + var->setValue(newValue); + plan->execute(); + } + + editC->destroyConstraint(this); + delete plan; + delete editV; + } + + void constraintsConsuming(Variable* v, + std::function fn) { + AbstractConstraint* determiningC = v->getDeterminedBy(); + + v->getConstraints()->forEach([&](AbstractConstraint* c) -> void { + if (c != determiningC && c->isSatisfied()) { + fn(c); + } + }); + } + + int32_t newMark() { + _currentMark += 1; + return _currentMark; + } + + Vector* removePropagateFrom(Variable* out) { + auto* unsatisfied = new Vector(); + + out->setDeterminedBy(nullptr); + out->setWalkStrength(Strength::absoluteWeakest()); + out->setStay(true); + + auto* todo = Vector::with(out); + + while (!todo->isEmpty()) { + Variable* v = todo->removeFirst(); + + v->getConstraints()->forEach( + [unsatisfied](AbstractConstraint* c) -> void { + if (!c->isSatisfied()) { + unsatisfied->append(c); + } + }); + + constraintsConsuming(v, [&todo](AbstractConstraint* c) -> void { + c->recalculate(); + todo->append(c->getOutput()); + }); + } + delete todo; + + unsatisfied->sort( + [](AbstractConstraint* c1, AbstractConstraint* c2) -> int32_t { + return c1->getStrength()->stronger(c2->getStrength()) ? -1 : 1; + }); + + return unsatisfied; + } + + static void chainTest(int32_t n) { + Strength::initializeConstants(); + Planner planner = Planner(); + auto* vars = new Variable*[n + 1]; + for (int32_t i = 0; i < n + 1; i += 1) { + vars[i] = new Variable(); + } + for (int32_t i = 0; i < n; i += 1) { + Variable* v1 = vars[i]; + Variable* v2 = vars[i + 1]; + new EqualityConstraint(v1, v2, &Strength::REQUIRED, &planner); + } + new StayConstraint(vars[n], &Strength::STRONG_DEFAULT, &planner); + + auto* editC = new EditConstraint(vars[0], &Strength::PREFERRED, &planner); + auto* editV = Vector::with(editC); + Plan* plan = planner.extractPlanFromConstraints(editV); + delete editV; + + for (int32_t i = 0; i < 100; i += 1) { + vars[0]->setValue(i); + plan->execute(); + if (vars[n]->getValue() != i) { + throw Error("Chain test failed!"); + } + } + editC->destroyConstraint(&planner); + delete plan; + delete[] vars; + ObjectTracker::releaseAll(); + } + + static void projectionTest(int32_t n) { + Planner planner = Planner(); + Vector dests = Vector(); + + Variable* scale = Variable::value(10); + Variable* offset = Variable::value(1000); + + Variable* src = nullptr; + Variable* dst = nullptr; + for (int32_t i = 1; i <= n; i += 1) { + src = Variable::value(i); + dst = Variable::value(i); + dests.append(dst); + new StayConstraint(src, &Strength::DEFAULT, &planner); + new ScaleConstraint(src, scale, offset, dst, &Strength::REQUIRED, + &planner); + } + + planner.change(src, 17); + if (dst->getValue() != 1170) { + throw Error("Projection test 1 failed!"); + } + + planner.change(dst, 1050); + if (src->getValue() != 5) { + throw Error("Projection test 2 failed!"); + } + + planner.change(scale, 5); + for (int32_t i = 0; i < n - 1; i += 1) { + Variable* di = *dests.at(i); + if (di->getValue() != (i + 1) * 5 + 1000) { + throw Error("Projection test 3 failed!"); + } + } + + planner.change(offset, 2000); + for (int32_t i = 0; i < n - 1; i += 1) { + Variable* di = *dests.at(i); + if (di->getValue() != (i + 1) * 5 + 2000) { + throw Error("Projection test 4 failed!"); + } + } + ObjectTracker::releaseAll(); + Strength::releaseConstants(); + } +}; + +class DeltaBlue : public Benchmark { + public: + bool inner_benchmark_loop(int32_t innerIterations) override { + Planner::chainTest(innerIterations); + Planner::projectionTest(innerIterations); + return true; + } + + std::any benchmark() override { return nullptr; } + + bool verify_result(std::any) override { return false; } +}; diff --git a/benchmarks/C++/src/harness.cpp b/benchmarks/C++/src/harness.cpp new file mode 100644 index 00000000..1b5a6485 --- /dev/null +++ b/benchmarks/C++/src/harness.cpp @@ -0,0 +1,42 @@ +#include +#include + +#include "run.h" + +static Run process_arguments(int32_t argc, const char* argv[]) { // NOLINT + std::string name(argv[1]); + Run run(name); + + if (argc > 2) { + run.set_num_iterations(std::stoi(argv[2])); + if (argc > 3) { + run.set_inner_iterations(std::stoi(argv[3])); + } + } + + return run; +} + +static void print_usage() { + std::cout << "./harness benchmark [num-iterations [inner-iter]]\n"; + std::cout << "\n"; + std::cout << " benchmark - benchmark class name\n"; + std::cout << " num-iterations - number of times to execute benchmark, " + "default: 1\n"; + std::cout << " inner-iter - number of times the benchmark is executed " + "in an inner loop,\n"; + std::cout << " which is measured in total, default: 1\n"; +} + +int main(int argc, const char* argv[]) { + if (argc < 2) { + print_usage(); + return 1; + } + + Run run = process_arguments(argc, argv); + run.run_benchmark(); + run.print_total(); + + return 0; +} diff --git a/benchmarks/C++/src/havlak.h b/benchmarks/C++/src/havlak.h new file mode 100644 index 00000000..450c5301 --- /dev/null +++ b/benchmarks/C++/src/havlak.h @@ -0,0 +1,591 @@ +#pragma once + +#include +#include +#include + +#include "som/error.h" +#include "som/identity_set.h" + +using std::cout; + +class BasicBlock { + private: + Vector _inEdges{2}; + Vector _outEdges{2}; + uint32_t _name; + + public: + explicit BasicBlock(int32_t name) : _name(name) {} + + Vector& getInEdges() { return _inEdges; } + Vector& getOutEdges() { return _outEdges; } + int32_t getNumPred() { return static_cast(_inEdges.size()); } + void addOutEdge(BasicBlock* to) { _outEdges.append(to); } + void addInEdge(BasicBlock* from) { _inEdges.append(from); } + [[nodiscard]] uint32_t customHash() const { return _name; } + + bool equal(BasicBlock* other) const { return _name == other->_name; } +}; + +class BasicBlockEdge; + +class ControlFlowGraph { + private: + Vector _basicBlockMap{}; + BasicBlock* _startNode{nullptr}; + Vector _edgeList{}; + + public: + ControlFlowGraph() = default; + + ~ControlFlowGraph() { + _basicBlockMap.destroyValues(); + _edgeList.destroyValues(); + } + + BasicBlock* createNode(int32_t name) { + BasicBlock* node = nullptr; + if (_basicBlockMap.at(name) != nullptr) { + node = *_basicBlockMap.at(name); + } else { + node = new BasicBlock(name); + _basicBlockMap.atPut(name, node); + } + + if (getNumNodes() == 1) { + _startNode = node; + } + return node; + } + + void addEdge(BasicBlockEdge* edge) { _edgeList.append(edge); } + int32_t getNumNodes() { return static_cast(_basicBlockMap.size()); } + BasicBlock* getStartBasicBlock() { return _startNode; } + Vector& getBasicBlocks() { return _basicBlockMap; } +}; + +class BasicBlockEdge { + private: + BasicBlock* _from; + BasicBlock* _to; + + public: + BasicBlockEdge(ControlFlowGraph& cfg, int32_t fromName, int32_t toName) + : _from(cfg.createNode(fromName)), _to(cfg.createNode(toName)) { + _from->addOutEdge(_to); + _to->addInEdge(_from); + + cfg.addEdge(this); + } +}; + +class SimpleLoop { + private: + IdentitySet _basicBlocks{}; + IdentitySet _children{}; + SimpleLoop* _parent{nullptr}; + BasicBlock* _header; + bool _isReducible; + bool _isRoot{false}; + int32_t _nestingLevel{0}; + int32_t _counter{0}; + int32_t _depthLevel{0}; + + void addChildLoop(SimpleLoop* loop) { _children.add(loop); } + + public: + SimpleLoop(BasicBlock* bb, bool isReducible) + : _header(bb), _isReducible(isReducible) { + if (bb != nullptr) { + _basicBlocks.add(bb); + } + } + + bool equal(SimpleLoop* other) const { return this == other; } + + void addNode(BasicBlock* bb) { _basicBlocks.add(bb); } + + [[nodiscard]] IdentitySet& getChildren() { return _children; } + [[nodiscard]] SimpleLoop* getParent() const { return _parent; } + [[nodiscard]] int32_t getNestingLevel() const { return _nestingLevel; } + [[nodiscard]] bool isRoot() const { return _isRoot; } + + void setParent(SimpleLoop* parent) { + _parent = parent; + _parent->addChildLoop(this); + } + + void setIsRoot() { _isRoot = true; } + void setCounter(int32_t value) { _counter = value; } + + void setNestingLevel(int32_t level) { + _nestingLevel = level; + if (level == 0) { + setIsRoot(); + } + } + + void setDepthLevel(int32_t level) { _depthLevel = level; } +}; + +class LoopStructureGraph { + private: + SimpleLoop* _root; + Vector _loops{}; + int32_t _loopCounter{0}; + + public: + LoopStructureGraph() : _root(new SimpleLoop(nullptr, true)) { + _root->setNestingLevel(0); + _root->setCounter(_loopCounter); + _loopCounter += 1; + _loops.append(_root); + } + + ~LoopStructureGraph() { _loops.destroyValues(); } + + [[nodiscard]] SimpleLoop* createNewLoop(BasicBlock* bb, bool isReducible) { + auto* loop = new SimpleLoop(bb, isReducible); + loop->setCounter(_loopCounter); + _loopCounter += 1; + _loops.append(loop); + return loop; + } + + void calculateNestingLevel() { + // link up all 1st level loops to artificial root node. + _loops.forEach([this](SimpleLoop* const& liter) -> void { + if (!liter->isRoot()) { + if (liter->getParent() == nullptr) { + liter->setParent(_root); + } + } + }); + + // recursively traverse the tree and assign levels. + calculateNestingLevelRec(_root, 0); + } + + void calculateNestingLevelRec(SimpleLoop* loop, int32_t depth) { + loop->setDepthLevel(depth); + loop->getChildren().forEach( + [this, loop, depth](SimpleLoop* const& liter) -> void { + calculateNestingLevelRec(liter, depth + 1); + + loop->setNestingLevel( + std::max(loop->getNestingLevel(), 1 + liter->getNestingLevel())); + }); + } + + int32_t getNumLoops() { return static_cast(_loops.size()); } +}; + +class UnionFindNode { + private: + UnionFindNode* _parent; + BasicBlock* _bb; + SimpleLoop* _loop; + int32_t _dfsNumber; + + public: + UnionFindNode() = default; + + void initNode(BasicBlock* bb, int32_t dfsNumber) { + _parent = this; + _bb = bb; + _dfsNumber = dfsNumber; + _loop = nullptr; + } + + UnionFindNode* findSet() { + Vector nodeList{}; + + UnionFindNode* node = this; + while (node != node->_parent) { + if (node->_parent != node->_parent->_parent) { + nodeList.append(node); + } + node = node->_parent; + } + + // Path Compression, all nodes' parents point to the 1st level parent. + nodeList.forEach([this](UnionFindNode* const& iter) -> void { + iter->unionSet(_parent); + }); + return node; + } + + void unionSet(UnionFindNode* basicBlock) { _parent = basicBlock; } + [[nodiscard]] BasicBlock* getBb() const { return _bb; } + [[nodiscard]] SimpleLoop* getLoop() const { return _loop; } + [[nodiscard]] int32_t getDfsNumber() const { return _dfsNumber; } + void setLoop(SimpleLoop* loop) { _loop = loop; } +}; + +class HavlakLoopFinder { + private: + enum BasicBlockClass { + BB_TOP, // uninitialized + BB_NONHEADER, // a regular BB + BB_REDUCIBLE, // reducible loop + BB_SELF, // single BB loop + BB_IRREDUCIBLE, // irreducible loop + BB_DEAD, // a dead BB + BB_LAST // Sentinel + }; + + ControlFlowGraph& _cfg; // Control Flow Graph + LoopStructureGraph& _lsg; // Loop Structure Graph + + static constexpr int32_t UNVISITED = std::numeric_limits::max(); + static constexpr int32_t MAXNONBACKPREDS = static_cast(32 * 1024); + + Vector*> _nonBackPreds{}; + Vector*> _backPreds{}; + IdentityDictionary _number{}; + + int32_t _maxSize{0}; + int32_t* _header{nullptr}; + BasicBlockClass* _type{nullptr}; + int32_t* _last{nullptr}; + UnionFindNode** _nodes{nullptr}; + + public: + HavlakLoopFinder(ControlFlowGraph& cfg, LoopStructureGraph& lsg) + : _cfg(cfg), _lsg(lsg) {} + + ~HavlakLoopFinder() { + _nonBackPreds.destroyValues(); + _backPreds.destroyValues(); + delete[] _header; + delete[] _type; + delete[] _last; + + for (int32_t i = 0; i < _maxSize; i += 1) { + delete _nodes[i]; + } + delete[] _nodes; + } + + void findLoops() { + if (_cfg.getStartBasicBlock() == nullptr) { + return; + } + + const int32_t size = _cfg.getNumNodes(); + + _nonBackPreds.removeAll(); + _backPreds.removeAll(); + _number.removeAll(); + if (size > _maxSize) { + _header = new int32_t[size]; + _type = new BasicBlockClass[size]; + _last = new int32_t[size]; + _nodes = new UnionFindNode*[size]; + _maxSize = size; + } + + for (int32_t i = 0; i < size; i += 1) { + _nonBackPreds.append(new Set()); + _backPreds.append(new Vector()); + _nodes[i] = new UnionFindNode(); + } + + initAllNodes(); + identifyEdges(size); + + // Start node is root of all other loops. + _header[0] = 0; + for (int32_t w = size - 1; w >= 0; w -= 1) { + // this is 'P' in Havlak's paper + Vector nodePool{}; + + BasicBlock* nodeW = _nodes[w]->getBb(); + if (nodeW != nullptr) { + stepD(w, nodePool); + + // Copy nodePool to workList. + Vector workList{}; + nodePool.forEach([&workList](UnionFindNode* const& niter) -> void { + workList.append(niter); + }); + + if (nodePool.size() != 0) { + _type[w] = BB_REDUCIBLE; + } + + // work the list... + // + while (!workList.isEmpty()) { + UnionFindNode* x = workList.removeFirst(); + + const auto nonBackSize = static_cast( + (*_nonBackPreds.at(x->getDfsNumber()))->size()); + if (nonBackSize > MAXNONBACKPREDS) { + return; + } + stepEProcessNonBackPreds(w, nodePool, workList, x); + } + + if ((nodePool.size() > 0) || (_type[w] == BB_SELF)) { + SimpleLoop* loop = + _lsg.createNewLoop(nodeW, _type[w] != BB_IRREDUCIBLE); + setLoopAttributes(w, nodePool, loop); + } + } + } // Step c + } // findLoops + + private: + bool isAncestor(int32_t w, int32_t v) { return w <= v && v <= _last[w]; } + + int32_t doDFS(BasicBlock* currentNode, int32_t current) { + _nodes[current]->initNode(currentNode, current); + _number.atPut(currentNode, current); + + int32_t lastId = current; + const Vector& outerBlocks = currentNode->getOutEdges(); + + for (int32_t i = 0; i < static_cast(outerBlocks.size()); i += 1) { + BasicBlock* target = *outerBlocks.at(i); + if (*_number.at(target) == UNVISITED) { + lastId = doDFS(target, lastId + 1); + } + } + + _last[current] = lastId; + return lastId; + } + + void initAllNodes() { + _cfg.getBasicBlocks().forEach([this](BasicBlock* const& bb) -> void { + _number.atPut(bb, UNVISITED); + }); + doDFS(_cfg.getStartBasicBlock(), 0); + } + + void identifyEdges(int32_t size) { + for (int32_t w = 0; w < size; w += 1) { + _header[w] = 0; + _type[w] = BB_NONHEADER; + + BasicBlock* nodeW = _nodes[w]->getBb(); + if (nodeW == nullptr) { + _type[w] = BB_DEAD; + } else { + processEdges(nodeW, w); + } + } + } + + void processEdges(BasicBlock* nodeW, int32_t w) { + if (nodeW->getNumPred() > 0) { + nodeW->getInEdges().forEach([this, w](BasicBlock* const& nodeV) -> void { + const int32_t v = *_number.at(nodeV); + if (v != UNVISITED) { + if (isAncestor(w, v)) { + (*_backPreds.at(w))->append(v); + } else { + (*_nonBackPreds.at(w))->add(v); + } + } + }); + } + } + + void stepEProcessNonBackPreds(int32_t w, + Vector& nodePool, + Vector& workList, + UnionFindNode* x) { + (*_nonBackPreds.at(x->getDfsNumber())) + ->forEach([this, w, &nodePool, &workList](int32_t const& iter) -> void { + UnionFindNode* y = _nodes[iter]; + UnionFindNode* ydash = y->findSet(); + + if (!isAncestor(w, ydash->getDfsNumber())) { + _type[w] = BB_IRREDUCIBLE; + (*_nonBackPreds.at(w))->add(ydash->getDfsNumber()); + } else { + if (ydash->getDfsNumber() != w) { + if (!nodePool.hasSome([ydash](UnionFindNode* const& e) -> bool { + return e == ydash; + })) { + workList.append(ydash); + nodePool.append(ydash); + } + } + } + }); + } + + void setLoopAttributes(int32_t w, + Vector& nodePool, + SimpleLoop* loop) { + _nodes[w]->setLoop(loop); + + nodePool.forEach([this, w, loop](UnionFindNode* const& node) -> void { + _header[node->getDfsNumber()] = w; + node->unionSet(_nodes[w]); + + // Nested loops are not added, but linked together. + if (node->getLoop() != nullptr) { + node->getLoop()->setParent(loop); + } else { + loop->addNode(node->getBb()); + } + }); + } + + void stepD(int32_t w, Vector& nodePool) { + (*_backPreds.at(w)) + ->forEach([this, w, &nodePool](int32_t const& v) -> void { + if (v != w) { + nodePool.append(_nodes[v]->findSet()); + } else { + _type[w] = BB_SELF; + } + }); + } +}; + +class LoopTesterApp { + private: + ControlFlowGraph _cfg{}; + LoopStructureGraph _lsg{}; + + int32_t buildDiamond(int32_t start) { + const int32_t bb0 = start; + new BasicBlockEdge(_cfg, bb0, bb0 + 1); + new BasicBlockEdge(_cfg, bb0, bb0 + 2); + new BasicBlockEdge(_cfg, bb0 + 1, bb0 + 3); + new BasicBlockEdge(_cfg, bb0 + 2, bb0 + 3); + + return bb0 + 3; + } + + void buildConnect(int32_t start, int32_t end) { + new BasicBlockEdge(_cfg, start, end); + } + + int32_t buildStraight(int32_t start, int32_t n) { + for (int32_t i = 0; i < n; i += 1) { + buildConnect(start + i, start + i + 1); + } + return start + n; + } + + int32_t buildBaseLoop(int32_t from) { + const int32_t header = buildStraight(from, 1); + const int32_t diamond1 = buildDiamond(header); + const int32_t d11 = buildStraight(diamond1, 1); + const int32_t diamond2 = buildDiamond(d11); + int32_t footer = buildStraight(diamond2, 1); + buildConnect(diamond2, d11); + buildConnect(diamond1, header); + buildConnect(footer, from); + footer = buildStraight(footer, 1); + return footer; + } + + void constructSimpleCFG() { + _cfg.createNode(0); + buildBaseLoop(0); + _cfg.createNode(1); + + new BasicBlockEdge(_cfg, 0, 2); + } + + public: + LoopTesterApp() { _cfg.createNode(0); } + + std::array main(int32_t numDummyLoops, + int32_t findLoopIterations, + int32_t parLoops, + int32_t pparLoops, + int32_t ppparLoops) { + constructSimpleCFG(); + addDummyLoops(numDummyLoops); + constructCFG(parLoops, pparLoops, ppparLoops); + findLoops(_lsg); + for (int32_t i = 0; i < findLoopIterations; i += 1) { + LoopStructureGraph l{}; + findLoops(l); + } + + _lsg.calculateNestingLevel(); + return {_lsg.getNumLoops(), _cfg.getNumNodes()}; + } + + void constructCFG(int32_t parLoops, int32_t pparLoops, int32_t ppparLoops) { + int32_t n = 2; + + for (int32_t parlooptrees = 0; parlooptrees < parLoops; parlooptrees += 1) { + _cfg.createNode(n + 1); + buildConnect(2, n + 1); + n += 1; + + for (int32_t i = 0; i < pparLoops; i += 1) { + const int32_t top = n; + n = buildStraight(n, 1); + for (int32_t j = 0; j < ppparLoops; j += 1) { + n = buildBaseLoop(n); + } + const int32_t bottom = buildStraight(n, 1); + buildConnect(n, top); + n = bottom; + } + buildConnect(n, 1); + } + } + + void addDummyLoops(int32_t numDummyLoops) { + for (int32_t dummyloop = 0; dummyloop < numDummyLoops; dummyloop += 1) { + findLoops(_lsg); + } + } + + void findLoops(LoopStructureGraph& loopStructure) { + HavlakLoopFinder finder(_cfg, loopStructure); + finder.findLoops(); + } +}; + +class Havlak : public Benchmark { + public: + bool inner_benchmark_loop(int32_t inner_iterations) override { + const bool result = verifyResult( + LoopTesterApp().main(inner_iterations, 50, 10 /* was 100 */, 10, 5), + inner_iterations); + return result; + } + + bool verifyResult(std::array r, int32_t innerIterations) { + if (innerIterations == 15000) { + return r[0] == 46602 && r[1] == 5213; + } + if (innerIterations == 1500) { + return r[0] == 6102 && r[1] == 5213; + } + if (innerIterations == 150) { + return r[0] == 2052 && r[1] == 5213; + } + if (innerIterations == 15) { + return r[0] == 1647 && r[1] == 5213; + } + if (innerIterations == 1) { + return r[0] == 1605 && r[1] == 5213; + } + + cout << "No verification result for " << innerIterations << " found\n"; + cout << "Result is: " << r[0] << ", " << r[1] << "\n"; + + return false; + } + + std::any benchmark() override { throw Error("Should never be reached"); } + + bool verify_result(std::any) override { + throw Error("Should never be reached"); + } +}; diff --git a/benchmarks/C++/src/json.h b/benchmarks/C++/src/json.h new file mode 100644 index 00000000..ff9fbd4f --- /dev/null +++ b/benchmarks/C++/src/json.h @@ -0,0 +1,1007 @@ +#pragma once + +#include +#include + +#include "benchmark.h" +#include "som/error.h" +#include "som/vector.h" + +using std::string; + +const string rapBenchmarkMinified = // NOLINT + "{\"head\":{\"requestCounter\":4},\"operations\":[[\"destroy\",\"w54\"],[" + "\"set\",\"w2\",{\"activeControl\":\"w99\"}],[\"set\",\"w21\",{" + "\"customVariant\":\"variant_navigation\"}],[\"set\",\"w28\",{" + "\"customVariant\":\"variant_selected\"}],[\"set\",\"w53\",{\"children\":[" + "\"w95\"]}],[\"create\",\"w95\",\"rwt.widgets.Composite\",{\"parent\":" + "\"w53\",\"style\":[\"NONE\"],\"bounds\":[0,0,1008,586],\"children\":[" + "\"w96\",\"w97\"],\"tabIndex\":-1,\"clientArea\":[0,0,1008,586]}],[" + "\"create\",\"w96\",\"rwt.widgets.Label\",{\"parent\":\"w95\",\"style\":[" + "\"NONE\"],\"bounds\":[10,30,112,26],\"tabIndex\":-1,\"customVariant\":" + "\"variant_pageHeadline\",\"text\":\"TableViewer\"}],[\"create\",\"w97\"," + "\"rwt.widgets.Composite\",{\"parent\":\"w95\",\"style\":[\"NONE\"]," + "\"bounds\":[0,61,1008,525],\"children\":[\"w98\",\"w99\",\"w226\"," + "\"w228\"],\"tabIndex\":-1,\"clientArea\":[0,0,1008,525]}],[\"create\"," + "\"w98\",\"rwt.widgets.Text\",{\"parent\":\"w97\",\"style\":[\"LEFT\"," + "\"SINGLE\",\"BORDER\"],\"bounds\":[10,10,988,32],\"tabIndex\":22," + "\"activeKeys\":[\"#13\",\"#27\",\"#40\"]}],[\"listen\",\"w98\",{" + "\"KeyDown\":true,\"Modify\":true}],[\"create\",\"w99\",\"rwt.widgets." + "Grid\",{\"parent\":\"w97\",\"style\":[\"SINGLE\",\"BORDER\"]," + "\"appearance\":\"table\",\"indentionWidth\":0,\"treeColumn\":-1," + "\"markupEnabled\":false}],[\"create\",\"w100\",\"rwt.widgets.ScrollBar\",{" + "\"parent\":\"w99\",\"style\":[\"HORIZONTAL\"]}],[\"create\",\"w101\"," + "\"rwt.widgets.ScrollBar\",{\"parent\":\"w99\",\"style\":[\"VERTICAL\"]}],[" + "\"set\",\"w99\",{\"bounds\":[10,52,988,402],\"children\":[],\"tabIndex\":" + "23,\"activeKeys\":[\"CTRL+#70\",\"CTRL+#78\",\"CTRL+#82\",\"CTRL+#89\"," + "\"CTRL+#83\",\"CTRL+#71\",\"CTRL+#69\"],\"cancelKeys\":[\"CTRL+#70\"," + "\"CTRL+#78\",\"CTRL+#82\",\"CTRL+#89\",\"CTRL+#83\",\"CTRL+#71\",\"CTRL+#" + "69\"]}],[\"listen\",\"w99\",{\"MouseDown\":true,\"MouseUp\":true," + "\"MouseDoubleClick\":true,\"KeyDown\":true}],[\"set\",\"w99\",{" + "\"itemCount\":118,\"itemHeight\":28,\"itemMetrics\":[[0,0,50,3,0,3,44],[1," + "50,50,53,0,53,44],[2,100,140,103,0,103,134],[3,240,180,243,0,243,174],[4," + "420,50,423,0,423,44],[5,470,50,473,0,473,44]],\"columnCount\":6," + "\"headerHeight\":35,\"headerVisible\":true,\"linesVisible\":true," + "\"focusItem\":\"w108\",\"selection\":[\"w108\"]}],[\"listen\",\"w99\",{" + "\"Selection\":true,\"DefaultSelection\":true}],[\"set\",\"w99\",{" + "\"enableCellToolTip\":true}],[\"listen\",\"w100\",{\"Selection\":true}],[" + "\"set\",\"w101\",{\"visibility\":true}],[\"listen\",\"w101\",{" + "\"Selection\":true}],[\"create\",\"w102\",\"rwt.widgets.GridColumn\",{" + "\"parent\":\"w99\",\"text\":\"Nr.\",\"width\":50,\"moveable\":true}],[" + "\"listen\",\"w102\",{\"Selection\":true}],[\"create\",\"w103\",\"rwt." + "widgets.GridColumn\",{\"parent\":\"w99\",\"text\":\"Sym.\",\"index\":1," + "\"left\":50,\"width\":50,\"moveable\":true}],[\"listen\",\"w103\",{" + "\"Selection\":true}],[\"create\",\"w104\",\"rwt.widgets.GridColumn\",{" + "\"parent\":\"w99\",\"text\":\"Name\",\"index\":2,\"left\":100,\"width\":" + "140,\"moveable\":true}],[\"listen\",\"w104\",{\"Selection\":true}],[" + "\"create\",\"w105\",\"rwt.widgets.GridColumn\",{\"parent\":\"w99\"," + "\"text\":\"Series\",\"index\":3,\"left\":240,\"width\":180,\"moveable\":" + "true}],[\"listen\",\"w105\",{\"Selection\":true}],[\"create\",\"w106\"," + "\"rwt.widgets.GridColumn\",{\"parent\":\"w99\",\"text\":\"Group\"," + "\"index\":4,\"left\":420,\"width\":50,\"moveable\":true}],[\"listen\"," + "\"w106\",{\"Selection\":true}],[\"create\",\"w107\",\"rwt.widgets." + "GridColumn\",{\"parent\":\"w99\",\"text\":\"Period\",\"index\":5,\"left\":" + "470,\"width\":50,\"moveable\":true}],[\"listen\",\"w107\",{\"Selection\":" + "true}],[\"create\",\"w108\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":0,\"texts\":[\"1\",\"H\",\"Hydrogen\",\"Nonmetal\",\"1\",\"1\"]," + "\"cellBackgrounds\":[null,null,null,[138,226,52,255],null,null]}],[" + "\"create\",\"w109\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":1,\"texts\":[\"2\",\"He\",\"Helium\",\"Noble " + "gas\",\"18\",\"1\"],\"cellBackgrounds\":[null,null,null,[114,159,207,255]," + "null,null]}],[\"create\",\"w110\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":2,\"texts\":[\"3\",\"Li\",\"Lithium\",\"Alkali " + "metal\",\"1\",\"2\"],\"cellBackgrounds\":[null,null,null,[239,41,41,255]," + "null,null]}],[\"create\",\"w111\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":3,\"texts\":[\"4\",\"Be\",\"Beryllium\",\"Alkaline " + "earth " + "metal\",\"2\",\"2\"],\"cellBackgrounds\":[null,null,null,[233,185,110,255]" + ",null,null]}],[\"create\",\"w112\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":4,\"texts\":[\"5\",\"B\",\"Boron\",\"Metalloid\",\"13\"," + "\"2\"],\"cellBackgrounds\":[null,null,null,[156,159,153,255],null,null]}]," + "[\"create\",\"w113\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":5,\"texts\":[\"6\",\"C\",\"Carbon\",\"Nonmetal\",\"14\",\"2\"]," + "\"cellBackgrounds\":[null,null,null,[138,226,52,255],null,null]}],[" + "\"create\",\"w114\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":6,\"texts\":[\"7\",\"N\",\"Nitrogen\",\"Nonmetal\",\"15\",\"2\"]" + ",\"cellBackgrounds\":[null,null,null,[138,226,52,255],null,null]}],[" + "\"create\",\"w115\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":7,\"texts\":[\"8\",\"O\",\"Oxygen\",\"Nonmetal\",\"16\",\"2\"]," + "\"cellBackgrounds\":[null,null,null,[138,226,52,255],null,null]}],[" + "\"create\",\"w116\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":8,\"texts\":[\"9\",\"F\",\"Fluorine\",\"Halogen\",\"17\",\"2\"]," + "\"cellBackgrounds\":[null,null,null,[252,233,79,255],null,null]}],[" + "\"create\",\"w117\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":9,\"texts\":[\"10\",\"Ne\",\"Neon\",\"Noble " + "gas\",\"18\",\"2\"],\"cellBackgrounds\":[null,null,null,[114,159,207,255]," + "null,null]}],[\"create\",\"w118\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":10,\"texts\":[\"11\",\"Na\",\"Sodium\",\"Alkali " + "metal\",\"1\",\"3\"],\"cellBackgrounds\":[null,null,null,[239,41,41,255]," + "null,null]}],[\"create\",\"w119\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":11,\"texts\":[\"12\",\"Mg\",\"Magnesium\",\"Alkaline " + "earth " + "metal\",\"2\",\"3\"],\"cellBackgrounds\":[null,null,null,[233,185,110,255]" + ",null,null]}],[\"create\",\"w120\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":12,\"texts\":[\"13\",\"Al\",\"Aluminium\",\"Poor " + "metal\",\"13\",\"3\"],\"cellBackgrounds\":[null,null,null,[238,238,236," + "255],null,null]}],[\"create\",\"w121\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":13,\"texts\":[\"14\",\"Si\",\"Silicon\"," + "\"Metalloid\",\"14\",\"3\"],\"cellBackgrounds\":[null,null,null,[156,159," + "153,255],null,null]}],[\"create\",\"w122\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":14,\"texts\":[\"15\",\"P\",\"Phosphorus\"," + "\"Nonmetal\",\"15\",\"3\"],\"cellBackgrounds\":[null,null,null,[138,226," + "52,255],null,null]}],[\"create\",\"w123\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":15,\"texts\":[\"16\",\"S\",\"Sulfur\"," + "\"Nonmetal\",\"16\",\"3\"],\"cellBackgrounds\":[null,null,null,[138,226," + "52,255],null,null]}],[\"create\",\"w124\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":16,\"texts\":[\"17\",\"Cl\",\"Chlorine\"," + "\"Halogen\",\"17\",\"3\"],\"cellBackgrounds\":[null,null,null,[252,233,79," + "255],null,null]}],[\"create\",\"w125\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":17,\"texts\":[\"18\",\"Ar\",\"Argon\"," + "\"Noble " + "gas\",\"18\",\"3\"],\"cellBackgrounds\":[null,null,null,[114,159,207,255]," + "null,null]}],[\"create\",\"w126\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":18,\"texts\":[\"19\",\"K\",\"Potassium\",\"Alkali " + "metal\",\"1\",\"4\"],\"cellBackgrounds\":[null,null,null,[239,41,41,255]," + "null,null]}],[\"create\",\"w127\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":19,\"texts\":[\"20\",\"Ca\",\"Calcium\",\"Alkaline " + "earth " + "metal\",\"2\",\"4\"],\"cellBackgrounds\":[null,null,null,[233,185,110,255]" + ",null,null]}],[\"create\",\"w128\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":20,\"texts\":[\"21\",\"Sc\",\"Scandium\",\"Transition " + "metal\",\"3\",\"4\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w129\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":21,\"texts\":[\"22\",\"Ti\",\"Titanium\",\"Transition " + "metal\",\"4\",\"4\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w130\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":22,\"texts\":[\"23\",\"V\",\"Vanadium\",\"Transition " + "metal\",\"5\",\"4\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w131\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":23,\"texts\":[\"24\",\"Cr\",\"Chromium\",\"Transition " + "metal\",\"6\",\"4\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w132\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":24,\"texts\":[\"25\",\"Mn\",\"Manganese\",\"Transition " + "metal\",\"7\",\"4\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w133\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":25,\"texts\":[\"26\",\"Fe\",\"Iron\",\"Transition " + "metal\",\"8\",\"4\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w134\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":26,\"texts\":[\"27\",\"Co\",\"Cobalt\",\"Transition " + "metal\",\"9\",\"4\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w135\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":27,\"texts\":[\"28\",\"Ni\",\"Nickel\",\"Transition " + "metal\",\"10\",\"4\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]" + ",null,null]}],[\"create\",\"w136\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":28,\"texts\":[\"29\",\"Cu\",\"Copper\",\"Transition " + "metal\",\"11\",\"4\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]" + ",null,null]}],[\"create\",\"w137\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":29,\"texts\":[\"30\",\"Zn\",\"Zinc\",\"Transition " + "metal\",\"12\",\"4\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]" + ",null,null]}],[\"create\",\"w138\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":30,\"texts\":[\"31\",\"Ga\",\"Gallium\",\"Poor " + "metal\",\"13\",\"4\"],\"cellBackgrounds\":[null,null,null,[238,238,236," + "255],null,null]}],[\"create\",\"w139\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":31,\"texts\":[\"32\",\"Ge\",\"Germanium\"," + "\"Metalloid\",\"14\",\"4\"],\"cellBackgrounds\":[null,null,null,[156,159," + "153,255],null,null]}],[\"create\",\"w140\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":32,\"texts\":[\"33\",\"As\",\"Arsenic\"," + "\"Metalloid\",\"15\",\"4\"],\"cellBackgrounds\":[null,null,null,[156,159," + "153,255],null,null]}],[\"create\",\"w141\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":33,\"texts\":[\"34\",\"Se\",\"Selenium\"," + "\"Nonmetal\",\"16\",\"4\"],\"cellBackgrounds\":[null,null,null,[138,226," + "52,255],null,null]}],[\"create\",\"w142\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":34,\"texts\":[\"35\",\"Br\",\"Bromine\"," + "\"Halogen\",\"17\",\"4\"],\"cellBackgrounds\":[null,null,null,[252,233,79," + "255],null,null]}],[\"create\",\"w143\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":35,\"texts\":[\"36\",\"Kr\",\"Krypton\"," + "\"Noble " + "gas\",\"18\",\"4\"],\"cellBackgrounds\":[null,null,null,[114,159,207,255]," + "null,null]}],[\"create\",\"w144\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":36,\"texts\":[\"37\",\"Rb\",\"Rubidium\",\"Alkali " + "metal\",\"1\",\"5\"],\"cellBackgrounds\":[null,null,null,[239,41,41,255]," + "null,null]}],[\"create\",\"w145\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":37,\"texts\":[\"38\",\"Sr\",\"Strontium\",\"Alkaline " + "earth " + "metal\",\"2\",\"5\"],\"cellBackgrounds\":[null,null,null,[233,185,110,255]" + ",null,null]}],[\"create\",\"w146\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":38,\"texts\":[\"39\",\"Y\",\"Yttrium\",\"Transition " + "metal\",\"3\",\"5\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w147\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":39,\"texts\":[\"40\",\"Zr\",\"Zirconium\",\"Transition " + "metal\",\"4\",\"5\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w148\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":40,\"texts\":[\"41\",\"Nb\",\"Niobium\",\"Transition " + "metal\",\"5\",\"5\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w149\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":41,\"texts\":[\"42\",\"Mo\",\"Molybdenum\",\"Transition " + "metal\",\"6\",\"5\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w150\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":42,\"texts\":[\"43\",\"Tc\",\"Technetium\",\"Transition " + "metal\",\"7\",\"5\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w151\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":43,\"texts\":[\"44\",\"Ru\",\"Ruthenium\",\"Transition " + "metal\",\"8\",\"5\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w152\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":44,\"texts\":[\"45\",\"Rh\",\"Rhodium\",\"Transition " + "metal\",\"9\",\"5\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w153\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":45,\"texts\":[\"46\",\"Pd\",\"Palladium\",\"Transition " + "metal\",\"10\",\"5\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]" + ",null,null]}],[\"create\",\"w154\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":46,\"texts\":[\"47\",\"Ag\",\"Silver\",\"Transition " + "metal\",\"11\",\"5\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]" + ",null,null]}],[\"create\",\"w155\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":47,\"texts\":[\"48\",\"Cd\",\"Cadmium\",\"Transition " + "metal\",\"12\",\"5\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]" + ",null,null]}],[\"create\",\"w156\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":48,\"texts\":[\"49\",\"In\",\"Indium\",\"Poor " + "metal\",\"13\",\"5\"],\"cellBackgrounds\":[null,null,null,[238,238,236," + "255],null,null]}],[\"create\",\"w157\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":49,\"texts\":[\"50\",\"Sn\",\"Tin\",\"Poor " + "metal\",\"14\",\"5\"],\"cellBackgrounds\":[null,null,null,[238,238,236," + "255],null,null]}],[\"create\",\"w158\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":50,\"texts\":[\"51\",\"Sb\",\"Antimony\"," + "\"Metalloid\",\"15\",\"5\"],\"cellBackgrounds\":[null,null,null,[156,159," + "153,255],null,null]}],[\"create\",\"w159\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":51,\"texts\":[\"52\",\"Te\",\"Tellurium\"," + "\"Metalloid\",\"16\",\"5\"],\"cellBackgrounds\":[null,null,null,[156,159," + "153,255],null,null]}],[\"create\",\"w160\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":52,\"texts\":[\"53\",\"I\",\"Iodine\"," + "\"Halogen\",\"17\",\"5\"],\"cellBackgrounds\":[null,null,null,[252,233,79," + "255],null,null]}],[\"create\",\"w161\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":53,\"texts\":[\"54\",\"Xe\",\"Xenon\"," + "\"Noble " + "gas\",\"18\",\"5\"],\"cellBackgrounds\":[null,null,null,[114,159,207,255]," + "null,null]}],[\"create\",\"w162\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":54,\"texts\":[\"55\",\"Cs\",\"Caesium\",\"Alkali " + "metal\",\"1\",\"6\"],\"cellBackgrounds\":[null,null,null,[239,41,41,255]," + "null,null]}],[\"create\",\"w163\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":55,\"texts\":[\"56\",\"Ba\",\"Barium\",\"Alkaline earth " + "metal\",\"2\",\"6\"],\"cellBackgrounds\":[null,null,null,[233,185,110,255]" + ",null,null]}],[\"create\",\"w164\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":56,\"texts\":[\"57\",\"La\",\"Lanthanum\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w165\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":57,\"texts\":[\"58\",\"Ce\",\"Cerium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w166\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":58,\"texts\":[\"59\",\"Pr\",\"Praseodymium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w167\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":59,\"texts\":[\"60\",\"Nd\",\"Neodymium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w168\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":60,\"texts\":[\"61\",\"Pm\",\"Promethium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w169\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":61,\"texts\":[\"62\",\"Sm\",\"Samarium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w170\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":62,\"texts\":[\"63\",\"Eu\",\"Europium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w171\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":63,\"texts\":[\"64\",\"Gd\",\"Gadolinium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w172\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":64,\"texts\":[\"65\",\"Tb\",\"Terbium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w173\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":65,\"texts\":[\"66\",\"Dy\",\"Dysprosium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w174\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":66,\"texts\":[\"67\",\"Ho\",\"Holmium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w175\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":67,\"texts\":[\"68\",\"Er\",\"Erbium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w176\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":68,\"texts\":[\"69\",\"Tm\",\"Thulium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w177\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":69,\"texts\":[\"70\",\"Yb\",\"Ytterbium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w178\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":70,\"texts\":[\"71\",\"Lu\",\"Lutetium\"," + "\"Lanthanide\",\"3\",\"6\"],\"cellBackgrounds\":[null,null,null,[173,127," + "168,255],null,null]}],[\"create\",\"w179\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":71,\"texts\":[\"72\",\"Hf\",\"Hafnium\"," + "\"Transition " + "metal\",\"4\",\"6\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w180\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":72,\"texts\":[\"73\",\"Ta\",\"Tantalum\",\"Transition " + "metal\",\"5\",\"6\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w181\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":73,\"texts\":[\"74\",\"W\",\"Tungsten\",\"Transition " + "metal\",\"6\",\"6\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w182\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":74,\"texts\":[\"75\",\"Re\",\"Rhenium\",\"Transition " + "metal\",\"7\",\"6\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w183\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":75,\"texts\":[\"76\",\"Os\",\"Osmium\",\"Transition " + "metal\",\"8\",\"6\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w184\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":76,\"texts\":[\"77\",\"Ir\",\"Iridium\",\"Transition " + "metal\",\"9\",\"6\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w185\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":77,\"texts\":[\"78\",\"Pt\",\"Platinum\",\"Transition " + "metal\",\"10\",\"6\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]" + ",null,null]}],[\"create\",\"w186\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":78,\"texts\":[\"79\",\"Au\",\"Gold\",\"Transition " + "metal\",\"11\",\"6\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]" + ",null,null]}],[\"create\",\"w187\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":79,\"texts\":[\"80\",\"Hg\",\"Mercury\",\"Transition " + "metal\",\"12\",\"6\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]" + ",null,null]}],[\"create\",\"w188\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":80,\"texts\":[\"81\",\"Tl\",\"Thallium\",\"Poor " + "metal\",\"13\",\"6\"],\"cellBackgrounds\":[null,null,null,[238,238,236," + "255],null,null]}],[\"create\",\"w189\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":81,\"texts\":[\"82\",\"Pb\",\"Lead\",\"Poor " + "metal\",\"14\",\"6\"],\"cellBackgrounds\":[null,null,null,[238,238,236," + "255],null,null]}],[\"create\",\"w190\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":82,\"texts\":[\"83\",\"Bi\",\"Bismuth\"," + "\"Poor " + "metal\",\"15\",\"6\"],\"cellBackgrounds\":[null,null,null,[238,238,236," + "255],null,null]}],[\"create\",\"w191\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":83,\"texts\":[\"84\",\"Po\",\"Polonium\"," + "\"Metalloid\",\"16\",\"6\"],\"cellBackgrounds\":[null,null,null,[156,159," + "153,255],null,null]}],[\"create\",\"w192\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":84,\"texts\":[\"85\",\"At\",\"Astatine\"," + "\"Halogen\",\"17\",\"6\"],\"cellBackgrounds\":[null,null,null,[252,233,79," + "255],null,null]}],[\"create\",\"w193\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":85,\"texts\":[\"86\",\"Rn\",\"Radon\"," + "\"Noble " + "gas\",\"18\",\"6\"],\"cellBackgrounds\":[null,null,null,[114,159,207,255]," + "null,null]}],[\"create\",\"w194\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":86,\"texts\":[\"87\",\"Fr\",\"Francium\",\"Alkali " + "metal\",\"1\",\"7\"],\"cellBackgrounds\":[null,null,null,[239,41,41,255]," + "null,null]}],[\"create\",\"w195\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":87,\"texts\":[\"88\",\"Ra\",\"Radium\",\"Alkaline earth " + "metal\",\"2\",\"7\"],\"cellBackgrounds\":[null,null,null,[233,185,110,255]" + ",null,null]}],[\"create\",\"w196\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":88,\"texts\":[\"89\",\"Ac\",\"Actinium\",\"Actinide\"," + "\"3\",\"7\"],\"cellBackgrounds\":[null,null,null,[173,127,168,255],null," + "null]}],[\"create\",\"w197\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":89,\"texts\":[\"90\",\"Th\",\"Thorium\",\"Actinide\",\"3\"," + "\"7\"],\"cellBackgrounds\":[null,null,null,[173,127,168,255],null,null]}]," + "[\"create\",\"w198\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":90,\"texts\":[\"91\",\"Pa\",\"Protactinium\",\"Actinide\",\"3\"," + "\"7\"],\"cellBackgrounds\":[null,null,null,[173,127,168,255],null,null]}]," + "[\"create\",\"w199\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":91,\"texts\":[\"92\",\"U\",\"Uranium\",\"Actinide\",\"3\",\"7\"]" + ",\"cellBackgrounds\":[null,null,null,[173,127,168,255],null,null]}],[" + "\"create\",\"w200\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":92,\"texts\":[\"93\",\"Np\",\"Neptunium\",\"Actinide\",\"3\"," + "\"7\"],\"cellBackgrounds\":[null,null,null,[173,127,168,255],null,null]}]," + "[\"create\",\"w201\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":93,\"texts\":[\"94\",\"Pu\",\"Plutonium\",\"Actinide\",\"3\"," + "\"7\"],\"cellBackgrounds\":[null,null,null,[173,127,168,255],null,null]}]," + "[\"create\",\"w202\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":94,\"texts\":[\"95\",\"Am\",\"Americium\",\"Actinide\",\"3\"," + "\"7\"],\"cellBackgrounds\":[null,null,null,[173,127,168,255],null,null]}]," + "[\"create\",\"w203\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":95,\"texts\":[\"96\",\"Cm\",\"Curium\",\"Actinide\",\"3\",\"7\"]" + ",\"cellBackgrounds\":[null,null,null,[173,127,168,255],null,null]}],[" + "\"create\",\"w204\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":96,\"texts\":[\"97\",\"Bk\",\"Berkelium\",\"Actinide\",\"3\"," + "\"7\"],\"cellBackgrounds\":[null,null,null,[173,127,168,255],null,null]}]," + "[\"create\",\"w205\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":97,\"texts\":[\"98\",\"Cf\",\"Californium\",\"Actinide\",\"3\"," + "\"7\"],\"cellBackgrounds\":[null,null,null,[173,127,168,255],null,null]}]," + "[\"create\",\"w206\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":98,\"texts\":[\"99\",\"Es\",\"Einsteinium\",\"Actinide\",\"3\"," + "\"7\"],\"cellBackgrounds\":[null,null,null,[173,127,168,255],null,null]}]," + "[\"create\",\"w207\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":99,\"texts\":[\"100\",\"Fm\",\"Fermium\",\"Actinide\",\"3\"," + "\"7\"],\"cellBackgrounds\":[null,null,null,[173,127,168,255],null,null]}]," + "[\"create\",\"w208\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":100,\"texts\":[\"101\",\"Md\",\"Mendelevium\",\"Actinide\"," + "\"3\",\"7\"],\"cellBackgrounds\":[null,null,null,[173,127,168,255],null," + "null]}],[\"create\",\"w209\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":101,\"texts\":[\"102\",\"No\",\"Nobelium\",\"Actinide\",\"3\"," + "\"7\"],\"cellBackgrounds\":[null,null,null,[173,127,168,255],null,null]}]," + "[\"create\",\"w210\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":102,\"texts\":[\"103\",\"Lr\",\"Lawrencium\",\"Actinide\",\"3\"," + "\"7\"],\"cellBackgrounds\":[null,null,null,[173,127,168,255],null,null]}]," + "[\"create\",\"w211\",\"rwt.widgets.GridItem\",{\"parent\":\"w99\"," + "\"index\":103,\"texts\":[\"104\",\"Rf\",\"Rutherfordium\",\"Transition " + "metal\",\"4\",\"7\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w212\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":104,\"texts\":[\"105\",\"Db\",\"Dubnium\",\"Transition " + "metal\",\"5\",\"7\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w213\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":105,\"texts\":[\"106\",\"Sg\",\"Seaborgium\"," + "\"Transition " + "metal\",\"6\",\"7\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w214\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":106,\"texts\":[\"107\",\"Bh\",\"Bohrium\",\"Transition " + "metal\",\"7\",\"7\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w215\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":107,\"texts\":[\"108\",\"Hs\",\"Hassium\",\"Transition " + "metal\",\"8\",\"7\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w216\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":108,\"texts\":[\"109\",\"Mt\",\"Meitnerium\"," + "\"Transition " + "metal\",\"9\",\"7\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]," + "null,null]}],[\"create\",\"w217\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":109,\"texts\":[\"110\",\"Ds\",\"Darmstadtium\"," + "\"Transition " + "metal\",\"10\",\"7\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]" + ",null,null]}],[\"create\",\"w218\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":110,\"texts\":[\"111\",\"Rg\",\"Roentgenium\"," + "\"Transition " + "metal\",\"11\",\"7\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]" + ",null,null]}],[\"create\",\"w219\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":111,\"texts\":[\"112\",\"Uub\",\"Ununbium\"," + "\"Transition " + "metal\",\"12\",\"7\"],\"cellBackgrounds\":[null,null,null,[252,175,62,255]" + ",null,null]}],[\"create\",\"w220\",\"rwt.widgets.GridItem\",{\"parent\":" + "\"w99\",\"index\":112,\"texts\":[\"113\",\"Uut\",\"Ununtrium\",\"Poor " + "metal\",\"13\",\"7\"],\"cellBackgrounds\":[null,null,null,[238,238,236," + "255],null,null]}],[\"create\",\"w221\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":113,\"texts\":[\"114\",\"Uuq\"," + "\"Ununquadium\",\"Poor " + "metal\",\"14\",\"7\"],\"cellBackgrounds\":[null,null,null,[238,238,236," + "255],null,null]}],[\"create\",\"w222\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":114,\"texts\":[\"115\",\"Uup\"," + "\"Ununpentium\",\"Poor " + "metal\",\"15\",\"7\"],\"cellBackgrounds\":[null,null,null,[238,238,236," + "255],null,null]}],[\"create\",\"w223\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":115,\"texts\":[\"116\",\"Uuh\"," + "\"Ununhexium\",\"Poor " + "metal\",\"16\",\"7\"],\"cellBackgrounds\":[null,null,null,[238,238,236," + "255],null,null]}],[\"create\",\"w224\",\"rwt.widgets.GridItem\",{" + "\"parent\":\"w99\",\"index\":116,\"texts\":[\"117\",\"Uus\"," + "\"Ununseptium\",\"Halogen\",\"17\",\"7\"],\"cellBackgrounds\":[null,null," + "null,[252,233,79,255],null,null]}],[\"create\",\"w225\",\"rwt.widgets." + "GridItem\",{\"parent\":\"w99\",\"index\":117,\"texts\":[\"118\",\"Uuo\"," + "\"Ununoctium\",\"Noble " + "gas\",\"18\",\"7\"],\"cellBackgrounds\":[null,null,null,[114,159,207,255]," + "null,null]}],[\"create\",\"w226\",\"rwt.widgets.Composite\",{\"parent\":" + "\"w97\",\"style\":[\"BORDER\"],\"bounds\":[10,464,988,25],\"children\":[" + "\"w227\"],\"tabIndex\":-1,\"clientArea\":[0,0,986,23]}],[\"create\"," + "\"w227\",\"rwt.widgets.Label\",{\"parent\":\"w226\",\"style\":[\"NONE\"]," + "\"bounds\":[10,10,966,3],\"tabIndex\":-1,\"text\":\"Hydrogen " + "(H)\"}],[\"create\",\"w228\",\"rwt.widgets.Label\",{\"parent\":\"w97\"," + "\"style\":[\"WRAP\"],\"bounds\":[10,499,988,16],\"tabIndex\":-1," + "\"foreground\":[150,150,150,255],\"font\":[[\"Verdana\",\"Lucida " + "Sans\",\"Arial\",\"Helvetica\",\"sans-serif\"],10,false,false],\"text\":" + "\"Shortcuts: [CTRL+F] - Filter | Sort by: [CTRL+R] - Number, [CTRL+Y] - " + "Symbol, [CTRL+N] - Name, [CTRL+S] - Series, [CTRL+G] - Group, [CTRL+E] - " + "Period\"}],[\"set\",\"w1\",{\"focusControl\":\"w99\"}],[\"call\",\"rwt." + "client.BrowserNavigation\",\"addToHistory\",{\"entries\":[[" + "\"tableviewer\",\"TableViewer\"]]}]]}"; + +class HashIndexTable { + std::array _hashTable{}; + + public: + void add(const std::string& name, int32_t index) { + const int32_t slot = hashSlotFor(name); + if (index < 0xff) { + // increment by 1, 0 stands for empty + _hashTable[slot] = (index + 1) & 0xff; + } else { + _hashTable[slot] = 0; + } + } + + [[nodiscard]] int32_t get(const std::string& name) const { + const int32_t slot = hashSlotFor(name); + // subtract 1, 0 stands for empty + return (_hashTable[slot] & 0xff) - 1; + } + + private: + [[nodiscard]] int32_t stringHash(const std::string& s) const { + // this is not a proper hash, but sufficient for the benchmark, + // and very portable! + return static_cast(s.size()) * 1402589; + } + + [[nodiscard]] int32_t hashSlotFor(const std::string& element) const { + return stringHash(element) & (static_cast(_hashTable.size()) - 1); + } +}; + +class JsonArray; +class JsonObject; + +class JsonValue { + public: + JsonValue() = default; + virtual ~JsonValue() = default; + + [[nodiscard]] virtual bool isObject() const { return false; } + [[nodiscard]] virtual bool isArray() const { return false; } + [[nodiscard]] virtual bool isNumber() const { return false; } + [[nodiscard]] virtual bool isString() const { return false; } + [[nodiscard]] virtual bool isBoolean() const { return false; } + [[nodiscard]] virtual bool isTrue() const { return false; } + [[nodiscard]] virtual bool isFalse() const { return false; } + [[nodiscard]] virtual bool isNull() const { return false; } + + [[nodiscard]] virtual const JsonObject* asObject() const { + throw Error("Not an object: " + toString()); + } + + [[nodiscard]] virtual const JsonArray* asArray() const { + throw Error("Not an array: " + toString()); + } + + [[nodiscard]] virtual string toString() const { return ""; } +}; + +class JsonArray : public JsonValue { + private: + Vector _values{}; + + public: + JsonArray() = default; + ~JsonArray() override { _values.destroyValues(); } + + void add(const JsonValue* value) { + if (value == nullptr) { + throw Error("value is null"); + } + _values.append(value); + } + + [[nodiscard]] size_t size() const { return _values.size(); } + + [[nodiscard]] const JsonValue* get(int32_t index) const { + return *_values.at(index); + } + + [[nodiscard]] bool isArray() const override { return true; } + + [[nodiscard]] const JsonArray* asArray() const override { return this; } +}; + +class JsonLiteral : public JsonValue { + private: + string _value; + bool _isNull; + bool _isTrue; + bool _isFalse; + + JsonLiteral(string value, bool isNull, bool isTrue, bool isFalse) + : _value(std::move(value)), + _isNull(isNull), + _isTrue(isTrue), + _isFalse(isFalse) {} + + public: + explicit JsonLiteral(const string& value) + : _value(value), + _isNull(value == "null"), + _isTrue(value == "true"), + _isFalse(value == "false") {} + + [[nodiscard]] bool isNull() const override { return _isNull; } + [[nodiscard]] bool isTrue() const override { return _isTrue; } + [[nodiscard]] bool isFalse() const override { return _isFalse; } + [[nodiscard]] bool isBoolean() const override { return _isTrue || _isFalse; } + + [[nodiscard]] string toString() const override { return _value; } + + static JsonLiteral* createNull() { + return new JsonLiteral("null", true, false, false); + } + + static JsonLiteral* createTrue() { + return new JsonLiteral("true", false, true, false); + } + + static JsonLiteral* createFalse() { + return new JsonLiteral("false", false, false, true); + } +}; + +class JsonNumber : public JsonValue { + private: + string _string; + + public: + explicit JsonNumber(const string& string) : _string(string) { + if (string.empty()) { + throw Error("value is null"); + } + } + + [[nodiscard]] string toString() const override { return _string; } + [[nodiscard]] bool isNumber() const override { return true; } +}; + +class JsonObject : public JsonValue { + private: + Vector _names{}; + Vector _values{}; + HashIndexTable _table{}; + + [[nodiscard]] int32_t indexOf(const string& name) const { + const int32_t index = _table.get(name); + if (index != -1 && name == *_names.at(index)) { + return index; + } + throw Error("not yet implemented, not relevant for the benchmark"); + } + + public: + JsonObject() = default; + ~JsonObject() override { _values.destroyValues(); } + + void add(const string& name, const JsonValue* value) { + if (name.empty()) { + throw Error("name is null"); + } + if (value == nullptr) { + throw Error("value is null"); + } + _table.add(name, static_cast(_names.size())); + _names.append(name); + _values.append(value); + } + + [[nodiscard]] const JsonValue* get(const string& name) const { + if (name.empty()) { + throw Error("name is null"); + } + const int32_t index = indexOf(name); + return index == -1 ? nullptr : *_values.at(index); + } + + [[nodiscard]] size_t size() const { return _names.size(); } + [[nodiscard]] bool isEmpty() const { return _names.isEmpty(); } + [[nodiscard]] bool isObject() const override { return true; } + [[nodiscard]] const JsonObject* asObject() const override { return this; } +}; + +class JsonString : public JsonValue { + private: + string _string; + + public: + explicit JsonString(string string) : _string(std::move(string)) {} + + [[nodiscard]] bool isString() const override { return true; } +}; + +class ParseException : virtual public std::exception { + private: + size_t _offset; + int32_t _line; + int32_t _column; + string _what; + + public: + ParseException(const string& message, + size_t offset, + int32_t line, + int32_t column) + : _offset(offset), + _line(line), + _column(column), + _what(message + " at " + std::to_string(line) + ":" + + std::to_string(column)) {} + + [[nodiscard]] size_t getOffset() const { return _offset; } + [[nodiscard]] int32_t getLine() const { return _line; } + [[nodiscard]] int32_t getColumn() const { return _column; } + [[nodiscard]] const char* what() const noexcept override { + return (_what.c_str()); + } +}; + +class JsonPureStringParser { + private: + string _input; + size_t _index{SIZE_MAX}; + int32_t _line{1}; + int32_t _column{0}; + string _current{}; + string _captureBuffer{}; + size_t _captureStart{SIZE_MAX}; + + const JsonValue* readValue() { + if (_current == "n") { + return readNull(); + } + if (_current == "t") { + return readTrue(); + } + if (_current == "f") { + return readFalse(); + } + if (_current == "\"") { + return readString(); + } + if (_current == "[") { + return readArray(); + } + if (_current == "{") { + return readObject(); + } + if (_current == "-" || _current == "0" || _current == "1" || + _current == "2" || _current == "3" || _current == "4" || + _current == "5" || _current == "6" || _current == "7" || + _current == "8" || _current == "9") { + return readNumber(); + } + + throw expected("value"); + } + + JsonObject* readObject() { + read(); + auto* object = new JsonObject(); + skipWhiteSpace(); + if (readChar("}")) { + return object; + } + do { + skipWhiteSpace(); + const string name = readName(); + skipWhiteSpace(); + if (!readChar(":")) { + throw expected("':'"); + } + skipWhiteSpace(); + + object->add(name, readValue()); + skipWhiteSpace(); + } while (readChar(",")); + if (!readChar("}")) { + throw expected("',' or '}'"); + } + return object; + } + + string readName() { + if (_current != "\"") { + throw expected("name"); + } + return readStringInternal(); + } + + JsonArray* readArray() { + read(); + auto* array = new JsonArray(); + skipWhiteSpace(); + if (readChar("]")) { + return array; + } + do { + skipWhiteSpace(); + array->add(readValue()); + skipWhiteSpace(); + } while (readChar(",")); + + if (!readChar("]")) { + throw expected("',' or ']'"); + } + return array; + } + + const JsonValue* readNull() { + read(); + readRequiredChar("u"); + readRequiredChar("l"); + readRequiredChar("l"); + return JsonLiteral::createNull(); + } + + const JsonValue* readTrue() { + read(); + readRequiredChar("r"); + readRequiredChar("u"); + readRequiredChar("e"); + return JsonLiteral::createTrue(); + } + + const JsonValue* readFalse() { + read(); + readRequiredChar("a"); + readRequiredChar("l"); + readRequiredChar("s"); + readRequiredChar("e"); + return JsonLiteral::createFalse(); + } + + void readRequiredChar(const string& ch) { + if (!readChar(ch)) { + throw expected("'" + ch + "'"); + } + } + + const JsonValue* readString() { return new JsonString(readStringInternal()); } + + string readStringInternal() { + read(); + startCapture(); + while (_current != "\"") { + if (_current == "\\") { + pauseCapture(); + readEscape(); + startCapture(); + } else { + read(); + } + } + string string = endCapture(); + read(); + return string; + } + + void readEscape() { + read(); + if (_current == "\"" || _current == "/" || _current == "\\") { + _captureBuffer += _current; + } else if (_current == "b") { + _captureBuffer += "\b"; + } else if (_current == "f") { + _captureBuffer += "\f"; + } else if (_current == "n") { + _captureBuffer += "\n"; + } else if (_current == "r") { + _captureBuffer += "\r"; + } else if (_current == "t") { + _captureBuffer += "\t"; + } else { + throw expected("valid escape sequence"); + } + read(); + } + + const JsonValue* readNumber() { + startCapture(); + readChar("-"); + const string firstDigit = _current; + if (!readDigit()) { + throw expected("digit"); + } + if (firstDigit != "0") { + while (readDigit()) { + } + } + readFraction(); + readExponent(); + return new JsonNumber(endCapture()); + } + + bool readFraction() { + if (!readChar(".")) { + return false; + } + if (!readDigit()) { + throw expected("digit"); + } + while (readDigit()) { + } + + return true; + } + + bool readExponent() { + if (!readChar("e") && !readChar("E")) { + return false; + } + if (!readChar("+")) { + readChar("-"); + } + if (!readDigit()) { + throw expected("digit"); + } + + while (readDigit()) { + } + return true; + } + + bool readChar(const string& ch) { + if (_current != ch) { + return false; + } + read(); + return true; + } + + bool readDigit() { + if (!isDigit()) { + return false; + } + read(); + return true; + } + + void skipWhiteSpace() { + while (isWhiteSpace()) { + read(); + } + } + + void read() { + if ("\n" == _current) { + _line += 1; + _column = 0; + } + _index += 1; + if (_index < _input.length()) { + _current = _input.substr(_index, 1); + } else { + _current = ""; + } + } + + void startCapture() { _captureStart = _index; } + + void pauseCapture() { + const size_t _end = _current.empty() ? _index : _index - 1; + _captureBuffer += _input.substr(_captureStart, _end - _captureStart + 1); + _captureStart = -1; + } + + string endCapture() { + const size_t _end = _current.empty() ? _index : _index - 1; + string captured; + if (_captureBuffer.empty()) { + captured = _input.substr(_captureStart, _end - _captureStart + 1); + } else { + _captureBuffer += _input.substr(_captureStart, _end - _captureStart + 1); + captured = _captureBuffer; + _captureBuffer = ""; + } + _captureStart = -1; + return captured; + } + + ParseException expected(const string& expected) { + if (isEndOfText()) { + return error("Unexpected end of input"); + } + + return error("Expected " + expected); + } + + [[nodiscard]] ParseException error(const string& message) const { + return {message, _index, _line, _column - 1}; + } + + bool isWhiteSpace() { + return " " == _current || "\t" == _current || "\n" == _current || + "\r" == _current; + } + + bool isDigit() { + return "0" == _current || "1" == _current || "2" == _current || + "3" == _current || "4" == _current || "5" == _current || + "6" == _current || "7" == _current || "8" == _current || + "9" == _current; + } + + bool isEndOfText() { return _current.empty(); } + + public: + explicit JsonPureStringParser(string string) : _input(std::move(string)) {} + + const JsonValue* parse() { + read(); + skipWhiteSpace(); + const JsonValue* result = readValue(); + skipWhiteSpace(); + if (!isEndOfText()) { + throw Error("Unexpected character"); + } + return result; + } +}; + +class Json : public Benchmark { + private: + public: + std::any benchmark() override { + JsonPureStringParser parser{rapBenchmarkMinified}; + const JsonValue* const result = parser.parse(); + return result; + } + + bool has_expected_content(const JsonValue* result) { + if (!result->isObject()) { + return false; + } + const auto* resultObject = result->asObject(); + if (!resultObject->get("head")->isObject()) { + return false; + } + if (!resultObject->get("operations")->isArray()) { + return false; + } + const auto* resultArray = resultObject->get("operations")->asArray(); + return resultArray->size() == 156; + } + + bool verify_result(std::any r) override { + const auto* const result = std::any_cast(r); + const bool doesVerify = has_expected_content(result); + delete result; + return doesVerify; + } +}; diff --git a/benchmarks/C++/src/list.h b/benchmarks/C++/src/list.h new file mode 100644 index 00000000..2513f388 --- /dev/null +++ b/benchmarks/C++/src/list.h @@ -0,0 +1,85 @@ +#pragma once + +#include + +#include "benchmark.h" + +class Element { + private: + std::any val; + Element* next{nullptr}; + + public: + explicit Element(std::any v) : val(std::move(v)) {} + + ~Element() { + if (next != nullptr) { + delete next; + } + } + + [[nodiscard]] int32_t length() { + if (next == nullptr) { + return 1; + } + return 1 + next->length(); + } + + [[nodiscard]] std::any getVal() const { return val; } + void setVal(std::any v) { val = std::move(v); } + [[nodiscard]] Element* getNext() const { return next; } + void setNext(Element* e) { next = e; } +}; + +class List : public Benchmark { + public: + std::any benchmark() override { + Element* x = makeList(15); + Element* y = makeList(10); + Element* z = makeList(6); + + Element* result = tail(x, y, z); + const int32_t l = result->length(); + + delete x; + delete y; + delete z; + return l; + } + + private: + [[nodiscard]] Element* makeList(int32_t length) const { + if (length == 0) { + return nullptr; + } + auto* e = new Element(length); + e->setNext(makeList(length - 1)); + return e; + } + + [[nodiscard]] bool isShorterThan(Element* x, Element* y) const { + Element* xTail = x; + Element* yTail = y; + + while (yTail != nullptr) { + if (xTail == nullptr) { + return true; + } + xTail = xTail->getNext(); + yTail = yTail->getNext(); + } + return false; + } + + Element* tail(Element* x, Element* y, Element* z) { + if (isShorterThan(y, x)) { + return tail(tail(x->getNext(), y, z), tail(y->getNext(), z, x), + tail(z->getNext(), x, y)); + } + return z; + } + + bool verify_result(std::any result) override { + return 10 == std::any_cast(result); + } +}; diff --git a/benchmarks/C++/src/mandelbrot.h b/benchmarks/C++/src/mandelbrot.h new file mode 100644 index 00000000..f10cf536 --- /dev/null +++ b/benchmarks/C++/src/mandelbrot.h @@ -0,0 +1,92 @@ +#pragma once + +#include +#include + +#include "benchmark.h" + +class Mandelbrot : public Benchmark { + public: + std::any benchmark() override { return nullptr; } + bool verify_result(std::any) override { return false; } + + bool inner_benchmark_loop(int32_t inner_iterations) override { + return verify_result(mandelbrot(inner_iterations), inner_iterations); + } + + private: + bool verify_result(int32_t result, int32_t inner_iterations) { + if (inner_iterations == 500) { + return result == 191; + } + if (inner_iterations == 750) { + return result == 50; + } + if (inner_iterations == 1) { + return result == 128; + } + + std::cout << "No verification result for " << inner_iterations + << " found\n"; + std::cout << "Result is: " << result << "\n"; + return false; + } + + int32_t mandelbrot(int32_t size) { + int32_t sum = 0; + int32_t byte_acc = 0; + int32_t bit_num = 0; + + int32_t y = 0; + + while (y < size) { + const double ci = (2.0 * y / size) - 1.0; + int32_t x = 0; + + while (x < size) { + double zrzr = 0.0; + double zi = 0.0; + double zizi = 0.0; + const double cr = (2.0 * x / size) - 1.5; + + int32_t z = 0; + bool notDone = true; + int32_t escape = 0; + while (notDone && z < 50) { + const double zr = zrzr - zizi + cr; + zi = 2.0 * zr * zi + ci; + + // preserve recalculation + zrzr = zr * zr; + zizi = zi * zi; + + if (zrzr + zizi > 4.0) { + notDone = false; + escape = 1; + } + z += 1; + } + + byte_acc = (byte_acc << 1) + escape; + bit_num += 1; + + // Code is very similar for these cases, but using separate blocks + // ensures we skip the shifting when it's unnecessary, which is most + // cases. + if (bit_num == 8) { + sum ^= byte_acc; + byte_acc = 0; + bit_num = 0; + } else if (x == size - 1) { + byte_acc <<= (8 - bit_num); + sum ^= byte_acc; + byte_acc = 0; + bit_num = 0; + } + x += 1; + } + y += 1; + } + return sum; + } +}; diff --git a/benchmarks/C++/src/memory/object_tracker.cpp b/benchmarks/C++/src/memory/object_tracker.cpp new file mode 100644 index 00000000..47b126a3 --- /dev/null +++ b/benchmarks/C++/src/memory/object_tracker.cpp @@ -0,0 +1,10 @@ +#include "object_tracker.h" + +std::vector ObjectTracker::trackedObjects{}; + +void ObjectTracker::releaseAll() { + for (auto* obj : trackedObjects) { + delete obj; + } + trackedObjects.clear(); +} diff --git a/benchmarks/C++/src/memory/object_tracker.h b/benchmarks/C++/src/memory/object_tracker.h new file mode 100644 index 00000000..9355e075 --- /dev/null +++ b/benchmarks/C++/src/memory/object_tracker.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +class TrackedObject; + +class ObjectTracker { + private: + static std::vector trackedObjects; + + public: + static void track(TrackedObject* obj) { trackedObjects.push_back(obj); } + + static void releaseAll(); +}; + +class TrackedObject { + public: + TrackedObject() { ObjectTracker::track(this); } + virtual ~TrackedObject() = default; +}; diff --git a/benchmarks/C++/src/nbody.h b/benchmarks/C++/src/nbody.h new file mode 100644 index 00000000..5d26c863 --- /dev/null +++ b/benchmarks/C++/src/nbody.h @@ -0,0 +1,203 @@ +#pragma once + +#include + +#include "benchmark.h" +#include "som/error.h" + +using std::cout; + +class Body { + private: + constexpr static double PI = 3.141592653589793; + constexpr static double SOLAR_MASS = 4 * PI * PI; + constexpr static double DAYS_PER_YER = 365.24; + + double _x; + double _y; + double _z; + double _vx; + double _vy; + double _vz; + double _mass; + + public: + [[nodiscard]] double getX() const { return _x; } + [[nodiscard]] double getY() const { return _y; } + [[nodiscard]] double getZ() const { return _z; } + + [[nodiscard]] double getVX() const { return _vx; } + [[nodiscard]] double getVY() const { return _vy; } + [[nodiscard]] double getVZ() const { return _vz; } + + [[nodiscard]] double getMass() const { return _mass; } + + void setX(double x) { _x = x; } + void setY(double y) { _y = y; } + void setZ(double z) { _z = z; } + + void setVX(double vx) { _vx = vx; } + void setVY(double vy) { _vy = vy; } + void setVZ(double vz) { _vz = vz; } + + void offsetMomentum(double px, double py, double pz) { + _vx = 0.0 - (px / SOLAR_MASS); + _vy = 0.0 - (py / SOLAR_MASS); + _vz = 0.0 - (pz / SOLAR_MASS); + } + + Body(Body& other) noexcept = default; + + Body(double x, + double y, + double z, + double vx, + double vy, + double vz, + double mass) + : _x(x), + _y(y), + _z(z), + _vx(vx * DAYS_PER_YER), + _vy(vy * DAYS_PER_YER), + _vz(vz * DAYS_PER_YER), + _mass(mass * SOLAR_MASS) {} + + static Body jupiter() { + return {4.84143144246472090e+00, -1.16032004402742839e+00, + -1.03622044471123109e-01, 1.66007664274403694e-03, + 7.69901118419740425e-03, -6.90460016972063023e-05, + 9.54791938424326609e-04}; + } + static Body saturn() { + return {8.34336671824457987e+00, 4.12479856412430479e+00, + -4.03523417114321381e-01, -2.76742510726862411e-03, + 4.99852801234917238e-03, 2.30417297573763929e-05, + 2.85885980666130812e-04}; + } + static Body uranus() { + return {1.28943695621391310e+01, -1.51111514016986312e+01, + -2.23307578892655734e-01, 2.96460137564761618e-03, + 2.37847173959480950e-03, -2.96589568540237556e-05, + 4.36624404335156298e-05}; + } + + static Body neptune() { + return {1.53796971148509165e+01, -2.59193146099879641e+01, + 1.79258772950371181e-01, 2.68067772490389322e-03, + 1.62824170038242295e-03, -9.51592254519715870e-05, + 5.15138902046611451e-05}; + } + static Body sun() { return {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0}; } +}; + +class NBodySystem { + private: + int32_t _bodiesSize{5}; + std::array _bodies; + + public: + NBodySystem() : _bodies(createBodies()) {} + + [[nodiscard]] std::array createBodies() const { + std::array bodies = {Body::sun(), Body::jupiter(), Body::saturn(), + Body::uranus(), Body::neptune()}; + + double px = 0.0; + double py = 0.0; + double pz = 0.0; + + for (int32_t i = 0; i < _bodiesSize; i += 1) { + px += bodies.at(i).getVX() * bodies.at(i).getMass(); + py += bodies.at(i).getVY() * bodies.at(i).getMass(); + pz += bodies.at(i).getVZ() * bodies.at(i).getMass(); + } + + bodies.at(0).offsetMomentum(px, py, pz); + + return bodies; + } + void advance(double dt) { + for (int32_t i = 0; i < _bodiesSize; i += 1) { + Body& iBody = _bodies.at(i); + + for (int32_t j = i + 1; j < _bodiesSize; j += 1) { + Body& jBody = _bodies.at(j); + const double dx = iBody.getX() - jBody.getX(); + const double dy = iBody.getY() - jBody.getY(); + const double dz = iBody.getZ() - jBody.getZ(); + + const double dSquared = dx * dx + dy * dy + dz * dz; + const double distance = sqrt(dSquared); + const double mag = dt / (dSquared * distance); + + iBody.setVX(iBody.getVX() - (dx * jBody.getMass() * mag)); + iBody.setVY(iBody.getVY() - (dy * jBody.getMass() * mag)); + iBody.setVZ(iBody.getVZ() - (dz * jBody.getMass() * mag)); + + jBody.setVX(jBody.getVX() + (dx * iBody.getMass() * mag)); + jBody.setVY(jBody.getVY() + (dy * iBody.getMass() * mag)); + jBody.setVZ(jBody.getVZ() + (dz * iBody.getMass() * mag)); + } + } + + for (int32_t i = 0; i < _bodiesSize; i += 1) { + _bodies.at(i).setX(_bodies.at(i).getX() + dt * _bodies.at(i).getVX()); + _bodies.at(i).setY(_bodies.at(i).getY() + dt * _bodies.at(i).getVY()); + _bodies.at(i).setZ(_bodies.at(i).getZ() + dt * _bodies.at(i).getVZ()); + } + } + + double energy() { + double e = 0.0; + + for (int32_t i = 0; i < _bodiesSize; i += 1) { + const Body& iBody = _bodies.at(i); + e += 0.5 * iBody.getMass() * + (iBody.getVX() * iBody.getVX() + iBody.getVY() * iBody.getVY() + + iBody.getVZ() * iBody.getVZ()); + + for (int32_t j = i + 1; j < _bodiesSize; j += 1) { + const Body& jBody = _bodies.at(j); + const double dx = iBody.getX() - jBody.getX(); + const double dy = iBody.getY() - jBody.getY(); + const double dz = iBody.getZ() - jBody.getZ(); + + const double distance = sqrt(dx * dx + dy * dy + dz * dz); + e -= (iBody.getMass() * jBody.getMass()) / distance; + } + } + return e; + } +}; + +class NBody : public Benchmark { + private: + bool verify_result(double result, int32_t innerIterations) { + if (innerIterations == 250000) { + return result == -0.1690859889909308; + } + if (innerIterations == 1) { + return result == -0.16907495402506745; + } + + cout << "No verification result for " << innerIterations << " found\n"; + cout << "Result is: " << result << "\n"; + return false; + } + + public: + bool inner_benchmark_loop(int32_t innerIterations) override { + NBodySystem system{}; + for (int32_t i = 0; i < innerIterations; i += 1) { + system.advance(0.01); + } + + return verify_result(system.energy(), innerIterations); + } + + std::any benchmark() override { throw Error("Should never be reached"); } + bool verify_result(std::any) override { + throw Error("Should never be reached"); + } +}; diff --git a/benchmarks/C++/src/permute.h b/benchmarks/C++/src/permute.h new file mode 100644 index 00000000..ae046a73 --- /dev/null +++ b/benchmarks/C++/src/permute.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include "benchmark.h" + +class Permute : public Benchmark { + private: + int32_t count{0}; + int32_t* v{nullptr}; + + void permute(int32_t n) { + count += 1; + if (n != 0) { + const int32_t n1 = n - 1; + permute(n1); + for (int32_t i = n1; i >= 0; i -= 1) { + swap(n1, i); + permute(n1); + swap(n1, i); + } + } + } + + void swap(int32_t i, int32_t j) noexcept { + const int32_t tmp = v[i]; + v[i] = v[j]; + v[j] = tmp; + } + + public: + std::any benchmark() override { + count = 0; + v = new int32_t[6]; + permute(6); + delete[] v; + return count; + } + + bool verify_result(std::any result) override { + return 8660 == std::any_cast(result); + } +}; diff --git a/benchmarks/C++/src/queens.h b/benchmarks/C++/src/queens.h new file mode 100644 index 00000000..2bd983d5 --- /dev/null +++ b/benchmarks/C++/src/queens.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include + +#include "benchmark.h" + +class Queens : public Benchmark { + private: + bool* free_maxs{nullptr}; + bool* free_rows{nullptr}; + bool* free_mins{nullptr}; + int32_t* queen_rows{nullptr}; + + bool queens() { + free_rows = new bool[8]; + std::fill_n(free_rows, 8, true); + free_maxs = new bool[16]; + std::fill_n(free_maxs, 16, true); + free_mins = new bool[16]; + std::fill_n(free_mins, 16, true); + queen_rows = new int32_t[8]; + std::fill_n(queen_rows, 8, -1); + + const bool result = place_queen(0); + + delete[] free_rows; + delete[] free_maxs; + delete[] free_mins; + delete[] queen_rows; + + return result; + } + + bool place_queen(int32_t c) { + for (int32_t r = 0; r < 8; r += 1) { + if (get_row_column(r, c)) { + queen_rows[r] = c; + set_row_column(r, c, false); + + if (c == 7) { + return true; + } + + if (place_queen(c + 1)) { + return true; + } + set_row_column(r, c, true); + } + } + return false; + } + + bool get_row_column(int32_t r, int32_t c) { + return free_rows[r] && free_maxs[c + r] && free_mins[c - r + 7]; + } + + void set_row_column(int32_t r, int32_t c, bool v) { + free_rows[r] = v; + free_maxs[c + r] = v; + free_mins[c - r + 7] = v; + } + + public: + std::any benchmark() override { + bool result = true; + for (int32_t i = 0; i < 10; i += 1) { + result = result && queens(); + } + return result; + } + + bool verify_result(std::any result) override { + return std::any_cast(result); + } +}; diff --git a/benchmarks/C++/src/richards.cpp b/benchmarks/C++/src/richards.cpp new file mode 100644 index 00000000..eae1a735 --- /dev/null +++ b/benchmarks/C++/src/richards.cpp @@ -0,0 +1,16 @@ +#include "richards.h" + +Packet* RBObject::append(Packet* packet, Packet* queueHead) { + packet->setLink(NO_WORK); + if (NO_WORK == queueHead) { + return packet; + } + + Packet* mouse = queueHead; + Packet* link = NO_WORK; + while (NO_WORK != (link = mouse->getLink())) { + mouse = link; + } + mouse->setLink(packet); + return queueHead; +} diff --git a/benchmarks/C++/src/richards.h b/benchmarks/C++/src/richards.h new file mode 100644 index 00000000..8d09591e --- /dev/null +++ b/benchmarks/C++/src/richards.h @@ -0,0 +1,512 @@ +#pragma once + +#include "benchmark.h" +#include "memory/object_tracker.h" +#include "som/error.h" + +#include +#include +#include +#include +#include + +using std::cout; + +class Packet; +class TaskControlBlock; + +class RBObject : public TrackedObject { + public: + Packet* append(Packet* packet, Packet* queueHead); + + static const int32_t IDLER = 0; + static const int32_t WORKER = 1; + static const int32_t HANDLER_A = 2; + static const int32_t HANDLER_B = 3; + static const int32_t DEVICE_A = 4; + static const int32_t DEVICE_B = 5; + static const int32_t NUM_TYPES = 6; + + static const int32_t DEVICE_PACKET_KIND = 0; + static const int32_t WORK_PACKET_KIND = 1; + + static constexpr Packet* const NO_WORK = nullptr; + static constexpr TaskControlBlock* const NO_TASK = nullptr; +}; + +class TaskState : public RBObject { + private: + bool _packetPending{false}; + bool _taskWaiting{false}; + bool _taskHolding{false}; + + public: + [[nodiscard]] bool isPacketPending() const { return _packetPending; } + [[nodiscard]] bool isTaskHolding() const { return _taskHolding; } + [[nodiscard]] bool isTaskWaiting() const { return _taskWaiting; } + void setTaskHolding(bool b) { _taskHolding = b; } + void setTaskWaiting(bool b) { _taskWaiting = b; } + void setPacketPending(bool b) { _packetPending = b; } + + void packetPending() { + _packetPending = true; + _taskWaiting = false; + _taskHolding = false; + } + + void running() { _packetPending = _taskWaiting = _taskHolding = false; } + + void waiting() { + _packetPending = _taskHolding = false; + _taskWaiting = true; + } + + void waitingWithPacket() { + _taskHolding = false; + _taskWaiting = _packetPending = true; + } + + [[nodiscard]] bool isTaskHoldingOrWaiting() const { + return _taskHolding || (!_packetPending && _taskWaiting); + } + + [[nodiscard]] bool isWaitingWithPacket() const { + return _packetPending && _taskWaiting && !_taskHolding; + } + + [[nodiscard]] static TaskState* createRunning() { + auto* t = new TaskState(); + t->running(); + return t; + } + + [[nodiscard]] static TaskState* createWaiting() { + auto* t = new TaskState(); + t->waiting(); + return t; + } + + [[nodiscard]] static TaskState* createWaitingWithPacket() { + auto* t = new TaskState(); + t->waitingWithPacket(); + return t; + } +}; + +class DeviceTaskDataRecord : public RBObject { + private: + Packet* _pending{NO_WORK}; + + public: + DeviceTaskDataRecord() = default; + + [[nodiscard]] Packet* getPending() const { return _pending; } + void setPending(Packet* packet) { _pending = packet; } +}; + +class HandlerTaskDataRecord : public RBObject { + private: + Packet* _workIn{NO_WORK}; + Packet* _deviceIn{NO_WORK}; + + public: + HandlerTaskDataRecord() = default; + + Packet* deviceIn() { return _deviceIn; } + void deviceIn(Packet* aPacket) { _deviceIn = aPacket; } + void deviceInAdd(Packet* packet) { _deviceIn = append(packet, _deviceIn); } + + Packet* workIn() { return _workIn; } + void workIn(Packet* aWorkQueue) { _workIn = aWorkQueue; } + void workInAdd(Packet* packet) { _workIn = append(packet, _workIn); } +}; + +class IdleTaskDataRecord : public RBObject { + private: + int32_t _control{1}; + int32_t _count{10000}; + + public: + IdleTaskDataRecord() = default; + + [[nodiscard]] int32_t getControl() const { return _control; } + void setControl(int32_t aNumber) { _control = aNumber; } + [[nodiscard]] int32_t getCount() const { return _count; } + void setCount(int32_t aCount) { _count = aCount; } +}; + +class Packet : public RBObject { + public: + static const int32_t DATA_SIZE = 4; + + private: + Packet* _link; + int32_t _identity; + int32_t _kind; + int32_t _datum{0}; + std::array _data{0}; + + public: + Packet(Packet* link, int32_t identity, int32_t kind) + : _link(link), _identity(identity), _kind(kind) {} + + [[nodiscard]] std::array* getData() { + return &_data; + } + + [[nodiscard]] int32_t getDatum() const { return _datum; } + void setDatum(int32_t someData) { _datum = someData; } + [[nodiscard]] int32_t getIdentity() const { return _identity; } + void setIdentity(int32_t anIdentity) { _identity = anIdentity; } + [[nodiscard]] int32_t getKind() const { return _kind; } + [[nodiscard]] Packet* getLink() { return _link; } + void setLink(Packet* aLink) { _link = aLink; } + + [[nodiscard]] std::string toString() const { + return "Packet id: " + std::to_string(_identity) + + " kind: " + std::to_string(_kind); + } +}; + +class TaskControlBlock : public TaskState { + private: + TaskControlBlock* _link; + int32_t _identity; + int32_t _priority; + Packet* _input; + std::function _function; + RBObject* _handle; + + public: + TaskControlBlock( + TaskControlBlock* aLink, + int32_t anIdentity, + int32_t aPriority, + Packet* anInitialWorkQueue, + TaskState* anInitialState, + std::function aBlock, + RBObject* aPrivateData) + : _link(aLink), + _identity(anIdentity), + _priority(aPriority), + _input(anInitialWorkQueue), + _function(std::move(aBlock)), + _handle(aPrivateData) { + setPacketPending(anInitialState->isPacketPending()); + setTaskWaiting(anInitialState->isTaskWaiting()); + setTaskHolding(anInitialState->isTaskHolding()); + } + + [[nodiscard]] int32_t getIdentity() const { return _identity; } + [[nodiscard]] TaskControlBlock* getLink() { return _link; } + [[nodiscard]] int32_t getPriority() const { return _priority; } + + [[nodiscard]] TaskControlBlock* addInputAndCheckPriority( + Packet* packet, + TaskControlBlock* oldTask) { + if (NO_WORK == _input) { + _input = packet; + setPacketPending(true); + if (_priority > oldTask->getPriority()) { + return this; + } + } else { + _input = append(packet, _input); + } + return oldTask; + } + + [[nodiscard]] TaskControlBlock* runTask() { + Packet* message = NO_WORK; + + if (isWaitingWithPacket()) { + message = _input; + _input = message->getLink(); + if (NO_WORK == _input) { + running(); + } else { + packetPending(); + } + } else { + message = NO_WORK; + } + return _function(message, _handle); + } +}; + +class WorkerTaskDataRecord : public RBObject { + private: + int32_t _destination{RBObject::HANDLER_A}; + int32_t _count{0}; + + public: + WorkerTaskDataRecord() = default; + + [[nodiscard]] int32_t getCount() const { return _count; } + void setCount(int32_t aCount) { _count = aCount; } + [[nodiscard]] int32_t getDestination() const { return _destination; } + void setDestination(int32_t aHandler) { _destination = aHandler; } +}; + +class Scheduler : public RBObject { + private: + TaskControlBlock* _taskList{NO_TASK}; + TaskControlBlock* _currentTask{NO_TASK}; + int32_t _currentTaskIdentity{0}; + std::array _taskTable{NO_TASK}; + int32_t _queuePacketCount{0}; + int32_t _holdCount{0}; + int32_t _layout{0}; + static const bool TRACING = false; + + public: + Scheduler() = default; + + void createDevice(int32_t identity, + int32_t priority, + Packet* workPacket, + TaskState* state) { + auto* data = new DeviceTaskDataRecord(); + + createTask( + identity, priority, workPacket, state, + [this](Packet* workArg, RBObject* wordArg) -> TaskControlBlock* { + auto* dataRecord = dynamic_cast(wordArg); + Packet* functionWork = workArg; + if (RBObject::NO_WORK == functionWork) { + functionWork = dataRecord->getPending(); + if (RBObject::NO_WORK == functionWork) { + return markWaiting(); + } + dataRecord->setPending(RBObject::NO_WORK); + return queuePacket(functionWork); + } + + dataRecord->setPending(functionWork); + if (TRACING) { + trace(functionWork->getDatum()); + } + return holdSelf(); + }, + data); + } + + void createHandler(int32_t identity, + int32_t priority, + Packet* workPaket, + TaskState* state) { + auto* data = new HandlerTaskDataRecord(); + + createTask( + identity, priority, workPaket, state, + [this](Packet* work, RBObject* word) -> TaskControlBlock* { + auto* dataRecord = dynamic_cast(word); + if (RBObject::NO_WORK != work) { + if (WORK_PACKET_KIND == work->getKind()) { + dataRecord->workInAdd(work); + } else { + dataRecord->deviceInAdd(work); + } + } + + Packet* workPacket = dataRecord->workIn(); + if (RBObject::NO_WORK == workPacket) { + return markWaiting(); + } + const int32_t count = workPacket->getDatum(); + if (count >= Packet::DATA_SIZE) { + dataRecord->workIn(workPacket->getLink()); + return queuePacket(workPacket); + } + Packet* devicePacket = dataRecord->deviceIn(); + if (RBObject::NO_WORK == devicePacket) { + return markWaiting(); + } + dataRecord->deviceIn(devicePacket->getLink()); + devicePacket->setDatum(workPacket->getData()->at(count)); + workPacket->setDatum(count + 1); + return queuePacket(devicePacket); + }, + data); + } + + void createIdler(int32_t identity, + int32_t priority, + Packet* work, + TaskState* state) { + auto* data = new IdleTaskDataRecord(); + + createTask( + identity, priority, work, state, + [this](Packet*, RBObject* wordArg) -> TaskControlBlock* { + auto* dataRecord = dynamic_cast(wordArg); + dataRecord->setCount(dataRecord->getCount() - 1); + if (0 == dataRecord->getCount()) { + return holdSelf(); + } + if (0 == (dataRecord->getControl() & 1)) { + dataRecord->setControl(dataRecord->getControl() / 2); + return release(DEVICE_A); + } + dataRecord->setControl((dataRecord->getControl() / 2) ^ 53256); + return release(DEVICE_B); + }, + data); + } + + Packet* createPacket(Packet* link, int32_t identity, int32_t kind) { + return new Packet(link, identity, kind); + } + + void createTask( + int32_t identity, + int32_t priority, + Packet* work, + TaskState* state, + std::function aBlock, + RBObject* data) { + auto* t = new TaskControlBlock(_taskList, identity, priority, work, state, + std::move(aBlock), data); + _taskList = t; + _taskTable.at(identity) = t; + } + + void createWorker(int32_t identity, + int32_t priority, + Packet* workPaket, + TaskState* state) { + auto* dataRecord = new WorkerTaskDataRecord(); + + createTask( + identity, priority, workPaket, state, + [this](Packet* work, RBObject* word) -> TaskControlBlock* { + auto* data = dynamic_cast(word); + if (RBObject::NO_WORK == work) { + return markWaiting(); + } + data->setDestination( + (HANDLER_A == data->getDestination()) ? HANDLER_B : HANDLER_A); + work->setIdentity(data->getDestination()); + work->setDatum(0); + for (int32_t i = 0; i < Packet::DATA_SIZE; i += 1) { + data->setCount(data->getCount() + 1); + if (data->getCount() > 26) { + data->setCount(1); + } + work->getData()->at(i) = 65 + data->getCount() - 1; + } + return queuePacket(work); + }, + dataRecord); + } + + bool start() { + Packet* workQ = RBObject::NO_WORK; + createIdler(IDLER, 0, RBObject::NO_WORK, TaskState::createRunning()); + + workQ = createPacket(RBObject::NO_WORK, WORKER, WORK_PACKET_KIND); + workQ = createPacket(workQ, WORKER, WORK_PACKET_KIND); + createWorker(WORKER, 1000, workQ, TaskState::createWaitingWithPacket()); + + workQ = createPacket(RBObject::NO_WORK, DEVICE_A, DEVICE_PACKET_KIND); + workQ = createPacket(workQ, DEVICE_A, DEVICE_PACKET_KIND); + workQ = createPacket(workQ, DEVICE_A, DEVICE_PACKET_KIND); + createHandler(HANDLER_A, 2000, workQ, TaskState::createWaitingWithPacket()); + + workQ = createPacket(RBObject::NO_WORK, DEVICE_B, DEVICE_PACKET_KIND); + workQ = createPacket(workQ, DEVICE_B, DEVICE_PACKET_KIND); + workQ = createPacket(workQ, DEVICE_B, DEVICE_PACKET_KIND); + createHandler(HANDLER_B, 3000, workQ, TaskState::createWaitingWithPacket()); + + createDevice(DEVICE_A, 4000, RBObject::NO_WORK, TaskState::createWaiting()); + createDevice(DEVICE_B, 5000, RBObject::NO_WORK, TaskState::createWaiting()); + + schedule(); + + return _queuePacketCount == 23246 && _holdCount == 9297; + } + + TaskControlBlock* findTask(int32_t identity) { + TaskControlBlock* t = _taskTable.at(identity); + if (RBObject::NO_TASK == t) { + throw Error("findTask failed"); + } + return t; + } + + TaskControlBlock* holdSelf() { + _holdCount = _holdCount + 1; + _currentTask->setTaskHolding(true); + return _currentTask->getLink(); + } + + TaskControlBlock* queuePacket(Packet* packet) { + TaskControlBlock* t = findTask(packet->getIdentity()); + if (RBObject::NO_TASK == t) { + return RBObject::NO_TASK; + } + + _queuePacketCount = _queuePacketCount + 1; + + packet->setLink(RBObject::NO_WORK); + packet->setIdentity(_currentTaskIdentity); + return t->addInputAndCheckPriority(packet, _currentTask); + } + + TaskControlBlock* release(int32_t identity) { + TaskControlBlock* t = findTask(identity); + if (RBObject::NO_TASK == t) { + return RBObject::NO_TASK; + } + t->setTaskHolding(false); + if (t->getPriority() > _currentTask->getPriority()) { + return t; + } + return _currentTask; + } + + void trace(int32_t id) { + _layout = _layout - 1; + if (0 >= _layout) { + cout << "\n"; + _layout = 50; + } + cout << id << "\n"; + } + + TaskControlBlock* markWaiting() { + _currentTask->setTaskWaiting(true); + return _currentTask; + } + + void schedule() { + _currentTask = _taskList; + while (RBObject::NO_TASK != _currentTask) { + if (_currentTask->isTaskHoldingOrWaiting()) { + _currentTask = _currentTask->getLink(); + } else { + _currentTaskIdentity = _currentTask->getIdentity(); + if (TRACING) { + trace(_currentTaskIdentity); + } + _currentTask = _currentTask->runTask(); + } + } + } +}; + +class Richards : public Benchmark { + public: + bool verify_result(std::any result) override { + const bool result_cast = std::any_cast(result); + return result_cast; + } + + std::any benchmark() override { + auto* scheduler = new Scheduler(); + const bool result = scheduler->start(); + + ObjectTracker::releaseAll(); + return result; + } +}; diff --git a/benchmarks/C++/src/run.h b/benchmarks/C++/src/run.h new file mode 100644 index 00000000..1f6e1157 --- /dev/null +++ b/benchmarks/C++/src/run.h @@ -0,0 +1,136 @@ +#pragma once + +#include +#include + +#include "bounce.h" +#include "cd.h" +#include "deltablue.h" +#include "havlak.h" +#include "json.h" +#include "list.h" +#include "mandelbrot.h" +#include "nbody.h" +#include "permute.h" +#include "queens.h" +#include "richards.h" +#include "sieve.h" +#include "storage.h" +#include "towers.h" + +using benchmark_suite_ctr = Benchmark* (*)(); + +class Run { + private: + const std::string name; + benchmark_suite_ctr suite; + int32_t num_iterations{1}; + int32_t inner_iterations{1}; + int64_t total{0}; + + public: + explicit Run(std::string& name) : name(name), suite(select_benchmark(name)) {} + + void run_benchmark() { + std::cout << "Starting " << name << " benchmark ...\n"; + Benchmark* benchmark = suite(); + do_runs(benchmark); + delete benchmark; + + report_benchmark(); + std::cout << "\n"; + } + + void print_total() const { + std::cout << "Total Runtime: " << total << "us\n"; + } + + void set_num_iterations(int32_t num_iterations) { + this->num_iterations = num_iterations; + } + + void set_inner_iterations(int32_t inner_iterations) { + this->inner_iterations = inner_iterations; + } + + private: + benchmark_suite_ctr select_benchmark(std::string& name) { + if (name == "Bounce") { + return []() -> Benchmark* { return new Bounce(); }; + } + if (name == "CD") { + return []() -> Benchmark* { return new CD(); }; + } + if (name == "Havlak") { + return []() -> Benchmark* { return new Havlak(); }; + } + if (name == "Json") { + return []() -> Benchmark* { return new Json(); }; + } + if (name == "List") { + return []() -> Benchmark* { return new List(); }; + } + if (name == "Mandelbrot") { + return []() -> Benchmark* { return new Mandelbrot(); }; + } + if (name == "NBody") { + return []() -> Benchmark* { return new NBody(); }; + } + if (name == "Permute") { + return []() -> Benchmark* { return new Permute(); }; + } + if (name == "Queens") { + return []() -> Benchmark* { return new Queens(); }; + } + if (name == "Richards") { + return []() -> Benchmark* { return new Richards(); }; + } + if (name == "Sieve") { + return []() -> Benchmark* { return new Sieve(); }; + } + if (name == "Storage") { + return []() -> Benchmark* { return new Storage(); }; + } + if (name == "Towers") { + return []() -> Benchmark* { return new Towers(); }; + } + if (name == "DeltaBlue") { + return []() -> Benchmark* { return new DeltaBlue(); }; + } + + std::cerr << "Benchmark not recognized: " << name << "\n"; + exit(1); + } + + void measure(Benchmark* const bench) { + auto start_time = std::chrono::high_resolution_clock::now(); + if (!bench->inner_benchmark_loop(inner_iterations)) { + std::cout << "Benchmark failed with incorrect result\n"; + exit(1); + } + auto end_time = std::chrono::high_resolution_clock::now(); + const int64_t run_time = + std::chrono::duration_cast(end_time - + start_time) + .count(); + + print_result(run_time); + total += run_time; + } + + void do_runs(Benchmark* const bench) { + for (int32_t i = 0; i < num_iterations; i += 1) { + measure(bench); + } + } + + void report_benchmark() { + std::cout << name << ": iterations=" << num_iterations + << " average: " << (total / num_iterations) + << "us total: " << total << "us\n"; + } + + void print_result(int64_t run_time) { + std::cout << name << ": iterations=1 runtime: " << run_time << "us\n"; + } +}; diff --git a/benchmarks/C++/src/sieve.h b/benchmarks/C++/src/sieve.h new file mode 100644 index 00000000..935a10e2 --- /dev/null +++ b/benchmarks/C++/src/sieve.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "benchmark.h" + +class Sieve : public Benchmark { + public: + std::any benchmark() override { + const int32_t num_flags = 5000; + std::array flags{}; + + std::fill_n(flags.begin(), num_flags, true); + + return sieve(flags, 5000); + } + + bool verify_result(std::any result) override { + return 669 == std::any_cast(result); + } + + private: + template + int32_t sieve(std::array flags, int32_t size) { + int32_t prime_count = 0; + + for (int32_t i = 2; i <= size; i += 1) { + if (flags[i - 1]) { + prime_count += 1; + int k = i + i; + while (k <= size) { + flags[k - 1] = false; + k += i; + } + } + } + return prime_count; + } +}; diff --git a/benchmarks/C++/src/som/dictionary.h b/benchmarks/C++/src/som/dictionary.h new file mode 100644 index 00000000..b2c0cd56 --- /dev/null +++ b/benchmarks/C++/src/som/dictionary.h @@ -0,0 +1,258 @@ +#pragma once + +#include +#include "vector.h" + +template +class IdentityDictionary; + +/** + * The Dictionary class does not manage the memory of keys and values. + * + * The memory of Entry objects is managed by ownership through the _buckets + * field. Thus, they are anchored in the _buckets, and only freed from there. + */ +template +class Dictionary { + friend class IdentityDictionary; + + private: + class Entry { + friend class IdentityDictionary; + friend class Dictionary; + + private: + uint32_t _hash; + const K* const _key; + V _value; + Entry* _next; + + public: + Entry(uint32_t h, const K* k, const V& v, Entry* n) + : _hash(h), _key(k), _value(v), _next(n) {} + virtual ~Entry() = default; + + virtual bool match(uint32_t h, const K* const k) { + return _hash == h && _key == k; + } + + [[nodiscard]] const K* getKey() const { return _key; } + + [[nodiscard]] uint32_t getHash() const { return _hash; } + }; + + static const uint32_t INITIAL_CAPACITY = 16; + + Entry** _buckets; + uint32_t _size{0}; + uint32_t _capacity; + + public: + explicit Dictionary(uint32_t capacity = INITIAL_CAPACITY) + : _buckets(new Entry* [capacity] {}), _capacity(capacity) {} + + virtual ~Dictionary() { + removeAll(); + delete[] _buckets; + } + + [[nodiscard]] uint32_t getSize() const { return _size; } + + [[nodiscard]] bool isEmpty() const { return _size == 0; } + + [[nodiscard]] uint32_t hash(const K* key) const { + if (key == nullptr) { + return 0; + } + const uint32_t h = key->customHash(); + return h ^ (h >> 16U); + } + + [[nodiscard]] bool containsKey(const K* key) const { + uint32_t h = hash(key); + Entry* e = getBucket(h); + + while (e != nullptr) { + if (e->match(h, key)) { + return true; + } + e = e->_next; + } + return false; + } + + V* at(const K* key) const { + const uint32_t h = this->hash(key); + Entry* e = getBucket(h); + + while (e != nullptr) { + if (e->match(h, key)) { + return &e->_value; + } + e = e->_next; + } + // Return a default-constructed V if the key is not found + return nullptr; + } + + void atPut(const K* const key, const V& value) { + const uint32_t h = hash(key); + const uint32_t i = getBucketIdx(h); + + Entry* current = _buckets[i]; + + if (current == nullptr) { + _buckets[i] = newEntry(key, value, h); + _size += 1; + } else { + insertBucketEntry(key, value, h, current); + } + + if (_size > _capacity) { + resize(); + } + } + + void removeAll() { + for (uint32_t i = 0; i < _capacity; i += 1) { + Entry* current = _buckets[i]; + while (current != nullptr) { + Entry* toBeDeleted = current; + current = current->_next; + delete toBeDeleted; + } + + _buckets[i] = nullptr; // Reset each element to a default value + } + _size = 0; + } + + [[nodiscard]] Vector* getKeys() { + auto* keys = new Vector(); + for (uint32_t i = 0; i < _capacity; i += 1) { + Entry* current = _buckets[i]; + while (current != nullptr) { + keys->append(current->_key); + current = current->_next; + } + } + return keys; + } + + [[nodiscard]] Vector* getValues() { + auto* values = new Vector(_size); + + for (uint32_t i = 0; i < _capacity; i += 1) { + Entry* current = _buckets[i]; + while (current != nullptr) { + values->append(current->_value); + current = current->_next; + } + } + return values; + } + + void destroyValues() { + for (uint32_t i = 0; i < _capacity; i += 1) { + Entry* current = _buckets[i]; + while (current != nullptr) { + delete current->_value; + current = current->_next; + } + } + } + + virtual Entry* newEntry(const K* key, V value, uint32_t hash) { + return new Entry(hash, key, value, nullptr); + } + + private: + [[nodiscard]] uint32_t getBucketIdx(uint32_t hash) const { + return (_capacity - 1) & hash; + } + + [[nodiscard]] Entry* getBucket(uint32_t hash) const { + return _buckets[getBucketIdx(hash)]; + } + + void insertBucketEntry(const K* key, + const V& value, + uint32_t hash, + Entry* head) { + Entry* current = head; + + while (true) { + if (current->match(hash, key)) { + current->_value = value; + return; + } + if (current->_next == nullptr) { + _size += 1; + current->_next = newEntry(key, value, hash); + return; + } + current = current->_next; + } + } + + void resize() { + Entry** oldStorage = _buckets; + const uint32_t oldCapacity = _capacity; + _capacity *= 2; + auto* newStorage = new Entry* [_capacity] {}; + _buckets = newStorage; + transferEntries(oldStorage, oldCapacity); + delete[] oldStorage; + } + + void transferEntries(Entry** oldStorage, uint32_t oldCapacity) { + for (uint32_t i = 0; i < oldCapacity; i += 1) { + Entry* current = oldStorage[i]; + if (current != nullptr) { + oldStorage[i] = nullptr; + if (current->_next == nullptr) { + _buckets[current->_hash & (oldCapacity - 1)] = current; + } else { + splitBucket(oldCapacity, i, current); + } + } + } + } + + void splitBucket(uint32_t oldCapacity, uint32_t idx, Entry* head) { + Entry* loHead = nullptr; + Entry* loTail = nullptr; + Entry* hiHead = nullptr; + Entry* hiTail = nullptr; + + Entry* current = head; + + while (current != nullptr) { + if ((current->_hash & oldCapacity) == 0) { + if (loTail == nullptr) { + loHead = current; + } else { + loTail->_next = current; + } + loTail = current; + } else { + if (hiTail == nullptr) { + hiHead = current; + } else { + hiTail->_next = current; + } + hiTail = current; + } + current = current->_next; + } + + if (loTail != nullptr) { + loTail->_next = nullptr; + _buckets[idx] = loHead; + } + if (hiTail != nullptr) { + hiTail->_next = nullptr; + _buckets[idx + oldCapacity] = hiHead; + } + } +}; diff --git a/benchmarks/C++/src/som/error.h b/benchmarks/C++/src/som/error.h new file mode 100644 index 00000000..52165bf0 --- /dev/null +++ b/benchmarks/C++/src/som/error.h @@ -0,0 +1,18 @@ + +#pragma once + +#include + +class Error : virtual public std::exception { + private: + std::string _what; + + public: + explicit Error(std::string const& what) : _what(what) {} // NOLINT + + explicit Error(const char* what) : _what(std::string(what)) {} + + [[nodiscard]] const char* what() const noexcept override { + return (_what.c_str()); + } +}; diff --git a/benchmarks/C++/src/som/identity_dictionary.h b/benchmarks/C++/src/som/identity_dictionary.h new file mode 100644 index 00000000..9dc17425 --- /dev/null +++ b/benchmarks/C++/src/som/identity_dictionary.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include "dictionary.h" + +template +class IdentityDictionary : public Dictionary { + private: + class IdEntry : public Dictionary::Entry { + public: + IdEntry(uint32_t hash, + const K* key, + const V& value, + typename Dictionary::Entry* next) + : Dictionary::Entry(hash, key, value, next) {} + ~IdEntry() override = default; + + bool match(uint32_t h, const K* k) override { + return this->getHash() == h && this->getKey() == k; + } + }; + + public: + explicit IdentityDictionary(const uint32_t size) : Dictionary(size) {} + IdentityDictionary() : Dictionary(Dictionary::INITIAL_CAPACITY) {} + + typename Dictionary::Entry* newEntry(const K* key, + V value, + uint32_t hash) override { + return new IdEntry(hash, key, value, nullptr); + } +}; diff --git a/benchmarks/C++/src/som/identity_set.h b/benchmarks/C++/src/som/identity_set.h new file mode 100644 index 00000000..99a2f853 --- /dev/null +++ b/benchmarks/C++/src/som/identity_set.h @@ -0,0 +1,16 @@ +#pragma once + +#include "set.h" + +#include + +template +class IdentitySet : public Set { + public: + explicit IdentitySet() = default; + explicit IdentitySet(size_t size) : Set(size) {} + + bool contains(E& obj) { + return hasSome([&obj](const E& e) { return e == obj; }); + } +}; diff --git a/benchmarks/C++/src/som/random.h b/benchmarks/C++/src/som/random.h new file mode 100644 index 00000000..86be6cdc --- /dev/null +++ b/benchmarks/C++/src/som/random.h @@ -0,0 +1,14 @@ +#pragma once + +class Random { + private: + int32_t seed{74755}; + + public: + Random() = default; + + int32_t next() { + seed = ((seed * 1309) + 13849) & 65535; + return seed; + } +}; diff --git a/benchmarks/C++/src/som/set.h b/benchmarks/C++/src/som/set.h new file mode 100644 index 00000000..c17b06be --- /dev/null +++ b/benchmarks/C++/src/som/set.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include "vector.h" + +template +class Set { + private: + Vector items{INITIAL_SIZE}; + + public: + explicit Set() = default; + explicit Set(size_t size) : items(size) {} + + [[nodiscard]] size_t size() const { return items.size(); } + + void forEach(const std::function& fn) const { + items.forEach(fn); + } + + [[nodiscard]] bool hasSome(const std::function& fn) const { + return items.hasSome(fn); + } + + [[nodiscard]] E getOne(const std::function& fn) const { + return items.getOne(fn); + } + + void add(E obj) { + if (!contains(obj)) { + items.append(obj); + } + } + + template + [[nodiscard]] Vector* collect(const std::function& fn) const { + auto* coll = new Vector(); + forEach([&coll, &fn](const E& e) { coll->append(fn(e)); }); + return coll; + } + + [[nodiscard]] bool contains(E& obj) { + return hasSome([&obj](const E& e) { + // C++17 compile-time magic to avoid issues with non-pointer types + if constexpr (std::is_same_v) { + return e == obj; + } else { + return e->equal(obj); + } + }); + } + + void removeAll() { items.removeAll(); } +}; diff --git a/benchmarks/C++/src/som/vector.h b/benchmarks/C++/src/som/vector.h new file mode 100644 index 00000000..1a62e503 --- /dev/null +++ b/benchmarks/C++/src/som/vector.h @@ -0,0 +1,248 @@ +#pragma once + +#include +#include +#include +#include +#include + +constexpr const size_t INITIAL_SIZE = 10; + +template +class Vector { + private: + E* storage; + size_t _capacity; + size_t _firstIdx{0}; + size_t _lastIdx{0}; + + void sort(size_t i, size_t j, std::function c) { + if (c == nullptr) { + defaultSort(i, j); + return; // Make sure to return if c is nullptr. + } + + const size_t n = j + 1 - i; + if (n <= 1) { + return; + } + + E di = storage[i]; + E dj = storage[j]; + + if (c(di, dj) > 0) { + swap(storage, i, j); + E tt = di; + di = dj; + dj = tt; + } + + if (n > 2) { + const size_t ij = (i + j) / 2; + E dij = storage[ij]; + + if (c(di, dij) <= 0) { + if (c(dij, dj) > 0) { + swap(storage, j, ij); + dij = dj; + } + } else { + swap(storage, i, ij); + dij = di; + } + + if (n > 3) { + size_t k = i; + size_t l = j - 1; + + while (true) { + while (k <= l && c(dij, storage[l]) <= 0) { + l -= 1; + } + + k += 1; + while (k <= l && c(storage[k], dij) <= 0) { + k += 1; + } + + if (k > l) { + break; + } + swap(storage, k, l); + } + + sort(i, l, c); + sort(k, j, c); + } + } + } + + public: + static Vector* with(const E& elem) { + auto v = new Vector(1); + v->append(elem); + return v; + } + + explicit Vector(size_t size) + : storage(size == 0 ? nullptr : new E[size]), _capacity(size) {} + + explicit Vector() : Vector(0) {} // NOLINT + + Vector(const Vector& other) = default; + + ~Vector() { delete[] storage; } + + void destroyValues() { + for (size_t i = _firstIdx; i < _lastIdx; i += 1) { + delete storage[i]; + } + } + + [[nodiscard]] E* at(size_t idx) const { + if (storage == nullptr || idx >= _lastIdx - _firstIdx) { + return nullptr; + } + return &storage[_firstIdx + idx]; + } + + void atPut(size_t idx, const E& val) { + if (storage == nullptr) { + _capacity = std::max(idx + 1, INITIAL_SIZE); + storage = new E[_capacity]; + } else if (idx >= _lastIdx - _firstIdx) { + size_t newLength = _capacity; + while (newLength <= idx + _firstIdx) { + newLength *= 2; + } + E* newStorage = new E[newLength]; + for (size_t i = 0; i < _lastIdx; i += 1) { + newStorage[i] = storage[i]; + } + delete[] storage; + storage = newStorage; + _capacity = newLength; + } + storage[_firstIdx + idx] = val; + if (_lastIdx < idx + 1) { + _lastIdx = idx + 1; + } + } + + void append(const E& elem) { + if (storage == nullptr) { + storage = new E[INITIAL_SIZE]; + _capacity = INITIAL_SIZE; + } else if (_lastIdx >= _capacity) { + // Need to expand _capacity first + _capacity *= 2; + E* newStorage = new E[_capacity]; + for (size_t i = 0; i < _lastIdx; i += 1) { + newStorage[i] = storage[i]; + } + delete[] storage; + storage = newStorage; + } + + storage[_lastIdx] = elem; + _lastIdx += 1; + } + + [[nodiscard]] bool isEmpty() const { return _lastIdx == _firstIdx; } + + void forEach(const std::function& fn) const { + for (size_t i = _firstIdx; i < _lastIdx; i += 1) { + fn(storage[i]); + } + } + + bool hasSome(const std::function& fn) const { + for (size_t i = _firstIdx; i < _lastIdx; i += 1) { + if (fn(storage[i])) { + return true; + } + } + return false; + } + + E getOne(const std::function& fn) const { + for (size_t i = _firstIdx; i < _lastIdx; i += 1) { + const E& e = storage[i]; + if (fn(e)) { + return e; + } + } + return nullptr; // Return a default-constructed object if not found + } + + [[nodiscard]] E first() const { + if (isEmpty()) { + return nullptr; // Return a default-constructed object for an empty + // vector + } + return storage[_firstIdx]; + } + + E removeFirst() { + if (isEmpty()) { + return nullptr; // Return a default-constructed object for an empty + // vector + } + _firstIdx += 1; + return storage[_firstIdx - 1]; + } + + bool remove(const E& obj) { + if (storage == nullptr || isEmpty()) { + return false; + } + + bool found = false; + size_t newLast = _firstIdx; + for (size_t i = _firstIdx; i < _lastIdx; i += 1) { + if (storage[i] == obj) { + found = true; + } else { + storage[newLast] = storage[i]; + newLast += 1; + } + } + _lastIdx = newLast; + return found; + } + + void removeAll() { + destroyValues(); + + _firstIdx = 0; + _lastIdx = 0; + + if (storage != nullptr) { + delete[] storage; + storage = new E[_capacity]; + } + } + + [[nodiscard]] size_t size() const { return _lastIdx - _firstIdx; } + + [[nodiscard]] size_t getCapacity() const { + return storage == nullptr ? 0 : _capacity; + } + + void sort(std::function c) { + if (size() > 0) { + sort(_firstIdx, _lastIdx - 1, c); + } + } + + private: + static void swap(E*, size_t, size_t) noexcept { + std::cerr << "swap not implemented\n"; + exit(1); + } + + void defaultSort(size_t, size_t) { + std::cerr << "defaultSort not implemented\n"; + exit(1); + } +}; diff --git a/benchmarks/C++/src/storage.h b/benchmarks/C++/src/storage.h new file mode 100644 index 00000000..58039d88 --- /dev/null +++ b/benchmarks/C++/src/storage.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include "benchmark.h" +#include "som/random.h" + +class ArrayTree { + public: + ArrayTree* children{nullptr}; + + ArrayTree() = default; + ~ArrayTree() { delete[] children; } +}; + +class Storage : public Benchmark { + private: + int32_t count{0}; + + public: + Storage() = default; + + std::any benchmark() override { + Random random; + + count = 0; + ArrayTree* result = build_tree_depth(7, random); + delete[] result; + + return count; + } + + bool verify_result(std::any result) override { + return 5461 == std::any_cast(result); + } + + private: + ArrayTree* build_tree_depth(int32_t depth, Random& random) { + count += 1; + if (depth == 1) { + return new ArrayTree[random.next() % 10 + 1]; + } + + auto* arr = new ArrayTree[4]; + for (size_t i = 0; i < 4; i += 1) { + arr[i].children = build_tree_depth(depth - 1, random); + } + return arr; + } +}; diff --git a/benchmarks/C++/src/towers.h b/benchmarks/C++/src/towers.h new file mode 100644 index 00000000..52955849 --- /dev/null +++ b/benchmarks/C++/src/towers.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include + +#include "benchmark.h" + +class TowersDisk { + private: + const int32_t size; + TowersDisk* next{nullptr}; + + public: + explicit TowersDisk(int32_t size) : size(size) {} + + ~TowersDisk() { delete next; } + + [[nodiscard]] int32_t get_size() const { return size; } + + [[nodiscard]] TowersDisk* get_next() const { return next; } + + void set_next(TowersDisk* disk) { next = disk; } +}; + +class Towers : public Benchmark { + private: + std::array piles{}; + int32_t moves_done{0}; + + public: + std::any benchmark() override { + piles = std::array{}; + build_tower_at(0, 13); + moves_done = 0; + move_disks(13, 0, 1); + + for (TowersDisk* disk : piles) { + delete disk; + } + + return moves_done; + } + + bool verify_result(std::any result) override { + return 8191 == std::any_cast(result); + } + + private: + void push_disk(TowersDisk* disk, int32_t pile) { + TowersDisk* top = piles.at(pile); + if (!(top == nullptr) && (disk->get_size() >= top->get_size())) { + std::cerr << "Cannot put a big disk on a smaller one"; + exit(1); + } + + disk->set_next(top); + piles.at(pile) = disk; + } + + TowersDisk* pop_disk_from(int32_t pile) { + TowersDisk* top = piles.at(pile); + if (top == nullptr) { + std::cerr << "Attempting to remove a disk from an empty pile"; + exit(1); + } + + piles.at(pile) = top->get_next(); + top->set_next(nullptr); + return top; + } + + void move_top_disk(int32_t from_pile, int32_t to_pile) { + push_disk(pop_disk_from(from_pile), to_pile); + moves_done += 1; + } + + void build_tower_at(int32_t pile, int32_t disks) { + for (int32_t i = disks; i >= 0; i -= 1) { + push_disk(new TowersDisk(i), pile); + } + } + + void move_disks(int32_t disks, int32_t from_pile, int32_t to_pile) { + if (disks == 1) { + move_top_disk(from_pile, to_pile); + } else { + const int32_t other_pile = (3 - from_pile) - to_pile; + move_disks(disks - 1, from_pile, other_pile); + move_top_disk(from_pile, to_pile); + move_disks(disks - 1, other_pile, to_pile); + } + } +}; diff --git a/docs/core-language.md b/docs/core-language.md index 9df8e1f3..78dcde9b 100644 --- a/docs/core-language.md +++ b/docs/core-language.md @@ -33,9 +33,9 @@ permitted. ## The *Core* Languages -While our initial intention is to compare object-oriented (OO) languages, the +While our intention is to compare object-oriented (OO) languages, the benchmarks could be implemented on other languages as well as long as for -instance the polymorphic nature of the benchmark code can be expressed somehow. +instance the polymorphic nature of the benchmark code can be expressed. #### Required Abstractions @@ -47,8 +47,6 @@ The set of required concepts is: - basic array-like abstractions, ideally with a fixed size - strings, with access to individual characters, support for mutation is not required - - garbage collection, currently benchmarks rely on it and we do not yet have - variants that do manual memory management For some languages, a mapping of these abstraction is not trivial and we define [guidelines](guidelines.md) for these cases. For example, Java does not support diff --git a/docs/guidelines.md b/docs/guidelines.md index fcdfd67b..9f3a2bfd 100644 --- a/docs/guidelines.md +++ b/docs/guidelines.md @@ -71,7 +71,7 @@ requirements of the benchmarks. As a basic guideline, we use the following rules: - - Code should pass a linter (if available). This gives some basic consistency + - Code should pass a linter, if available, to ensure some basic consistency with established rules. However, some language-specific rules impact comparability for an example see our [Ruby Rubocop](../benchmarks/Ruby/.rubocop.yml) settings. @@ -85,7 +85,7 @@ As a basic guideline, we use the following rules: properties preferable. - Identical code structure is more important than 100% idiomatic code. - While prefer the use of idiomatic iteration constructs, in other cases it is + While we prefer idiomatic iteration constructs, in other cases it is required to use the same structure of methods and similar naming to prevent differences in the general code structure etc. @@ -144,6 +144,115 @@ languages. - Use plain fields instead of getter/setter when they would be trivial + +### C++ + +With C++, we added support for the first language that does not have garbage +collection, which comes with a new dimension of issues to be considered. + +Explicit Memory Management Rules: + + - benchmarks have to run without memory leaks + + - stack allocation can be used where natural and where it does not change the + nature of the benchmark, for instance when an object/array is used only in + the dynamic scope of a function + + - existing data structures should be used where possible to manage + dynamically-created objects, for instance, iterate over an already existing + list of objects at the end of the benchmark or a method to free them + + - changes to code structure and APIs should be as minimal as possible, + for instance, if a method returns an allocated object, leave it as such, + and let the caller manage the memory + + - if the useful lifetime of object fields is restricted to a method, + the allocations referenced by these fields should be freed before + the end of a method, but the field should remain a field. + + - for arbitrary object graphs, as in DeltaBlue, `memory/object_tracker.h` can + be used to free the objects when not needed. + The use of `shared_ptr` may also be appropriate, but did not work for DeltaBlue. + +Memory Management Strategies Per Benchmarks: + + - **CD** use value objects for most data. Since it's a tree, the red/black tree + is trivially managed by deleting the nodes from the root. Vectors are managed + explicitly for the voxel map. Don't miss the empty vectors that are not + passed on as result though. + + - **DeltaBlue** uses `object_tracker`, since there are cyclic dependencies, + but we can free the full setup once it's not needed. + A mix of `shared_ptr` and `weak_ptr` could probably also work. + + - **Havlak** manages memory explicitly by assigning ownership to specific + classes. Specifically, the ControlFlowGraph owns the basic blocks and + block edges, the LoopStructureGraph owns the loops, the HavlakLoopFinder + owns its data including UnionFindNodes. Thus, the destructors can free + the corresponding memory. + + - **Json** relies on JSON documents being trees, and uses the + tree to free objects. The major tradeoff here is that we need to allocate + `true`, `false`, and `null` literal objects to have a uniform memory representation. + Though, otherwise, we do not require any management overhead. + + - **Richards** uses `object_tracker` for simplicity. + It could use `shared_ptr` and when accounting for cyclic references that + could work, too. Naively freing the task list did not seem to work, + but I might have missed something. + + - **Bounce** allocates everything statically, i.e., on the stack. + + - **List** trivially uses the list structure for freeing the list. + + - **Mandelbrot** does not allocate any data structures. + + - **NBody** allocates everything statically, i.e., on the stack. + + - **Permute** allocates an array dynamically, and frees it directly. + Since the benchmark holds the reference in a field, and allocates on + each iteration, the new/delete dance is needed to comply. + + - **Queens** allocates its arrays dynamically, and frees them directly, + same as Permute. + + - **Sieve** allocates everything statically, i.e., on the stack. + + - **Storage** allocates its tree dynamically, and frees it from the root. + + - **Towers** allocates the disks dynamically, which form a linked list, + that is used to free them once not needed anymore. + +General C++-isms: + + - the benchmarks, where possible, can be in headers only to match the code + structure of other languages + + - we use clang-tidy and clang-format + + - use `std::array` for fixed-sized arrays + + - use `const` where it is appropriate, but it won't really work with containers + and can be problematic for value classes + + - use `auto` and `auto*` to make code more concise as recommended by linter, + for instance for allocations + + - use annotations like `[[nodiscard]]` where indicated by the linter + + - use modern C++-isms, for instance range loops and `.at()` instead of `[]` on `std::array` + + - use initializer syntax for default values and member initializers lists when depending on constructor parameter + + - prefer `int32_t`, `size_t` and similar to be more explicit about semantics + and size of value, plain `int`/`long` shouldn't be used + + - avoid changing signatures for the sake of the compiler. It should do an + appropriate return-value optimization itself. + + - use templates where Java uses generics + + ## Repository Structure - `[benchmarks](../benchmarks)` contains for each language one folder with the diff --git a/test.conf b/test.conf index ab154308..1d6b9249 100644 --- a/test.conf +++ b/test.conf @@ -12,7 +12,7 @@ runs: benchmark_suites: test-som: gauge_adapter: RebenchLog - command: " -cp .:Core:CD:DeltaBlue:Havlak:Json:NBody:Richards:../../TruffleSOM/Smalltalk Harness.som %(benchmark)s 1 " + command: " -cp .:Core:CD:DeltaBlue:Havlak:Json:NBody:Richards:../../TruffleSOM/Smalltalk Harness.som %(benchmark)s 1 %(input)s " location: benchmarks/SOM max_invocation_time: 240 benchmarks: &BENCHMARKS @@ -28,23 +28,23 @@ benchmark_suites: extra_args: 1 - Bounce: - extra_args: 1 + input_sizes: [1, 100] - List: extra_args: 1 - Mandelbrot: - extra_args: 1 + input_sizes: [1, 500, 750] - NBody: extra_args: 1 - Permute: - extra_args: 1 + input_sizes: [1] - Queens: - extra_args: 1 + input_sizes: [1] - Sieve: - extra_args: 1 + input_sizes: [1] - Storage: - extra_args: 1 + input_sizes: [1] - Towers: - extra_args: 1 + input_sizes: [1] test-somns: gauge_adapter: RebenchLog @@ -100,6 +100,13 @@ benchmark_suites: max_invocation_time: 60 benchmarks: *BENCHMARKS + test-c++: + gauge_adapter: RebenchLog + location: benchmarks/C++ + command: " %(benchmark)s 1 %(input)s " + max_invocation_time: 60 + benchmarks: *BENCHMARKS + # VMs have a name and are specified by a path and the binary to be executed executors: Java: @@ -136,6 +143,9 @@ executors: executable: lua5.2 Python: executable: python + C++: + path: benchmarks/C++ + executable: harness experiments: test-som: @@ -172,3 +182,6 @@ experiments: test-python: suites: [test-python] executions: [Python] + test-cpp: + suites: [test-c++] + executions: [C++]