diff --git a/.clang-format b/.clang-format index f64df3d..0a5bfb8 100644 --- a/.clang-format +++ b/.clang-format @@ -3,17 +3,19 @@ Language: Cpp ColumnLimit: 110 IndentPPDirectives: BeforeHash AlwaysBreakTemplateDeclarations: Yes +BreakAfterAttributes: Always PackConstructorInitializers: CurrentLine AccessModifierOffset: -1 IndentCaseLabels: true AllowShortLambdasOnASingleLine: Empty RequiresExpressionIndentation: OuterScope BinPackArguments: false -# BinPackParameters: false LambdaBodyIndentation: Signature PenaltyReturnTypeOnItsOwnLine: 1 Macros: + - LF_TRY=if + - LF_CATCH_ALL=else - LF_HOF(x)={x;} - LF_HOF(x,y)={x,y;} - LF_HOF(x,y,z)={x,y,z;} diff --git a/.clang-tidy b/.clang-tidy index 5d813fd..d056811 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -10,148 +10,150 @@ Checks: "*,\ -llvm-header-guard,\ -llvm-include-order,\ -llvmlibc-*,\ - -modernize-use-nodiscard,\ + -readability-identifier-length,\ -misc-non-private-member-variables-in-classes" -WarningsAsErrors: '' +WarningsAsErrors: "" CheckOptions: - key: readability-function-cognitive-complexity.IgnoreMacros - value: 'true' - - key: 'bugprone-argument-comment.StrictMode' - value: 'true' -# Prefer using enum classes with 2 values for parameters instead of bools - - key: 'bugprone-argument-comment.CommentBoolLiterals' - value: 'true' - - key: 'bugprone-misplaced-widening-cast.CheckImplicitCasts' - value: 'true' - - key: 'bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression' - value: 'true' - - key: 'bugprone-suspicious-string-compare.WarnOnLogicalNotComparison' - value: 'true' - - key: 'readability-simplify-boolean-expr.ChainedConditionalReturn' - value: 'true' - - key: 'readability-simplify-boolean-expr.ChainedConditionalAssignment' - value: 'true' - - key: 'readability-uniqueptr-delete-release.PreferResetCall' - value: 'true' - - key: 'cppcoreguidelines-init-variables.MathHeader' - value: '' - - key: 'cppcoreguidelines-narrowing-conversions.PedanticMode' - value: 'true' - - key: 'readability-else-after-return.WarnOnUnfixable' - value: 'true' - - key: 'readability-else-after-return.WarnOnConditionVariables' - value: 'true' - - key: 'readability-inconsistent-declaration-parameter-name.Strict' - value: 'true' - - key: 'readability-qualified-auto.AddConstToQualified' - value: 'true' - - key: 'readability-redundant-access-specifiers.CheckFirstDeclaration' - value: 'true' -# These seem to be the most common identifier styles - - key: 'readability-identifier-naming.AbstractClassCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ClassCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ClassConstantCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ClassMemberCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ClassMethodCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ConstantCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ConstantMemberCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ConstantParameterCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ConstantPointerParameterCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ConstexprFunctionCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ConstexprMethodCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ConstexprVariableCase' - value: 'lower_case' - - key: 'readability-identifier-naming.EnumCase' - value: 'lower_case' - - key: 'readability-identifier-naming.EnumConstantCase' - value: 'lower_case' - - key: 'readability-identifier-naming.FunctionCase' - value: 'lower_case' - - key: 'readability-identifier-naming.GlobalConstantCase' - value: 'lower_case' - - key: 'readability-identifier-naming.GlobalConstantPointerCase' - value: 'lower_case' - - key: 'readability-identifier-naming.GlobalFunctionCase' - value: 'lower_case' - - key: 'readability-identifier-naming.GlobalPointerCase' - value: 'lower_case' - - key: 'readability-identifier-naming.GlobalVariableCase' - value: 'lower_case' - - key: 'readability-identifier-naming.InlineNamespaceCase' - value: 'lower_case' - - key: 'readability-identifier-naming.LocalConstantCase' - value: 'lower_case' - - key: 'readability-identifier-naming.LocalConstantPointerCase' - value: 'lower_case' - - key: 'readability-identifier-naming.LocalPointerCase' - value: 'lower_case' - - key: 'readability-identifier-naming.LocalVariableCase' - value: 'lower_case' - - key: 'readability-identifier-naming.MacroDefinitionCase' - value: 'UPPER_CASE' - - key: 'readability-identifier-naming.MemberCase' - value: 'lower_case' - - key: 'readability-identifier-naming.MethodCase' - value: 'lower_case' - - key: 'readability-identifier-naming.NamespaceCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ParameterCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ParameterPackCase' - value: 'lower_case' - - key: 'readability-identifier-naming.PointerParameterCase' - value: 'lower_case' - - key: 'readability-identifier-naming.PrivateMemberCase' - value: 'lower_case' - - key: 'readability-identifier-naming.PrivateMemberPrefix' - value: 'm_' - - key: 'readability-identifier-naming.PrivateMethodCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ProtectedMemberCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ProtectedMemberPrefix' - value: 'm_' - - key: 'readability-identifier-naming.ProtectedMethodCase' - value: 'lower_case' - - key: 'readability-identifier-naming.PublicMemberCase' - value: 'lower_case' - - key: 'readability-identifier-naming.PublicMethodCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ScopedEnumConstantCase' - value: 'lower_case' - - key: 'readability-identifier-naming.StaticConstantCase' - value: 'lower_case' - - key: 'readability-identifier-naming.StaticVariableCase' - value: 'lower_case' - - key: 'readability-identifier-naming.StructCase' - value: 'lower_case' - - key: 'readability-identifier-naming.TemplateParameterCase' - value: 'CamelCase' - - key: 'readability-identifier-naming.TemplateTemplateParameterCase' - value: 'CamelCase' - - key: 'readability-identifier-naming.TypeAliasCase' - value: 'lower_case' - - key: 'readability-identifier-naming.TypedefCase' - value: 'lower_case' - - key: 'readability-identifier-naming.TypeTemplateParameterCase' - value: 'CamelCase' - - key: 'readability-identifier-naming.UnionCase' - value: 'lower_case' - - key: 'readability-identifier-naming.ValueTemplateParameterCase' - value: 'CamelCase' - - key: 'readability-identifier-naming.VariableCase' - value: 'lower_case' - - key: 'readability-identifier-naming.VirtualMethodCase' - value: 'lower_case' + value: "true" + - key: "cppcoreguidelines-avoid-do-while.IgnoreMacros" + value: "true" + - key: "bugprone-argument-comment.StrictMode" + value: "true" + # Prefer using enum classes with 2 values for parameters instead of bools + - key: "bugprone-argument-comment.CommentBoolLiterals" + value: "true" + - key: "bugprone-misplaced-widening-cast.CheckImplicitCasts" + value: "true" + - key: "bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression" + value: "true" + - key: "bugprone-suspicious-string-compare.WarnOnLogicalNotComparison" + value: "true" + - key: "readability-simplify-boolean-expr.ChainedConditionalReturn" + value: "true" + - key: "readability-simplify-boolean-expr.ChainedConditionalAssignment" + value: "true" + - key: "readability-uniqueptr-delete-release.PreferResetCall" + value: "true" + - key: "cppcoreguidelines-init-variables.MathHeader" + value: "" + - key: "cppcoreguidelines-narrowing-conversions.PedanticMode" + value: "true" + - key: "readability-else-after-return.WarnOnUnfixable" + value: "true" + - key: "readability-else-after-return.WarnOnConditionVariables" + value: "true" + - key: "readability-inconsistent-declaration-parameter-name.Strict" + value: "true" + - key: "readability-qualified-auto.AddConstToQualified" + value: "true" + - key: "readability-redundant-access-specifiers.CheckFirstDeclaration" + value: "true" + # These seem to be the most common identifier styles + - key: "readability-identifier-naming.AbstractClassCase" + value: "lower_case" + - key: "readability-identifier-naming.ClassCase" + value: "lower_case" + - key: "readability-identifier-naming.ClassConstantCase" + value: "lower_case" + - key: "readability-identifier-naming.ClassMemberCase" + value: "lower_case" + - key: "readability-identifier-naming.ClassMethodCase" + value: "lower_case" + - key: "readability-identifier-naming.ConstantCase" + value: "lower_case" + - key: "readability-identifier-naming.ConstantMemberCase" + value: "lower_case" + - key: "readability-identifier-naming.ConstantParameterCase" + value: "lower_case" + - key: "readability-identifier-naming.ConstantPointerParameterCase" + value: "lower_case" + - key: "readability-identifier-naming.ConstexprFunctionCase" + value: "lower_case" + - key: "readability-identifier-naming.ConstexprMethodCase" + value: "lower_case" + - key: "readability-identifier-naming.ConstexprVariableCase" + value: "lower_case" + - key: "readability-identifier-naming.EnumCase" + value: "lower_case" + - key: "readability-identifier-naming.EnumConstantCase" + value: "lower_case" + - key: "readability-identifier-naming.FunctionCase" + value: "lower_case" + - key: "readability-identifier-naming.GlobalConstantCase" + value: "lower_case" + - key: "readability-identifier-naming.GlobalConstantPointerCase" + value: "lower_case" + - key: "readability-identifier-naming.GlobalFunctionCase" + value: "lower_case" + - key: "readability-identifier-naming.GlobalPointerCase" + value: "lower_case" + - key: "readability-identifier-naming.GlobalVariableCase" + value: "lower_case" + - key: "readability-identifier-naming.InlineNamespaceCase" + value: "lower_case" + - key: "readability-identifier-naming.LocalConstantCase" + value: "lower_case" + - key: "readability-identifier-naming.LocalConstantPointerCase" + value: "lower_case" + - key: "readability-identifier-naming.LocalPointerCase" + value: "lower_case" + - key: "readability-identifier-naming.LocalVariableCase" + value: "lower_case" + - key: "readability-identifier-naming.MacroDefinitionCase" + value: "UPPER_CASE" + - key: "readability-identifier-naming.MemberCase" + value: "lower_case" + - key: "readability-identifier-naming.MethodCase" + value: "lower_case" + - key: "readability-identifier-naming.NamespaceCase" + value: "lower_case" + - key: "readability-identifier-naming.ParameterCase" + value: "lower_case" + - key: "readability-identifier-naming.ParameterPackCase" + value: "lower_case" + - key: "readability-identifier-naming.PointerParameterCase" + value: "lower_case" + - key: "readability-identifier-naming.PrivateMemberCase" + value: "lower_case" + - key: "readability-identifier-naming.PrivateMemberPrefix" + value: "m_" + - key: "readability-identifier-naming.PrivateMethodCase" + value: "lower_case" + - key: "readability-identifier-naming.ProtectedMemberCase" + value: "lower_case" + - key: "readability-identifier-naming.ProtectedMemberPrefix" + value: "m_" + - key: "readability-identifier-naming.ProtectedMethodCase" + value: "lower_case" + - key: "readability-identifier-naming.PublicMemberCase" + value: "lower_case" + - key: "readability-identifier-naming.PublicMethodCase" + value: "lower_case" + - key: "readability-identifier-naming.ScopedEnumConstantCase" + value: "lower_case" + - key: "readability-identifier-naming.StaticConstantCase" + value: "lower_case" + - key: "readability-identifier-naming.StaticVariableCase" + value: "lower_case" + - key: "readability-identifier-naming.StructCase" + value: "lower_case" + - key: "readability-identifier-naming.TemplateParameterCase" + value: "CamelCase" + - key: "readability-identifier-naming.TemplateTemplateParameterCase" + value: "CamelCase" + - key: "readability-identifier-naming.TypeAliasCase" + value: "lower_case" + - key: "readability-identifier-naming.TypedefCase" + value: "lower_case" + - key: "readability-identifier-naming.TypeTemplateParameterCase" + value: "CamelCase" + - key: "readability-identifier-naming.UnionCase" + value: "lower_case" + - key: "readability-identifier-naming.ValueTemplateParameterCase" + value: "CamelCase" + - key: "readability-identifier-naming.VariableCase" + value: "lower_case" + - key: "readability-identifier-naming.VirtualMethodCase" + value: "lower_case" ... diff --git a/CMakeLists.txt b/CMakeLists.txt index 535b76a..a909f6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,10 @@ target_sources(libfork_libfork PUBLIC FILE_SET HEADERS FILES include/libfork/version.hpp - include/libfork/macros.hpp + include/libfork/__impl/compiler.hpp + include/libfork/__impl/exception.hpp + include/libfork/__impl/utils.hpp + include/libfork/__impl/assume.hpp BASE_DIRS include ) @@ -54,8 +57,12 @@ target_sources(libfork_libfork FILE_SET CXX_MODULES FILES src/core/core.cxx src/core/promise.cxx + src/core/concepts.cxx + src/core/utility.cxx + src/core/frame.cxx + src/core/constants.cxx PRIVATE - # src/libfork.cpp + src/exception.cpp ) # ====================== diff --git a/CMakeUserPresets.json b/CMakeUserPresets.json index 6947d51..65d9c6e 100644 --- a/CMakeUserPresets.json +++ b/CMakeUserPresets.json @@ -34,16 +34,6 @@ "execution": { "stopOnFailure": true } - }, - { - "name": "bench", - "configurePreset": "bench", - "output": { - "outputOnFailure": true - }, - "execution": { - "stopOnFailure": true - } } ], "workflowPresets": [ @@ -76,10 +66,6 @@ { "type": "build", "name": "bench" - }, - { - "type": "test", - "name": "bench" } ] } diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index a2c12ef..77a1744 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -44,10 +44,10 @@ target_sources(libfork_benchmark # ---- Libfork ---- -# target_sources(libfork_benchmark -# PRIVATE -# src/libfork_benchmark/fib/fib.cpp -# ) +target_sources(libfork_benchmark + PRIVATE + src/libfork_benchmark/fib/lf_parts.cpp +) target_link_libraries(libfork_benchmark PRIVATE diff --git a/benchmark/src/libfork_benchmark/common.hpp b/benchmark/src/libfork_benchmark/common.hpp index ee494ba..d886c22 100644 --- a/benchmark/src/libfork_benchmark/common.hpp +++ b/benchmark/src/libfork_benchmark/common.hpp @@ -3,7 +3,7 @@ #include #include -#include "libfork/macros.hpp" +#include "libfork/__impl/exception.hpp" struct incorrect_result : public std::runtime_error { using std::runtime_error::runtime_error; diff --git a/benchmark/src/libfork_benchmark/fib/fib.hpp b/benchmark/src/libfork_benchmark/fib/fib.hpp index 0b6cd90..a0cca1e 100644 --- a/benchmark/src/libfork_benchmark/fib/fib.hpp +++ b/benchmark/src/libfork_benchmark/fib/fib.hpp @@ -2,8 +2,10 @@ #include +#include "libfork_benchmark/common.hpp" + inline constexpr int fib_test = 3; -inline constexpr int fib_base = 40; +inline constexpr int fib_base = 37; /** * @brief Non-recursive Fibonacci calculation diff --git a/benchmark/src/libfork_benchmark/fib/lf_parts.cpp b/benchmark/src/libfork_benchmark/fib/lf_parts.cpp new file mode 100644 index 0000000..d2e07a9 --- /dev/null +++ b/benchmark/src/libfork_benchmark/fib/lf_parts.cpp @@ -0,0 +1,114 @@ +#include +#include +#include + +#include + +#include "libfork/__impl/assume.hpp" + +#include "libfork_benchmark/fib/fib.hpp" + +import libfork.core; + +namespace { + +struct stack_on_heap { + static constexpr auto operator new(std::size_t sz) -> void * { return ::operator new(sz); } + static constexpr auto operator delete(void *p, [[maybe_unused]] std::size_t sz) noexcept -> void { + ::operator delete(p, sz); + } +}; + +thread_local static std::byte *sp = nullptr; + +[[nodiscard]] +auto align(std::size_t n) -> std::size_t { + return (n + lf::k_new_align - 1) & ~(lf::k_new_align - 1); +} + +struct tls_stack { + + static auto operator new(std::size_t sz) -> void * { + auto *prev = sp; + sp += align(sz); + return prev; + } + + static auto operator delete([[maybe_unused]] void *p, [[maybe_unused]] std::size_t sz) noexcept -> void { + sp = std::bit_cast(p); + } +}; + +template +constexpr auto no_await = + [](this auto fib, std::int64_t *ret, std::int64_t n) -> lf::task { + if (n < 2) { + *ret = n; + co_return; + } + + std::int64_t lhs = 0; + std::int64_t rhs = 0; + + fib(&lhs, n - 1).release()->handle().resume(); + fib(&rhs, n - 2).release()->handle().resume(); + + *ret = lhs + rhs; +}; + +template +constexpr auto await = [](this auto fib, std::int64_t *ret, std::int64_t n) -> lf::task { + if (n < 2) { + *ret = n; + co_return; + } + + std::int64_t lhs = 0; + std::int64_t rhs = 0; + + co_await fib(&lhs, n - 1); + co_await fib(&rhs, n - 2); + + *ret = lhs + rhs; +}; + +template +void fib(benchmark::State &state) { + + std::int64_t n = state.range(0); + std::int64_t expect = fib_ref(n); + + state.counters["n"] = static_cast(n); + + std::unique_ptr buffer = std::make_unique(1024 * 1024); + + sp = buffer.get(); + + for (auto _ : state) { + benchmark::DoNotOptimize(n); + std::int64_t result = 0; + + Fn(&result, n).release()->handle().resume(); + + CHECK_RESULT(result, expect); + benchmark::DoNotOptimize(result); + } + + if (sp != buffer.get()) { + LF_TERMINATE("Stack leak detected"); + } +} + +} // namespace + +BENCHMARK(fib>)->Name("test/libfork/fib/heap/no_await")->Arg(fib_test); +BENCHMARK(fib>)->Name("base/libfork/fib/heap/no_await")->Arg(fib_base); + +BENCHMARK(fib>)->Name("test/libfork/fib/heap/await")->Arg(fib_test); +BENCHMARK(fib>)->Name("base/libfork/fib/heap/await")->Arg(fib_base); + +BENCHMARK(fib>)->Name("test/libfork/fib/data/no_await")->Arg(fib_test); +BENCHMARK(fib>)->Name("base/libfork/fib/data/no_await")->Arg(fib_base); + +BENCHMARK(fib>)->Name("test/libfork/fib/data/await")->Arg(fib_test); +BENCHMARK(fib>)->Name("base/libfork/fib/data/await")->Arg(fib_base); diff --git a/benchmark/src/libfork_benchmark/fib/serial.cpp b/benchmark/src/libfork_benchmark/fib/serial.cpp index 1d53b8b..4a50f5e 100644 --- a/benchmark/src/libfork_benchmark/fib/serial.cpp +++ b/benchmark/src/libfork_benchmark/fib/serial.cpp @@ -1,8 +1,7 @@ #include -#include "libfork/macros.hpp" +#include "libfork/__impl/compiler.hpp" -#include "libfork_benchmark/common.hpp" #include "libfork_benchmark/fib/fib.hpp" namespace { @@ -27,7 +26,7 @@ void fib_serial(benchmark::State &state) { std::int64_t n = state.range(0); std::int64_t expect = fib_ref(n); - state.counters["n"] = n; + state.counters["n"] = static_cast(n); for (auto _ : state) { benchmark::DoNotOptimize(n); diff --git a/benchmark/src/libfork_benchmark/fib/serial_return.cpp b/benchmark/src/libfork_benchmark/fib/serial_return.cpp index b43dd7e..976e967 100644 --- a/benchmark/src/libfork_benchmark/fib/serial_return.cpp +++ b/benchmark/src/libfork_benchmark/fib/serial_return.cpp @@ -1,6 +1,7 @@ #include -#include "libfork_benchmark/common.hpp" +#include "libfork/__impl/compiler.hpp" + #include "libfork_benchmark/fib/fib.hpp" namespace { @@ -21,7 +22,7 @@ void fib_serial_return(benchmark::State &state) { std::int64_t n = state.range(0); std::int64_t expect = fib_ref(n); - state.counters["n"] = n; + state.counters["n"] = static_cast(n); for (auto _ : state) { benchmark::DoNotOptimize(n); diff --git a/include/libfork/__impl/assume.hpp b/include/libfork/__impl/assume.hpp new file mode 100644 index 0000000..855f037 --- /dev/null +++ b/include/libfork/__impl/assume.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "libfork/__impl/exception.hpp" + +/** + * @file assume.hpp + * + * @brief A collection of internal macros. + * + * These macros are not safe to use unless `import std` is in scope. + */ + +/** + * @brief If expr evaluates to `false`, terminates the program with an error message. + * + * This macro is always active, regardless of optimization settings or `NDEBUG`. + */ +#define LF_ASSERT(expr) \ + do { \ + if (!(expr)) { \ + LF_TERMINATE("Assumption '" #expr "' failed!"); \ + } \ + } while (false) + +/** + * @brief Invokes undefined behavior if ``expr`` evaluates to `false`. + * + * \rst + * + * .. warning:: + * + * This has different semantics than ``[[assume(expr)]]`` as it WILL evaluate the + * expression at runtime. Hence you should conservatively only use this macro + * if ``expr`` is side-effect free and cheap to evaluate. + * + * \endrst + */ +#ifdef NDEBUG + #define LF_ASSUME(expr) \ + do { \ + if (!(expr)) { \ + ::std::unreachable(); \ + } \ + } while (false) +#else + #define LF_ASSUME(expr) LF_ASSERT(expr) +#endif diff --git a/include/libfork/__impl/compiler.hpp b/include/libfork/__impl/compiler.hpp new file mode 100644 index 0000000..8e71083 --- /dev/null +++ b/include/libfork/__impl/compiler.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include "libfork/__impl/exception.hpp" + +/** + * @file compiler.hpp + * + * @brief A collection of internal macros. + * + * These macros are standalone i.e. they can be used without importing/including anything else. + */ + +// =============== Inlining/optimization =============== // + +/** + * @brief Macro to use next to 'inline' to force a function to be inlined. + * + * \rst + * + * .. note:: + * + * This does not imply the c++'s `inline` keyword which also has an effect on linkage. + * + * \endrst + */ +#if !defined(LF_FORCE_INLINE) + #if defined(_MSC_VER) && !defined(__clang__) + #define LF_FORCE_INLINE __forceinline + #elif defined(__GNUC__) && __GNUC__ > 3 + // Clang also defines __GNUC__ (as 4) + #define LF_FORCE_INLINE __attribute__((__always_inline__)) + #else + #define LF_FORCE_INLINE + #endif +#endif + +/** + * @brief Macro to prevent a function to be inlined. + */ +#if !defined(LF_NO_INLINE) + #if defined(_MSC_VER) && !defined(__clang__) + #define LF_NO_INLINE __declspec(noinline) + #elif defined(__GNUC__) && __GNUC__ > 3 + // Clang also defines __GNUC__ (as 4) + #if defined(__CUDACC__) + // nvcc doesn't always parse __noinline__, see: https://svn.boost.org/trac/boost/ticket/9392 + #define LF_NO_INLINE __attribute__((noinline)) + #elif defined(__HIP__) + // See https://github.com/boostorg/config/issues/392 + #define LF_NO_INLINE __attribute__((noinline)) + #else + #define LF_NO_INLINE __attribute__((__noinline__)) + #endif + #else + #define LF_NO_INLINE + #endif +#endif diff --git a/include/libfork/__impl/exception.hpp b/include/libfork/__impl/exception.hpp new file mode 100644 index 0000000..857dbca --- /dev/null +++ b/include/libfork/__impl/exception.hpp @@ -0,0 +1,70 @@ +#pragma once + +/** + * @file exception.hpp + * + * @brief A collection of internal macros for exception handling. + * + * These macros are standalone i.e. they can be used without importing/including anything else. + */ + +/** + * @brief Detects if the compiler has exceptions enabled. + * + * Overridable by defining `LF_COMPILER_EXCEPTIONS` globally. + */ +#ifndef LF_COMPILER_EXCEPTIONS + #if defined(__cpp_exceptions) || (defined(_MSC_VER) && defined(_CPPUNWIND)) || defined(__EXCEPTIONS) + #define LF_COMPILER_EXCEPTIONS 1 + #else + #define LF_COMPILER_EXCEPTIONS 0 + #endif +#endif + +namespace lf::impl { + +/** + * @brief Calls `std::terminate` after printing `msg`. + */ +[[noreturn]] +void terminate_with(char const *message, char const *file, int line) noexcept; + +} // namespace lf::impl + +#define LF_TERMINATE(message) ::lf::impl::terminate_with((message), __FILE__, __LINE__) + +#if LF_COMPILER_EXCEPTIONS + /** + * @brief Expands to ``try`` if exceptions are enabled, otherwise expands to ``if constexpr (true)``. + */ + #define LF_TRY try + /** + * @brief Expands to ``catch (...)`` if exceptions are enabled, otherwise ``if constexpr (false)``. + */ + #define LF_CATCH_ALL catch (...) + /** + * @brief Expands to ``throw X`` if exceptions are enabled, otherwise terminates the program. + */ + #define LF_THROW(X) throw X + /** + * @brief Expands to ``throw`` if exceptions are enabled, otherwise terminates the program. + */ + #define LF_RETHROW throw +#else + /** + * @brief Expands to ``try`` if exceptions are enabled, otherwise expands to ``if constexpr (true)``. + */ + #define LF_TRY if constexpr (true) + /** + * @brief Expands to ``catch (...)`` if exceptions are enabled, otherwise ``if constexpr (false)``. + */ + #define LF_CATCH_ALL if constexpr (false) + /** + * @brief Expands to ``throw X`` if exceptions are enabled, otherwise terminates the program. + */ + #define LF_THROW(X) LF_TERMINATE("Tried to throw '" #X "' without compiler exceptions") + /** + * @brief Expands to ``throw`` if exceptions are enabled, otherwise terminates the program. + */ + #define LF_RETHROW LF_TERMINATE("Tried to rethrow without compiler exceptions") +#endif diff --git a/include/libfork/__impl/utils.hpp b/include/libfork/__impl/utils.hpp new file mode 100644 index 0000000..8f2e02e --- /dev/null +++ b/include/libfork/__impl/utils.hpp @@ -0,0 +1,27 @@ +#pragma once + +/** + * @file utils.hpp + * + * @brief A collection of internal utility macros. + * + * These macros are not safe to use unless `import std` is in scope. + */ + +// =============== Utility =============== // + +// clang-format off + +/** + * @brief Use like `BOOST_HOF_RETURNS` to define a function/lambda with all the noexcept/decltype specifiers. + * + * This macro is not truly variadic but the ``...`` allows commas in the macro argument. + */ +#define LF_HOF(...) noexcept(noexcept(__VA_ARGS__)) -> decltype(__VA_ARGS__) { return __VA_ARGS__;} + +// clang-format on + +/** + * @brief Use like `std::forward` to perfectly forward an expression. + */ +#define LF_FWD(...) ::std::forward(__VA_ARGS__) diff --git a/include/libfork/macros.hpp b/include/libfork/macros.hpp deleted file mode 100644 index 4a10506..0000000 --- a/include/libfork/macros.hpp +++ /dev/null @@ -1,125 +0,0 @@ -#pragma once - -/** - * @file macro.hpp - * - * @brief A collection of internal/public macros. - * - * These are exhaustively documented due to macros nasty visibility rules - * however, only macros that are marked as __[public]__ should be consumed. - */ - -// =============== Utility =============== // - -// clang-format off - -/** - * @brief Use like `BOOST_HOF_RETURNS` to define a function/lambda with all the noexcept/decltype specifiers. - * - * This macro is not truly variadic but the ``...`` allows commas in the macro argument. - */ -#define LF_HOF(...) noexcept(noexcept(__VA_ARGS__)) -> decltype(__VA_ARGS__) { return __VA_ARGS__;} - -// clang-format on - -/** - * @brief Use like `std::forward` to perfectly forward an expression. - */ -#define LF_FWD(...) std::forward(__VA_ARGS__) - -// =============== Inlining/optimization =============== // - -/** - * @brief Macro to use next to 'inline' to force a function to be inlined. - * - * \rst - * - * .. note:: - * - * This does not imply the c++'s `inline` keyword which also has an effect on linkage. - * - * \endrst - */ -#if !defined(LF_FORCE_INLINE) - #if defined(_MSC_VER) && !defined(__clang__) - #define LF_FORCE_INLINE __forceinline - #elif defined(__GNUC__) && __GNUC__ > 3 - // Clang also defines __GNUC__ (as 4) - #define LF_FORCE_INLINE __attribute__((__always_inline__)) - #else - #define LF_FORCE_INLINE - #endif -#endif - -/** - * @brief Macro to prevent a function to be inlined. - */ -#if !defined(LF_NO_INLINE) - #if defined(_MSC_VER) && !defined(__clang__) - #define LF_NO_INLINE __declspec(noinline) - #elif defined(__GNUC__) && __GNUC__ > 3 - // Clang also defines __GNUC__ (as 4) - #if defined(__CUDACC__) - // nvcc doesn't always parse __noinline__, see: https://svn.boost.org/trac/boost/ticket/9392 - #define LF_NO_INLINE __attribute__((noinline)) - #elif defined(__HIP__) - // See https://github.com/boostorg/config/issues/392 - #define LF_NO_INLINE __attribute__((noinline)) - #else - #define LF_NO_INLINE __attribute__((__noinline__)) - #endif - #else - #define LF_NO_INLINE - #endif -#endif - -// =============== Exception Handling Macros =============== // - -/** - * @brief __[public]__ Detects if the compiler has exceptions enabled. - * - * Overridable by defining ``LF_COMPILER_EXCEPTIONS``. - */ -#ifndef LF_COMPILER_EXCEPTIONS - #if defined(__cpp_exceptions) || (defined(_MSC_VER) && defined(_CPPUNWIND)) || defined(__EXCEPTIONS) - #define LF_COMPILER_EXCEPTIONS 1 - #else - #define LF_COMPILER_EXCEPTIONS 0 - #endif -#endif - -#if LF_COMPILER_EXCEPTIONS - /** - * @brief Expands to ``try`` if exceptions are enabled, otherwise expands to ``if constexpr (true)``. - */ - #define LF_TRY try - /** - * @brief Expands to ``catch (...)`` if exceptions are enabled, otherwise ``if constexpr (false)``. - */ - #define LF_CATCH_ALL catch (...) - /** - * @brief Expands to ``throw X`` if exceptions are enabled, otherwise terminates the program. - */ - #define LF_THROW(X) throw X - /** - * @brief Expands to ``throw`` if exceptions are enabled, otherwise terminates the program. - */ - #define LF_RETHROW throw -#else - - #include - #include - - #define LF_TRY if constexpr (true) - #define LF_CATCH_ALL if constexpr (false) - #ifndef NDEBUG - #define LF_THROW(X) assert(false && "Tried to throw: " #X) - #else - #define LF_THROW(X) std::terminate() - #endif - #ifndef NDEBUG - #define LF_RETHROW assert(false && "Tried to rethrow without compiler exceptions") - #else - #define LF_RETHROW std::terminate() - #endif -#endif diff --git a/src/core/concepts.cxx b/src/core/concepts.cxx new file mode 100644 index 0000000..bd88509 --- /dev/null +++ b/src/core/concepts.cxx @@ -0,0 +1,25 @@ +module; +export module libfork.core:concepts; + +import std; + +namespace lf { + +/** + * @brief A type returnable from libfork's async functions/coroutines. + * + * This requires that `T` is `void` or a `std::movable` type. + */ +template +concept returnable = std::is_void_v || std::movable; + +template +concept mixinable = std::is_empty_v && !std::is_final_v; + +export template +concept alloc_mixin = mixinable && requires (std::size_t n, T *ptr) { + { T::operator new(n) } -> std::same_as; + { T::operator delete(ptr, n) } noexcept -> std::same_as; +}; + +} // namespace lf diff --git a/src/core/constants.cxx b/src/core/constants.cxx new file mode 100644 index 0000000..e77cdd9 --- /dev/null +++ b/src/core/constants.cxx @@ -0,0 +1,14 @@ +module; +export module libfork.core:constants; + +import std; + +namespace lf { + +constexpr std::size_t k_kilobyte = 1024; +constexpr std::size_t k_megabyte = 1024 * k_kilobyte; + +export constexpr std::size_t k_new_align = __STDCPP_DEFAULT_NEW_ALIGNMENT__; +export constexpr std::size_t k_cache_line = std::hardware_destructive_interference_size; + +} // namespace lf diff --git a/src/core/core.cxx b/src/core/core.cxx index 079a3ad..25dbc61 100644 --- a/src/core/core.cxx +++ b/src/core/core.cxx @@ -2,3 +2,7 @@ export module libfork.core; // All partitions export import :promise; +export import :concepts; +export import :utility; +export import :frame; +export import :constants; diff --git a/src/core/frame.cxx b/src/core/frame.cxx new file mode 100644 index 0000000..f01a9b0 --- /dev/null +++ b/src/core/frame.cxx @@ -0,0 +1,13 @@ +export module libfork.core:frame; + +import std; + +namespace lf { + +struct frame_type { + frame_type *parent = nullptr; +}; + +static_assert(std::is_standard_layout_v); + +} // namespace lf diff --git a/src/core/promise.cxx b/src/core/promise.cxx index 4934acd..3a46707 100644 --- a/src/core/promise.cxx +++ b/src/core/promise.cxx @@ -1,59 +1,180 @@ module; #include -#include "libfork/macros.hpp" +#include "libfork/__impl/assume.hpp" +#include "libfork/__impl/utils.hpp" export module libfork.core:promise; import std; +import :concepts; +import :utility; +import :frame; + namespace lf { -// =============== Frame =============== // +// =============== Forward-decl =============== // + +template +struct promise_type; -struct frame_type { - frame_type *parent; +// =============== Task =============== // + +/** + * @brief `std::unique_ptr` compatible deleter for coroutine promises. + */ +struct promise_deleter { + template + constexpr static void operator()(T *ptr) noexcept { + std::coroutine_handle::from_promise(*ptr).destroy(); + } }; -static_assert(std::is_standard_layout_v); +template +using unique_promise = std::unique_ptr; + +/** + * @brief The return type for libfork's async functions/coroutines. + * + * This predominantly exists to disambiguate `libfork`s coroutines from other + * coroutines and specify `T` the async function's return type which is + * required to be `void` or a `std::movable` type. + * + * \rst + * + * .. note:: + * + * No consumer of this library should ever touch an instance of this type, + * it is used for specifying the return type of an `async` function only. + * + * \endrst + */ +export template +struct task final : unique_promise> {}; // =============== Frame-mixin =============== // +[[nodiscard]] +constexpr auto final_suspend(frame_type *frame) -> std::coroutine_handle<> { + + LF_ASSUME(frame != nullptr); + + frame_type *parent_frame = frame->parent; + + { + // Destroy the child frame + unique_promise _{frame}; + } + + if (parent_frame != nullptr) { + return std::coroutine_handle::from_promise(*parent_frame); + } + + return std::noop_coroutine(); +} + +struct final_awaitable : std::suspend_always { + template + constexpr auto + await_suspend(std::coroutine_handle> handle) noexcept -> std::coroutine_handle<> { + return final_suspend(&handle.promise().frame); + } +}; + +// TODO: can we type-erase T/Policy here? + +template +struct just_awaitable : std::suspend_always { + + task child; + + template + auto await_suspend(std::coroutine_handle> parent) noexcept -> std::coroutine_handle<> { + + LF_ASSUME(child != nullptr); + LF_ASSUME(child->frame.parent == nullptr); + + child->frame.parent = &parent.promise().frame; + + return child.release()->handle(); + } +}; + struct mixin_frame { - auto self(this auto &&self) LF_HOF(LF_FWD(self).frame) + + // === For internal use === // + + template + requires (!std::is_const_v) + [[nodiscard]] + constexpr auto handle(this Self &self) LF_HOF(std::coroutine_handle::from_promise(self)) + + [[nodiscard]] + constexpr auto get_frame(this auto &&self) + LF_HOF(LF_FWD(self).frame) + + // === Called by the compiler === // + + template + static constexpr auto await_transform(task child) -> just_awaitable { + return {.child = std::move(child)}; + } + + constexpr static auto initial_suspend() noexcept -> std::suspend_always { return {}; } + + constexpr static auto final_suspend() noexcept -> final_awaitable { return {}; } + + constexpr static void unhandled_exception() noexcept { std::terminate(); } }; static_assert(std::is_empty_v); -// =============== Promises =============== // +// =============== Promise (void) =============== // -template -struct promise_type; +template +struct promise_type : StackPolicy, mixin_frame { -template <> -struct promise_type : mixin_frame { frame_type frame; + + constexpr auto get_return_object() -> task { return {{this, {}}}; } + + constexpr static void return_void() {} +}; + +struct dummy_alloc { + static auto operator new(std::size_t) -> void *; + static auto operator delete(void *, std::size_t) noexcept -> void; }; -static_assert(alignof(promise_type) == alignof(frame_type)); +static_assert(alignof(promise_type) == alignof(frame_type)); #ifdef __cpp_lib_is_pointer_interconvertible -static_assert(std::is_pointer_interconvertible_with_class(&promise_type::frame)); +static_assert(std::is_pointer_interconvertible_with_class(&promise_type::frame)); #else -static_assert(std::is_standard_layout_v>); +static_assert(std::is_standard_layout_v>); #endif -template -struct promise_type : mixin_frame { +// =============== Promise (non-void) =============== // + +template +struct promise_type : StackPolicy, mixin_frame { frame_type frame; T *return_address; }; -static_assert(alignof(promise_type) == alignof(frame_type)); +static_assert(alignof(promise_type) == alignof(frame_type)); #ifdef __cpp_lib_is_pointer_interconvertible -static_assert(std::is_pointer_interconvertible_with_class(&promise_type::frame)); +static_assert(std::is_pointer_interconvertible_with_class(&promise_type::frame)); #else -static_assert(std::is_standard_layout_v>); +static_assert(std::is_standard_layout_v>); #endif } // namespace lf + +// =============== std specialization =============== // + +template +struct std::coroutine_traits, Args...> { + using promise_type = ::lf::promise_type; +}; diff --git a/src/core/utility.cxx b/src/core/utility.cxx new file mode 100644 index 0000000..ff5b93d --- /dev/null +++ b/src/core/utility.cxx @@ -0,0 +1,31 @@ +export module libfork.core:utility; + +import std; + +namespace lf { + +/** + * @brief An immovable, empty type, use as a mixin. + */ +export struct immovable { + immovable() = default; + immovable(const immovable &) = delete; + immovable(immovable &&) = delete; + auto operator=(const immovable &) -> immovable & = delete; + auto operator=(immovable &&) -> immovable & = delete; + ~immovable() = default; +}; + +/** + * @brief A move-only, empty type, use as a mixin. + */ +export struct move_only { + move_only() = default; + move_only(const move_only &) = delete; + move_only(move_only &&) = default; + auto operator=(const move_only &) -> move_only & = delete; + auto operator=(move_only &&) -> move_only & = default; + ~move_only() = default; +}; + +} // namespace lf diff --git a/src/exception.cpp b/src/exception.cpp new file mode 100644 index 0000000..049bed3 --- /dev/null +++ b/src/exception.cpp @@ -0,0 +1,19 @@ +#include + +#include "libfork/__impl/exception.hpp" + +import std; + +namespace lf::impl { + +[[noreturn]] +void terminate_with(char const *message, char const *file, int line) noexcept { + LF_TRY { + std::println(stderr, "{}:{}: {}", file, line, message); + } LF_CATCH_ALL { + // Drop exceptions during termination + } + std::terminate(); +} + +} // namespace lf::impl diff --git a/test/src/utility.cpp b/test/src/utility.cpp new file mode 100644 index 0000000..e4a745f --- /dev/null +++ b/test/src/utility.cpp @@ -0,0 +1,41 @@ +#include + +import std; + +import libfork.core; + +using lf::immovable; +using lf::move_only; + +namespace { + +struct test_immovable : immovable {}; +struct test_move_only : move_only {}; + +} // namespace + +TEST_CASE("Utility properties", "[utility]") { + + STATIC_REQUIRE(std::is_default_constructible_v); + STATIC_REQUIRE(!std::is_copy_constructible_v); + STATIC_REQUIRE(!std::is_move_constructible_v); + STATIC_REQUIRE(!std::is_copy_assignable_v); + STATIC_REQUIRE(!std::is_move_assignable_v); + + STATIC_REQUIRE(std::is_default_constructible_v); + STATIC_REQUIRE(!std::is_copy_constructible_v); + STATIC_REQUIRE(std::is_move_constructible_v); + STATIC_REQUIRE(!std::is_copy_assignable_v); + STATIC_REQUIRE(std::is_move_assignable_v); + STATIC_REQUIRE(std::movable); + + STATIC_REQUIRE(std::is_default_constructible_v); + STATIC_REQUIRE(!std::is_copy_constructible_v); + STATIC_REQUIRE(!std::is_move_constructible_v); + + STATIC_REQUIRE(std::is_default_constructible_v); + STATIC_REQUIRE(!std::is_copy_constructible_v); + STATIC_REQUIRE(std::is_move_constructible_v); + STATIC_REQUIRE(std::is_move_assignable_v); + STATIC_REQUIRE(std::movable); +}