|
38 | 38 |
|
39 | 39 | #include <nlohmann/json.hpp> |
40 | 40 | #include <boost/container/small_vector.hpp> |
| 41 | +#include <boost/unordered/concurrent_flat_map.hpp> |
41 | 42 |
|
42 | 43 | #include "nix/util/strings-inline.hh" |
43 | 44 |
|
@@ -192,6 +193,27 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env) |
192 | 193 |
|
193 | 194 | static constexpr size_t BASE_ENV_SIZE = 128; |
194 | 195 |
|
| 196 | +struct EvalState::SrcToStore |
| 197 | +{ |
| 198 | + boost::concurrent_flat_map<SourcePath, StorePath> inner; |
| 199 | +}; |
| 200 | + |
| 201 | +struct EvalState::ImportResolutionCache |
| 202 | +{ |
| 203 | + boost::concurrent_flat_map<SourcePath, SourcePath> inner; |
| 204 | +}; |
| 205 | + |
| 206 | +struct EvalState::FileEvalCache |
| 207 | +{ |
| 208 | + boost::concurrent_flat_map< |
| 209 | + SourcePath, |
| 210 | + Value *, |
| 211 | + std::hash<SourcePath>, |
| 212 | + std::equal_to<SourcePath>, |
| 213 | + traceable_allocator<std::pair<const SourcePath, Value *>>> |
| 214 | + inner; |
| 215 | +}; |
| 216 | + |
195 | 217 | EvalState::EvalState( |
196 | 218 | const LookupPath & lookupPathFromArguments, |
197 | 219 | ref<Store> store, |
@@ -264,6 +286,9 @@ EvalState::EvalState( |
264 | 286 | , debugRepl(nullptr) |
265 | 287 | , debugStop(false) |
266 | 288 | , trylevel(0) |
| 289 | + , srcToStore(make_ref<SrcToStore>()) |
| 290 | + , importResolutionCache(make_ref<ImportResolutionCache>()) |
| 291 | + , fileEvalCache(make_ref<FileEvalCache>()) |
267 | 292 | , regexCache(makeRegexCache()) |
268 | 293 | #if NIX_USE_BOEHMGC |
269 | 294 | , valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr)) |
@@ -1031,63 +1056,85 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env) |
1031 | 1056 | return &v; |
1032 | 1057 | } |
1033 | 1058 |
|
1034 | | -void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial) |
| 1059 | +/** |
| 1060 | + * A helper `Expr` class to lets us parse and evaluate Nix expressions |
| 1061 | + * from a thunk, ensuring that every file is parsed/evaluated only |
| 1062 | + * once (via the thunk stored in `EvalState::fileEvalCache`). |
| 1063 | + */ |
| 1064 | +struct ExprParseFile : Expr |
1035 | 1065 | { |
1036 | | - FileEvalCache::iterator i; |
1037 | | - if ((i = fileEvalCache.find(path)) != fileEvalCache.end()) { |
1038 | | - v = i->second; |
1039 | | - return; |
1040 | | - } |
| 1066 | + SourcePath & path; |
| 1067 | + bool mustBeTrivial; |
1041 | 1068 |
|
1042 | | - auto resolvedPath = resolveExprPath(path); |
1043 | | - if ((i = fileEvalCache.find(resolvedPath)) != fileEvalCache.end()) { |
1044 | | - v = i->second; |
1045 | | - return; |
| 1069 | + ExprParseFile(SourcePath & path, bool mustBeTrivial) |
| 1070 | + : path(path) |
| 1071 | + , mustBeTrivial(mustBeTrivial) |
| 1072 | + { |
1046 | 1073 | } |
1047 | 1074 |
|
1048 | | - printTalkative("evaluating file '%1%'", resolvedPath); |
1049 | | - Expr * e = nullptr; |
| 1075 | + void eval(EvalState & state, Env & env, Value & v) override |
| 1076 | + { |
| 1077 | + printTalkative("evaluating file '%s'", path); |
| 1078 | + |
| 1079 | + auto e = state.parseExprFromFile(path); |
1050 | 1080 |
|
1051 | | - auto j = fileParseCache.find(resolvedPath); |
1052 | | - if (j != fileParseCache.end()) |
1053 | | - e = j->second; |
| 1081 | + try { |
| 1082 | + auto dts = |
| 1083 | + state.debugRepl |
| 1084 | + ? makeDebugTraceStacker( |
| 1085 | + state, *e, state.baseEnv, e->getPos(), "while evaluating the file '%s':", path.to_string()) |
| 1086 | + : nullptr; |
| 1087 | + |
| 1088 | + // Enforce that 'flake.nix' is a direct attrset, not a |
| 1089 | + // computation. |
| 1090 | + if (mustBeTrivial && !(dynamic_cast<ExprAttrs *>(e))) |
| 1091 | + state.error<EvalError>("file '%s' must be an attribute set", path).debugThrow(); |
| 1092 | + |
| 1093 | + state.eval(e, v); |
| 1094 | + } catch (Error & e) { |
| 1095 | + state.addErrorTrace(e, "while evaluating the file '%s':", path.to_string()); |
| 1096 | + throw; |
| 1097 | + } |
| 1098 | + } |
| 1099 | +}; |
1054 | 1100 |
|
1055 | | - if (!e) |
1056 | | - e = parseExprFromFile(resolvedPath); |
| 1101 | +void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial) |
| 1102 | +{ |
| 1103 | + auto resolvedPath = getConcurrent(importResolutionCache->inner, path); |
1057 | 1104 |
|
1058 | | - fileParseCache.emplace(resolvedPath, e); |
| 1105 | + if (!resolvedPath) { |
| 1106 | + resolvedPath = resolveExprPath(path); |
| 1107 | + importResolutionCache->inner.emplace(path, *resolvedPath); |
| 1108 | + } |
1059 | 1109 |
|
1060 | | - try { |
1061 | | - auto dts = debugRepl ? makeDebugTraceStacker( |
1062 | | - *this, |
1063 | | - *e, |
1064 | | - this->baseEnv, |
1065 | | - e->getPos(), |
1066 | | - "while evaluating the file '%1%':", |
1067 | | - resolvedPath.to_string()) |
1068 | | - : nullptr; |
1069 | | - |
1070 | | - // Enforce that 'flake.nix' is a direct attrset, not a |
1071 | | - // computation. |
1072 | | - if (mustBeTrivial && !(dynamic_cast<ExprAttrs *>(e))) |
1073 | | - error<EvalError>("file '%s' must be an attribute set", path).debugThrow(); |
1074 | | - eval(e, v); |
1075 | | - } catch (Error & e) { |
1076 | | - addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string()); |
1077 | | - throw; |
| 1110 | + if (auto v2 = getConcurrent(fileEvalCache->inner, *resolvedPath)) { |
| 1111 | + forceValue(**v2, noPos); |
| 1112 | + v = **v2; |
| 1113 | + return; |
1078 | 1114 | } |
1079 | 1115 |
|
1080 | | - fileEvalCache.emplace(resolvedPath, v); |
1081 | | - if (path != resolvedPath) |
1082 | | - fileEvalCache.emplace(path, v); |
| 1116 | + Value * vExpr; |
| 1117 | + ExprParseFile expr{*resolvedPath, mustBeTrivial}; |
| 1118 | + |
| 1119 | + fileEvalCache->inner.try_emplace_and_cvisit( |
| 1120 | + *resolvedPath, |
| 1121 | + nullptr, |
| 1122 | + [&](auto & i) { |
| 1123 | + vExpr = allocValue(); |
| 1124 | + vExpr->mkThunk(&baseEnv, &expr); |
| 1125 | + i.second = vExpr; |
| 1126 | + }, |
| 1127 | + [&](auto & i) { vExpr = i.second; }); |
| 1128 | + |
| 1129 | + forceValue(*vExpr, noPos); |
| 1130 | + |
| 1131 | + v = *vExpr; |
1083 | 1132 | } |
1084 | 1133 |
|
1085 | 1134 | void EvalState::resetFileCache() |
1086 | 1135 | { |
1087 | | - fileEvalCache.clear(); |
1088 | | - fileEvalCache.rehash(0); |
1089 | | - fileParseCache.clear(); |
1090 | | - fileParseCache.rehash(0); |
| 1136 | + fileEvalCache->inner.clear(); |
| 1137 | + fileEvalCache->inner.rehash(0); |
1091 | 1138 | inputCache->clear(); |
1092 | 1139 | } |
1093 | 1140 |
|
@@ -2372,24 +2419,26 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat |
2372 | 2419 | if (nix::isDerivation(path.path.abs())) |
2373 | 2420 | error<EvalError>("file names are not allowed to end in '%1%'", drvExtension).debugThrow(); |
2374 | 2421 |
|
2375 | | - std::optional<StorePath> dstPath; |
2376 | | - if (!srcToStore.cvisit(path, [&dstPath](const auto & kv) { dstPath.emplace(kv.second); })) { |
2377 | | - dstPath.emplace(fetchToStore( |
| 2422 | + auto dstPathCached = getConcurrent(srcToStore->inner, path); |
| 2423 | + |
| 2424 | + auto dstPath = dstPathCached ? *dstPathCached : [&]() { |
| 2425 | + auto dstPath = fetchToStore( |
2378 | 2426 | fetchSettings, |
2379 | 2427 | *store, |
2380 | 2428 | path.resolveSymlinks(SymlinkResolution::Ancestors), |
2381 | 2429 | settings.readOnlyMode ? FetchMode::DryRun : FetchMode::Copy, |
2382 | 2430 | path.baseName(), |
2383 | 2431 | ContentAddressMethod::Raw::NixArchive, |
2384 | 2432 | nullptr, |
2385 | | - repair)); |
2386 | | - allowPath(*dstPath); |
2387 | | - srcToStore.try_emplace(path, *dstPath); |
2388 | | - printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(*dstPath)); |
2389 | | - } |
| 2433 | + repair); |
| 2434 | + allowPath(dstPath); |
| 2435 | + srcToStore->inner.try_emplace(path, dstPath); |
| 2436 | + printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath)); |
| 2437 | + return dstPath; |
| 2438 | + }(); |
2390 | 2439 |
|
2391 | | - context.insert(NixStringContextElem::Opaque{.path = *dstPath}); |
2392 | | - return *dstPath; |
| 2440 | + context.insert(NixStringContextElem::Opaque{.path = dstPath}); |
| 2441 | + return dstPath; |
2393 | 2442 | } |
2394 | 2443 |
|
2395 | 2444 | SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx) |
|
0 commit comments