diff --git a/benchmark/src/libfork_benchmark/fib/fib.hpp b/benchmark/src/libfork_benchmark/fib/fib.hpp index d39478d0..fd208483 100644 --- a/benchmark/src/libfork_benchmark/fib/fib.hpp +++ b/benchmark/src/libfork_benchmark/fib/fib.hpp @@ -59,32 +59,41 @@ struct tls_bump { } }; -constinit inline std::byte *bump_ptr = nullptr; +// === Shared Context Logic === -struct global_bump { +template +struct vector_ctx { - static auto operator new(std::size_t sz) -> void * { - auto *prev = bump_ptr; - bump_ptr += fib_align_size(sz); - return prev; - } + using handle_type = lf::frame_handle; - static auto operator delete(void *p, [[maybe_unused]] std::size_t sz) noexcept -> void { - bump_ptr = std::bit_cast(p); + std::vector work; + Alloc allocator; + + vector_ctx() { work.reserve(1024); } + + auto alloc() noexcept -> Alloc & { return allocator; } + + void push(handle_type handle) { work.push_back(handle); } + + auto pop() noexcept -> handle_type { + auto handle = work.back(); + work.pop_back(); + return handle; } }; -// === Shared Context Logic === +template +struct poly_vector_ctx final : lf::polymorphic_context { -struct vector_ctx final : lf::polymorphic_context { + using handle_type = lf::frame_handle>; - std::vector work; + std::vector work; - vector_ctx() { work.reserve(1024); } + poly_vector_ctx() { work.reserve(1024); } - void push(lf::work_handle handle) override { work.push_back(handle); } + void push(handle_type handle) override { work.push_back(handle); } - auto pop() noexcept -> lf::work_handle override { + auto pop() noexcept -> handle_type override { auto handle = work.back(); work.pop_back(); return handle; diff --git a/benchmark/src/libfork_benchmark/fib/lf_parts.cpp b/benchmark/src/libfork_benchmark/fib/lf_parts.cpp index e0cc3c40..3eeae2e2 100644 --- a/benchmark/src/libfork_benchmark/fib/lf_parts.cpp +++ b/benchmark/src/libfork_benchmark/fib/lf_parts.cpp @@ -12,15 +12,41 @@ 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); +struct global_allocator { + + struct empty {}; + + constexpr static auto push(std::size_t sz) -> void * { return ::operator new(sz); } + constexpr static auto pop(void *p, std::size_t sz) noexcept -> void { ::operator delete(p, sz); } + constexpr static auto checkpoint() noexcept -> empty { return {}; } + constexpr static auto switch_to(empty) noexcept -> void {} +}; + +static_assert(lf::stack_allocator); + +struct linear_allocator { + + std::unique_ptr data = std::make_unique(1024 * 1024); + std::byte *ptr = data.get(); + + constexpr auto push(std::size_t sz) -> void * { + auto *prev = ptr; + ptr += fib_align_size(sz); + return prev; } + constexpr auto pop(void *p, std::size_t) noexcept -> void { ptr = static_cast(p); } + + constexpr auto checkpoint() noexcept -> std::byte * { return data.get(); } + + constexpr static auto switch_to(std::byte *) noexcept -> void {} }; -template -constexpr auto no_await = [](this auto fib, std::int64_t *ret, std::int64_t n) -> lf::task { +static_assert(lf::stack_allocator); + +using lf::task; + +template +constexpr auto no_await = [](this auto fib, std::int64_t *ret, std::int64_t n) -> task { if (n < 2) { *ret = n; co_return; @@ -40,8 +66,8 @@ constexpr auto no_await = [](this auto fib, std::int64_t *ret, std::int64_t n) - *ret = lhs + rhs; }; -template -constexpr auto await = [](this auto fib, std::int64_t *ret, std::int64_t n) -> lf::task { +template +constexpr auto await = [](this auto fib, std::int64_t *ret, std::int64_t n) -> lf::task { if (n < 2) { *ret = n; co_return; @@ -56,7 +82,8 @@ constexpr auto await = [](this auto fib, std::int64_t *ret, std::int64_t n) -> l *ret = lhs + rhs; }; -constexpr auto ret = [](this auto fib, std::int64_t n) -> lf::task { +template +constexpr auto ret = [](this auto fib, std::int64_t n) -> lf::task { if (n < 2) { co_return n; } @@ -70,8 +97,8 @@ constexpr auto ret = [](this auto fib, std::int64_t n) -> lf::task -constexpr auto fork_call = [](this auto fib, std::int64_t n) -> lf::task { +template +constexpr auto fork_call = [](this auto fib, std::int64_t n) -> lf::task { if (n < 2) { co_return n; } @@ -85,7 +112,10 @@ constexpr auto fork_call = [](this auto fib, std::int64_t n) -> lf::task +using global_alloc = vector_ctx; +using linear_alloc = vector_ctx; + +template void fib(benchmark::State &state) { std::int64_t n = state.range(0); @@ -93,15 +123,13 @@ void fib(benchmark::State &state) { state.counters["n"] = static_cast(n); - // Set bump allocator buffer - std::unique_ptr buf = std::make_unique(1024 * 1024); - tls_bump_ptr = buf.get(); - bump_ptr = buf.get(); + T context; + + lf::thread_context = static_cast(&context); - // Set both context and poly context - std::unique_ptr ctx = std::make_unique(); - lf::thread_context = ctx.get(); - lf::thread_context = ctx.get(); + lf::defer _ = [] static noexcept { + lf::thread_context = nullptr; + }; for (auto _ : state) { benchmark::DoNotOptimize(n); @@ -121,55 +149,44 @@ void fib(benchmark::State &state) { CHECK_RESULT(result, expect); benchmark::DoNotOptimize(result); } - - if (tls_bump_ptr != buf.get() || bump_ptr != buf.get()) { - LF_TERMINATE("Stack leak detected"); - } - - tls_bump_ptr = nullptr; - bump_ptr = nullptr; - lf::thread_context = nullptr; - lf::thread_context = nullptr; } } // namespace -// Return by ref-arg, test direct root, no co-await, direct resumes, uses new/delete for alloc -BENCHMARK(fib>)->Name("test/libfork/fib/heap/no_await")->Arg(fib_test); -BENCHMARK(fib>)->Name("base/libfork/fib/heap/no_await")->Arg(fib_base); +static_assert(lf::worker_context); -// Same as above but uses tls bump allocator -BENCHMARK(fib>)->Name("test/libfork/fib/tls_bump/no_await")->Arg(fib_test); -BENCHMARK(fib>)->Name("base/libfork/fib/tls_bump/no_await")->Arg(fib_base); +// Return by ref-arg, test direct root, no co-await, direct resumes, uses new/delete for alloc +BENCHMARK(fib, global_alloc>)->Name("test/libfork/fib/heap/no_await")->Arg(fib_test); +BENCHMARK(fib, global_alloc>)->Name("base/libfork/fib/heap/no_await")->Arg(fib_base); -// Same as above but with global bump allocator -BENCHMARK(fib>)->Name("test/libfork/fib/global_bump/no_await")->Arg(fib_test); -BENCHMARK(fib>)->Name("base/libfork/fib/global_bump/no_await")->Arg(fib_base); +// Same as above but uses bump allocator +BENCHMARK(fib, linear_alloc>)->Name("test/libfork/fib/bump/no_await")->Arg(fib_test); +BENCHMARK(fib, linear_alloc>)->Name("base/libfork/fib/bump/no_await")->Arg(fib_base); // TODO: no_await with segmented stack allocator? // Return by ref-arg, libfork call/call with co-await, uses new/delete for alloc -BENCHMARK(fib>)->Name("test/libfork/fib/heap/await")->Arg(fib_test); -BENCHMARK(fib>)->Name("base/libfork/fib/heap/await")->Arg(fib_base); +BENCHMARK(fib, global_alloc>)->Name("test/libfork/fib/heap/await")->Arg(fib_test); +BENCHMARK(fib, global_alloc>)->Name("base/libfork/fib/heap/await")->Arg(fib_base); -// Same as above but uses tls bump allocator -BENCHMARK(fib>)->Name("test/libfork/fib/tls_bump/await")->Arg(fib_test); -BENCHMARK(fib>)->Name("base/libfork/fib/tls_bump/await")->Arg(fib_base); - -// Same as above but with global bump allocator -BENCHMARK(fib>)->Name("test/libfork/fib/global_bump/await")->Arg(fib_test); -BENCHMARK(fib>)->Name("base/libfork/fib/global_bump/await")->Arg(fib_base); +// // Same as above but uses bump allocator +BENCHMARK(fib, linear_alloc>)->Name("test/libfork/fib/bump/await")->Arg(fib_test); +BENCHMARK(fib, linear_alloc>)->Name("base/libfork/fib/bump/await")->Arg(fib_base); // Return by value // libfork call/call with co-await -BENCHMARK(fib)->Name("test/libfork/fib/tls_bump/return")->Arg(fib_test); -BENCHMARK(fib)->Name("base/libfork/fib/tls_bump/return")->Arg(fib_base); +BENCHMARK(fib, linear_alloc>)->Name("test/libfork/fib/bump/return")->Arg(fib_test); +BENCHMARK(fib, linear_alloc>)->Name("base/libfork/fib/bump/return")->Arg(fib_base); // Return by value // libfork call/fork (no join) // Non-polymorphic vector-backed context -BENCHMARK(fib>)->Name("test/libfork/fib/vector_ctx")->Arg(fib_test); -BENCHMARK(fib>)->Name("base/libfork/fib/vector_ctx")->Arg(fib_base); +BENCHMARK(fib, linear_alloc>)->Name("test/libfork/fib/vector_ctx")->Arg(fib_test); +BENCHMARK(fib, linear_alloc>)->Name("base/libfork/fib/vector_ctx")->Arg(fib_base); + +using A = poly_vector_ctx; +using B = lf::polymorphic_context; -BENCHMARK(fib>)->Name("test/libfork/fib/poly_vector_ctx")->Arg(fib_test); -BENCHMARK(fib>)->Name("base/libfork/fib/poly_vector_ctx")->Arg(fib_base); +// Same as above but with polymorphic contexts. +BENCHMARK(fib, A, B>)->Name("test/libfork/fib/poly_vector_ctx")->Arg(fib_test); +BENCHMARK(fib, A, B>)->Name("base/libfork/fib/poly_vector_ctx")->Arg(fib_base); diff --git a/src/core/concepts.cxx b/src/core/concepts.cxx index 918e1ebe..656cb823 100644 --- a/src/core/concepts.cxx +++ b/src/core/concepts.cxx @@ -5,6 +5,24 @@ import std; namespace lf { +// ========== Specialization ========== // + +template typename Template> +struct is_specialization_of : std::false_type {}; + +template