Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/libexpr-tests/tecnix-dependency-tracking.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ static bool containsPath(const std::vector<std::string> & paths, std::string_vie
static std::vector<std::string>
flattenFrame(const ref<EvalSourceAccessSetGraph> & graph, const TrackedSourceDepsFrame & frame)
{
std::vector<EvalSourceAccessId> direct(
frame.directSourceAccessSetAccesses.begin(), frame.directSourceAccessSetAccesses.end());
std::vector<EvalSourceAccessSetId> children(frame.childSourceAccessSets.begin(), frame.childSourceAccessSets.end());
auto frameAccesses = frame.directSourceAccessSetAccesses();
auto frameChildren = frame.childSourceAccessSets();
std::vector<EvalSourceAccessId> direct(frameAccesses.begin(), frameAccesses.end());
std::vector<EvalSourceAccessSetId> children(frameChildren.begin(), frameChildren.end());
return graph->flatten(direct, children);
}

Expand Down
34 changes: 33 additions & 1 deletion src/libexpr/include/nix/expr/tecnix/access-set-graph.hh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ struct EvalSourceAccessSetNode
uint64_t hash = 0;
};

/**
* One memoised `internAccessSet` *input*: the canonical (sorted, deduplicated)
* direct access ids and child set ids a frame published with, and the set id
* that input resolved to. Keyed on the input rather than on the flattened
* union, so a repeat publish never has to materialise the union at all.
*/
struct EvalSourceAccessSetInputNode
{
uint32_t first = 0;
uint32_t directCount = 0;
uint32_t childCount = 0;
uint32_t nextWithSameHash = 0;
uint64_t hash = 0;
EvalSourceAccessSetId accessSet = emptyEvalSourceAccessSetId;
};

class EvalSourceAccessSetGraph
{
std::atomic<bool> enabled{false};
Expand All @@ -43,11 +59,27 @@ class EvalSourceAccessSetGraph
std::vector<EvalSourceAccessId> accessSetItems;
std::vector<EvalSourceAccessSetId> singletonAccessSets;
boost::unordered_flat_map<uint64_t, EvalSourceAccessSetId> accessSetIdsByHash;
boost::unordered_flat_map<uint64_t, EvalSourceAccessSetId> pairUnionAccessSets;
std::vector<EvalSourceAccessSetInputNode> inputKeys;
std::vector<uint32_t> inputKeyItems;
boost::unordered_flat_map<uint64_t, uint32_t> inputKeyIdsByHash;
mutable std::vector<uint32_t> seenAccessGenerations;
mutable uint32_t nextFlattenGeneration = 1;

bool accessSetEquals(EvalSourceAccessSetId id, const std::vector<EvalSourceAccessId> & items) const;
bool inputKeyEquals(
uint32_t id,
std::span<const EvalSourceAccessId> directAccesses,
std::span<const EvalSourceAccessSetId> children) const;
EvalSourceAccessSetId lookupInputKey(
uint64_t hash,
std::span<const EvalSourceAccessId> directAccesses,
std::span<const EvalSourceAccessSetId> children,
bool & found) const;
void rememberInputKey(
uint64_t hash,
std::span<const EvalSourceAccessId> directAccesses,
std::span<const EvalSourceAccessSetId> children,
EvalSourceAccessSetId accessSet);

public:
EvalSourceAccessSetGraph();
Expand Down
37 changes: 30 additions & 7 deletions src/libexpr/include/nix/expr/tecnix/source-deps.hh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
#include "nix/util/pos-idx.hh"
#include "nix/util/ref.hh"

#include <boost/container/small_vector.hpp>

#include <cstddef>
#include <cstdint>
#include <span>
Expand Down Expand Up @@ -50,28 +48,47 @@ forceValueTracked(EvalState & state, Value & v, PosIdx pos, TrackingContext & tr

std::vector<std::string> parseGitPorcelainZDirtyPaths(std::string_view output);

using EvalSourceAccessIdFrameVector = boost::container::small_vector<EvalSourceAccessId, 1>;
using EvalSourceAccessSetIdFrameVector = boost::container::small_vector<EvalSourceAccessSetId, 2>;

/**
* A stack-resident accumulator for one bracketed region of evaluation: the
* force of one value (`value` set) or a source-deps scope / target root
* (`value` null). Collects direct path accesses and inherited child labels;
* interned into one set id when the region publishes.
*
* Frames are strictly LIFO within a tracking context, so their entries do not
* need per-frame containers: they live in two stacks owned by the context and
* a frame stores only the stack sizes at entry (its "watermarks"). A frame's
* entries are whatever sits above its marks.
*
* This makes the two operations that dominate tracked evaluation nearly free.
* Entering and leaving a region that records nothing -- the majority of forces
* -- costs two loads and two compares rather than constructing and destroying
* two containers. Merging an unpublished frame into its parent costs nothing
* at all, because the entries are already contiguous inside the parent's own
* region; leaving them in place *is* the merge. Publishing truncates back to
* the marks and pushes the single interned id in their place.
*
* The frame is trivially destructible, so it emits no destructor.
*/
struct TrackedSourceDepsFrame
{
TrackingContext & trackingCtx;
Value * value = nullptr;
EvalSourceAccessIdFrameVector directSourceAccessSetAccesses;
EvalSourceAccessSetIdFrameVector childSourceAccessSets;
/** Size of the context's access stack when this frame was entered. */
uint32_t accessBase = 0;
/** Size of the context's child stack when this frame was entered. */
uint32_t childBase = 0;
EvalSourceAccessSetId accessSet = emptyEvalSourceAccessSetId;
TrackedSourceDepsFrame * previous = nullptr;
TrackedSourceDepsFrame * nearestValueForceFrame = nullptr;
bool published = false;

TrackedSourceDepsFrame(
TrackingContext & trackingCtx, Value * value = nullptr, TrackedSourceDepsFrame * previous = nullptr);

/** The direct accesses recorded into this frame, as a view into the context stack. */
std::span<const EvalSourceAccessId> directSourceAccessSetAccesses() const;
/** The child labels recorded into this frame, as a view into the context stack. */
std::span<const EvalSourceAccessSetId> childSourceAccessSets() const;
};

/**
Expand All @@ -95,6 +112,12 @@ struct TrackedSourceDepsFrame
struct TrackingContext
{
ref<EvalSourceAccessSetGraph> sourceAccessSetGraph;
/**
* Backing storage for every frame in this context. Declared before
* `rootFrame` so they are constructed before it reads their sizes.
*/
std::vector<EvalSourceAccessId> frameAccessStack;
std::vector<EvalSourceAccessSetId> frameChildStack;
TrackedSourceDepsFrame rootFrame;

// Always captures the EvalState-owned source-access graph; no foreign graph constructor exists.
Expand Down
Loading
Loading