From 6b4a86a9e337b3b90bb02c6fcb01a986e70da074 Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Fri, 16 May 2025 21:53:06 +0000 Subject: [PATCH] libexpr: Add `EvalProfiler` This patch adds an EvalProfiler and MultiEvalProfiler that can be used to insert hooks into the evaluation for the purposes of function tracing (what function-trace currently does) or for flamegraph/tracy profilers. See the following commits for how this is supposed to be integrated into the evaluator and performance considerations. --- src/libexpr/eval-profiler.cc | 48 ++++++++ src/libexpr/include/nix/expr/eval-profiler.hh | 113 ++++++++++++++++++ .../include/nix/expr/function-trace.hh | 1 + src/libexpr/include/nix/expr/meson.build | 1 + src/libexpr/meson.build | 1 + 5 files changed, 164 insertions(+) create mode 100644 src/libexpr/eval-profiler.cc create mode 100644 src/libexpr/include/nix/expr/eval-profiler.hh diff --git a/src/libexpr/eval-profiler.cc b/src/libexpr/eval-profiler.cc new file mode 100644 index 000000000..5374d7d99 --- /dev/null +++ b/src/libexpr/eval-profiler.cc @@ -0,0 +1,48 @@ +#include "nix/expr/eval-profiler.hh" +#include "nix/expr/nixexpr.hh" + +namespace nix { + +void EvalProfiler::preFunctionCallHook( + const EvalState & state, const Value & v, std::span args, const PosIdx pos) +{ +} + +void EvalProfiler::postFunctionCallHook( + const EvalState & state, const Value & v, std::span args, const PosIdx pos) +{ +} + +void MultiEvalProfiler::preFunctionCallHook( + const EvalState & state, const Value & v, std::span args, const PosIdx pos) +{ + for (auto & profiler : profilers) { + if (profiler->getNeededHooks().test(Hook::preFunctionCall)) + profiler->preFunctionCallHook(state, v, args, pos); + } +} + +void MultiEvalProfiler::postFunctionCallHook( + const EvalState & state, const Value & v, std::span args, const PosIdx pos) +{ + for (auto & profiler : profilers) { + if (profiler->getNeededHooks().test(Hook::postFunctionCall)) + profiler->postFunctionCallHook(state, v, args, pos); + } +} + +EvalProfiler::Hooks MultiEvalProfiler::getNeededHooksImpl() const +{ + Hooks hooks; + for (auto & p : profilers) + hooks |= p->getNeededHooks(); + return hooks; +} + +void MultiEvalProfiler::addProfiler(ref profiler) +{ + profilers.push_back(profiler); + invalidateNeededHooks(); +} + +} diff --git a/src/libexpr/include/nix/expr/eval-profiler.hh b/src/libexpr/include/nix/expr/eval-profiler.hh new file mode 100644 index 000000000..763b737f7 --- /dev/null +++ b/src/libexpr/include/nix/expr/eval-profiler.hh @@ -0,0 +1,113 @@ +#pragma once +/** + * @file + * + * Evaluation profiler interface definitions and builtin implementations. + */ + +#include "nix/util/ref.hh" + +#include +#include +#include +#include + +namespace nix { + +class EvalState; +class PosIdx; +struct Value; + +class EvalProfiler +{ +public: + enum Hook { + preFunctionCall, + postFunctionCall, + }; + + static constexpr std::size_t numHooks = Hook::postFunctionCall + 1; + using Hooks = std::bitset; + +private: + std::optional neededHooks; + +protected: + /** Invalidate the cached neededHooks. */ + void invalidateNeededHooks() + { + neededHooks = std::nullopt; + } + + /** + * Get which hooks need to be called. + * + * This is the actual implementation which has to be defined by subclasses. + * Public API goes through the needsHooks, which is a + * non-virtual interface (NVI) which caches the return value. + */ + virtual Hooks getNeededHooksImpl() const + { + return Hooks{}; + } + +public: + /** + * Hook called in the EvalState::callFunction preamble. + * Gets called only if (getNeededHooks().test(Hook::preFunctionCall)) is true. + * + * @param state Evaluator state. + * @param v Function being invoked. + * @param args Function arguments. + * @param pos Function position. + */ + virtual void + preFunctionCallHook(const EvalState & state, const Value & v, std::span args, const PosIdx pos); + + /** + * Hook called on EvalState::callFunction exit. + * Gets called only if (getNeededHooks().test(Hook::postFunctionCall)) is true. + * + * @param state Evaluator state. + * @param v Function being invoked. + * @param args Function arguments. + * @param pos Function position. + */ + virtual void + postFunctionCallHook(const EvalState & state, const Value & v, std::span args, const PosIdx pos); + + virtual ~EvalProfiler() = default; + + /** + * Get which hooks need to be invoked for this EvalProfiler instance. + */ + Hooks getNeededHooks() + { + if (neededHooks.has_value()) + return *neededHooks; + return *(neededHooks = getNeededHooksImpl()); + } +}; + +/** + * Profiler that invokes multiple profilers at once. + */ +class MultiEvalProfiler : public EvalProfiler +{ + std::vector> profilers; + + [[gnu::noinline]] Hooks getNeededHooksImpl() const override; + +public: + MultiEvalProfiler() = default; + + /** Register a profiler instance. */ + void addProfiler(ref profiler); + + [[gnu::noinline]] void + preFunctionCallHook(const EvalState & state, const Value & v, std::span args, const PosIdx pos) override; + [[gnu::noinline]] void + postFunctionCallHook(const EvalState & state, const Value & v, std::span args, const PosIdx pos) override; +}; + +} diff --git a/src/libexpr/include/nix/expr/function-trace.hh b/src/libexpr/include/nix/expr/function-trace.hh index dc92d4b5c..f9b82d8a5 100644 --- a/src/libexpr/include/nix/expr/function-trace.hh +++ b/src/libexpr/include/nix/expr/function-trace.hh @@ -2,6 +2,7 @@ ///@file #include "nix/expr/eval.hh" +#include "nix/expr/eval-profiler.hh" #include diff --git a/src/libexpr/include/nix/expr/meson.build b/src/libexpr/include/nix/expr/meson.build index 50ea8f3c2..db902a616 100644 --- a/src/libexpr/include/nix/expr/meson.build +++ b/src/libexpr/include/nix/expr/meson.build @@ -14,6 +14,7 @@ headers = [config_pub_h] + files( 'eval-error.hh', 'eval-gc.hh', 'eval-inline.hh', + 'eval-profiler.hh', 'eval-settings.hh', 'eval.hh', 'function-trace.hh', diff --git a/src/libexpr/meson.build b/src/libexpr/meson.build index 2b465b85a..dc50d2b19 100644 --- a/src/libexpr/meson.build +++ b/src/libexpr/meson.build @@ -140,6 +140,7 @@ sources = files( 'eval-cache.cc', 'eval-error.cc', 'eval-gc.cc', + 'eval-profiler.cc', 'eval-settings.cc', 'eval.cc', 'function-trace.cc',