diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 4fe9e9e3a..5629865f0 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -38,6 +38,7 @@ #include #include +#include #include "nix/util/strings-inline.hh" @@ -192,6 +193,27 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env) static constexpr size_t BASE_ENV_SIZE = 128; +struct EvalState::SrcToStore +{ + boost::concurrent_flat_map inner; +}; + +struct EvalState::ImportResolutionCache +{ + boost::concurrent_flat_map inner; +}; + +struct EvalState::FileEvalCache +{ + boost::concurrent_flat_map< + SourcePath, + Value *, + std::hash, + std::equal_to, + traceable_allocator>> + inner; +}; + EvalState::EvalState( const LookupPath & lookupPathFromArguments, ref store, @@ -264,6 +286,9 @@ EvalState::EvalState( , debugRepl(nullptr) , debugStop(false) , trylevel(0) + , srcToStore(make_ref()) + , importResolutionCache(make_ref()) + , fileEvalCache(make_ref()) , regexCache(makeRegexCache()) #if NIX_USE_BOEHMGC , valueAllocCache(std::allocate_shared(traceable_allocator(), nullptr)) @@ -1031,63 +1056,85 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env) return &v; } +/** + * A helper `Expr` class to lets us parse and evaluate Nix expressions + * from a thunk, ensuring that every file is parsed/evaluated only + * once (via the thunk stored in `EvalState::fileEvalCache`). + */ +struct ExprParseFile : Expr +{ + SourcePath & path; + bool mustBeTrivial; + + ExprParseFile(SourcePath & path, bool mustBeTrivial) + : path(path) + , mustBeTrivial(mustBeTrivial) + { + } + + void eval(EvalState & state, Env & env, Value & v) override + { + printTalkative("evaluating file '%s'", path); + + auto e = state.parseExprFromFile(path); + + try { + auto dts = + state.debugRepl + ? makeDebugTraceStacker( + state, *e, state.baseEnv, e->getPos(), "while evaluating the file '%s':", path.to_string()) + : nullptr; + + // Enforce that 'flake.nix' is a direct attrset, not a + // computation. + if (mustBeTrivial && !(dynamic_cast(e))) + state.error("file '%s' must be an attribute set", path).debugThrow(); + + state.eval(e, v); + } catch (Error & e) { + state.addErrorTrace(e, "while evaluating the file '%s':", path.to_string()); + throw; + } + } +}; + void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial) { - FileEvalCache::iterator i; - if ((i = fileEvalCache.find(path)) != fileEvalCache.end()) { - v = i->second; + auto resolvedPath = getConcurrent(importResolutionCache->inner, path); + + if (!resolvedPath) { + resolvedPath = resolveExprPath(path); + importResolutionCache->inner.emplace(path, *resolvedPath); + } + + if (auto v2 = getConcurrent(fileEvalCache->inner, *resolvedPath)) { + forceValue(**v2, noPos); + v = **v2; return; } - auto resolvedPath = resolveExprPath(path); - if ((i = fileEvalCache.find(resolvedPath)) != fileEvalCache.end()) { - v = i->second; - return; - } + Value * vExpr; + ExprParseFile expr{*resolvedPath, mustBeTrivial}; - printTalkative("evaluating file '%1%'", resolvedPath); - Expr * e = nullptr; + fileEvalCache->inner.try_emplace_and_cvisit( + *resolvedPath, + nullptr, + [&](auto & i) { + vExpr = allocValue(); + vExpr->mkThunk(&baseEnv, &expr); + i.second = vExpr; + }, + [&](auto & i) { vExpr = i.second; }); - auto j = fileParseCache.find(resolvedPath); - if (j != fileParseCache.end()) - e = j->second; + forceValue(*vExpr, noPos); - if (!e) - e = parseExprFromFile(resolvedPath); - - fileParseCache.emplace(resolvedPath, e); - - try { - auto dts = debugRepl ? makeDebugTraceStacker( - *this, - *e, - this->baseEnv, - e->getPos(), - "while evaluating the file '%1%':", - resolvedPath.to_string()) - : nullptr; - - // Enforce that 'flake.nix' is a direct attrset, not a - // computation. - if (mustBeTrivial && !(dynamic_cast(e))) - error("file '%s' must be an attribute set", path).debugThrow(); - eval(e, v); - } catch (Error & e) { - addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string()); - throw; - } - - fileEvalCache.emplace(resolvedPath, v); - if (path != resolvedPath) - fileEvalCache.emplace(path, v); + v = *vExpr; } void EvalState::resetFileCache() { - fileEvalCache.clear(); - fileEvalCache.rehash(0); - fileParseCache.clear(); - fileParseCache.rehash(0); + fileEvalCache->inner.clear(); + fileEvalCache->inner.rehash(0); inputCache->clear(); } @@ -2372,9 +2419,10 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat if (nix::isDerivation(path.path.abs())) error("file names are not allowed to end in '%1%'", drvExtension).debugThrow(); - std::optional dstPath; - if (!srcToStore.cvisit(path, [&dstPath](const auto & kv) { dstPath.emplace(kv.second); })) { - dstPath.emplace(fetchToStore( + auto dstPathCached = getConcurrent(srcToStore->inner, path); + + auto dstPath = dstPathCached ? *dstPathCached : [&]() { + auto dstPath = fetchToStore( fetchSettings, *store, path.resolveSymlinks(SymlinkResolution::Ancestors), @@ -2382,14 +2430,15 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat path.baseName(), ContentAddressMethod::Raw::NixArchive, nullptr, - repair)); - allowPath(*dstPath); - srcToStore.try_emplace(path, *dstPath); - printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(*dstPath)); - } + repair); + allowPath(dstPath); + srcToStore->inner.try_emplace(path, dstPath); + printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath)); + return dstPath; + }(); - context.insert(NixStringContextElem::Opaque{.path = *dstPath}); - return *dstPath; + context.insert(NixStringContextElem::Opaque{.path = dstPath}); + return dstPath; } SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx) diff --git a/src/libexpr/include/nix/expr/eval.hh b/src/libexpr/include/nix/expr/eval.hh index 9e0638de8..4b294ad9a 100644 --- a/src/libexpr/include/nix/expr/eval.hh +++ b/src/libexpr/include/nix/expr/eval.hh @@ -20,7 +20,6 @@ // For `NIX_USE_BOEHMGC`, and if that's set, `GC_THREADS` #include "nix/expr/config.hh" -#include #include #include #include @@ -412,31 +411,21 @@ private: /* Cache for calls to addToStore(); maps source paths to the store paths. */ - boost::concurrent_flat_map> srcToStore; + struct SrcToStore; + ref srcToStore; /** - * A cache from path names to parse trees. + * A cache that maps paths to "resolved" paths for importing Nix + * expressions, i.e. `/foo` to `/foo/default.nix`. */ - typedef boost::unordered_flat_map< - SourcePath, - Expr *, - std::hash, - std::equal_to, - traceable_allocator>> - FileParseCache; - FileParseCache fileParseCache; + struct ImportResolutionCache; + ref importResolutionCache; /** - * A cache from path names to values. + * A cache from resolved paths to values. */ - typedef boost::unordered_flat_map< - SourcePath, - Value, - std::hash, - std::equal_to, - traceable_allocator>> - FileEvalCache; - FileEvalCache fileEvalCache; + struct FileEvalCache; + ref fileEvalCache; /** * Associate source positions of certain AST nodes with their preceding doc comment, if they have one.