From 1e064e08fa18499be0fc985b55c46c2871fafa37 Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Wed, 27 Nov 2024 02:09:08 +0100 Subject: [PATCH] libexpr: move builtin env to new class Change-Id: Ib76891372be756917bddcf5eaa24ccf8e1288035 --- lix/legacy/nix-env.cc | 2 +- lix/libcmd/repl.cc | 10 +- lix/libexpr/builtin-constants/builtins.md | 2 +- lix/libexpr/builtin-constants/nixPath.md | 2 +- lix/libexpr/builtin-constants/storeDir.md | 2 +- lix/libexpr/eval.cc | 76 ++++++++----- lix/libexpr/eval.hh | 127 +++++++++++++--------- lix/libexpr/primops.cc | 29 ++--- lix/nix/main.cc | 6 +- 9 files changed, 146 insertions(+), 110 deletions(-) diff --git a/lix/legacy/nix-env.cc b/lix/legacy/nix-env.cc index 78e67a58c..fd0cd5a15 100644 --- a/lix/legacy/nix-env.cc +++ b/lix/legacy/nix-env.cc @@ -148,7 +148,7 @@ static void getAllExprs(EvalState & state, vArg->mkString(path2.path.abs()); if (seen.size() == maxAttrs) throw Error("too many Nix expressions in directory '%1%'", path); - attrs.alloc(attrName).mkApp(&state.getBuiltin("import"), vArg); + attrs.alloc(attrName).mkApp(&state.builtins.get("import"), vArg); } else if (st.type == InputAccessor::tDirectory) /* `path2' is a directory (with no default.nix in it); diff --git a/lix/libcmd/repl.cc b/lix/libcmd/repl.cc index f8f994479..92a1bf226 100644 --- a/lix/libcmd/repl.cc +++ b/lix/libcmd/repl.cc @@ -237,7 +237,7 @@ NixRepl::NixRepl(const SearchPath & searchPath, nix::ref store, EvalState : AbstractNixRepl(state) , debugTraceIndex(0) , getValues(getValues) - , staticEnv(new StaticEnv(nullptr, state.staticBaseEnv.get())) + , staticEnv(new StaticEnv(nullptr, state.builtins.staticEnv.get())) , interacter(makeInteracter()) { } @@ -784,7 +784,7 @@ ProcessLineResult NixRepl::processLine(std::string line) else if (command == ":doc") { Value v; evalString(arg, v); - if (auto doc = state.getDoc(v)) { + if (auto doc = state.builtins.getDoc(v)) { std::string markdown; if (!doc->args.empty() && doc->name) { @@ -894,12 +894,12 @@ void NixRepl::loadFlake(const std::string & flakeRefS) void NixRepl::initEnv() { env = &state.mem.allocEnv(envSize); - env->up = &state.baseEnv; + env->up = &state.builtins.env; displ = 0; staticEnv->vars.clear(); varNames.clear(); - for (auto & i : state.staticBaseEnv->vars) + for (auto & i : state.builtins.staticEnv->vars) varNames.emplace(state.symbols[i.first]); } @@ -971,7 +971,7 @@ Value * NixRepl::getReplOverlaysEvalFunction() auto & expr = state.parseExprFromString( code, SourcePath(evalReplInitFilesPath), - state.staticBaseEnv + state.builtins.staticEnv ); state.eval(expr, **replOverlaysEvalFunction); diff --git a/lix/libexpr/builtin-constants/builtins.md b/lix/libexpr/builtin-constants/builtins.md index 4a46cad27..71f9bd2ba 100644 --- a/lix/libexpr/builtin-constants/builtins.md +++ b/lix/libexpr/builtin-constants/builtins.md @@ -1,7 +1,7 @@ --- name: builtins type: attrs -constructorArgs: [buildBindings(128).finish()] +constructorArgs: [mem.buildBindings(symbols, 128).finish()] renameInGlobalScope: false --- Contains all the [built-in functions](@docroot@/language/builtins.md) and values. diff --git a/lix/libexpr/builtin-constants/nixPath.md b/lix/libexpr/builtin-constants/nixPath.md index 8b88d99ca..9ec51fa55 100644 --- a/lix/libexpr/builtin-constants/nixPath.md +++ b/lix/libexpr/builtin-constants/nixPath.md @@ -1,7 +1,7 @@ --- name: nixPath type: list -implementation: getNixPath(*this, searchPath) +implementation: prepareNixPath(searchPath) --- The search path used to resolve angle bracket path lookups. diff --git a/lix/libexpr/builtin-constants/storeDir.md b/lix/libexpr/builtin-constants/storeDir.md index ab4800cf0..1cbd82785 100644 --- a/lix/libexpr/builtin-constants/storeDir.md +++ b/lix/libexpr/builtin-constants/storeDir.md @@ -1,7 +1,7 @@ --- name: storeDir type: string -constructorArgs: [store->config().storeDir] +constructorArgs: [storeDir] --- Logical file system location of the [Nix store](@docroot@/glossary.md#gloss-store) currently in use. diff --git a/lix/libexpr/eval.cc b/lix/libexpr/eval.cc index 42649c98d..1dd820244 100644 --- a/lix/libexpr/eval.cc +++ b/lix/libexpr/eval.cc @@ -262,30 +262,46 @@ EvalMemory::EvalMemory() assert(libexprInitialised); } +EvalBuiltins::EvalBuiltins( + EvalMemory & mem, + SymbolTable & symbols, + const SearchPath & searchPath, + const Path & storeDir, + size_t size +) + : mem(mem) + , symbols(symbols) + , env(mem.allocEnv(size)) + , staticEnv{std::make_shared(nullptr, nullptr)} +{ + createBaseEnv(searchPath, storeDir); +} + EvalState::EvalState( const SearchPath & _searchPath, ref store, std::shared_ptr buildStore) : s(symbols) + , searchPath([&] { + SearchPath searchPath; + if (!evalSettings.pureEval) { + for (auto & i : _searchPath.elements) + searchPath.elements.emplace_back(SearchPath::Elem {i}); + for (auto & i : evalSettings.nixPath.get()) + searchPath.elements.emplace_back(SearchPath::Elem::parse(i)); + } + return searchPath; + }()) + , builtins(mem, symbols, searchPath, store->config().storeDir) , repair(NoRepair) , store(store) , buildStore(buildStore ? buildStore : store) , regexCache(makeRegexCache()) - , baseEnv(mem.allocEnv(128)) - , staticBaseEnv{std::make_shared(nullptr, nullptr)} { countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0"; static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes"); - /* Initialise the Nix expression search path. */ - if (!evalSettings.pureEval) { - for (auto & i : _searchPath.elements) - searchPath.elements.emplace_back(SearchPath::Elem {i}); - for (auto & i : evalSettings.nixPath.get()) - searchPath.elements.emplace_back(SearchPath::Elem::parse(i)); - } - if (evalSettings.restrictEval || evalSettings.pureEval) { allowedPaths = std::optional(PathSet()); @@ -308,8 +324,6 @@ EvalState::EvalState( allowPath(path); } } - - createBaseEnv(); } @@ -426,7 +440,7 @@ Path EvalState::toRealPath(const Path & path, const NixStringContext & context) } -Value * EvalState::addConstant(const std::string & name, const Value & v, Constant info) +Value * EvalBuiltins::addConstant(const std::string & name, const Value & v, Constant info) { Value * v2 = mem.allocValue(); *v2 = v; @@ -435,7 +449,7 @@ Value * EvalState::addConstant(const std::string & name, const Value & v, Consta } -void EvalState::addConstant(const std::string & name, Value * v, Constant info) +void EvalBuiltins::addConstant(const std::string & name, Value * v, Constant info) { auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name; @@ -450,9 +464,9 @@ void EvalState::addConstant(const std::string & name, Value * v, Constant info) assert(info.type == gotType); /* Install value the base environment. */ - staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl); - baseEnv.values[baseEnvDispl++] = v; - baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v)); + staticEnv->vars.emplace_back(symbols.create(name), baseEnvDispl); + env.values[baseEnvDispl++] = v; + env.values[0]->attrs->push_back(Attr(symbols.create(name2), v)); } } @@ -463,7 +477,7 @@ std::ostream & operator<<(std::ostream & output, PrimOp & primOp) return output; } -Value * EvalState::addPrimOp(PrimOp && primOp) +Value * EvalBuiltins::addPrimOp(PrimOp && primOp) { /* Hack to make constants lazy: turn them into a application of the primop to a dummy value. */ @@ -485,20 +499,20 @@ Value * EvalState::addPrimOp(PrimOp && primOp) Value * v = mem.allocValue(); v->mkPrimOp(new PrimOp(primOp)); - staticBaseEnv->vars.emplace_back(envName, baseEnvDispl); - baseEnv.values[baseEnvDispl++] = v; - baseEnv.values[0]->attrs->push_back(Attr(symbols.create(primOp.name), v)); + staticEnv->vars.emplace_back(envName, baseEnvDispl); + env.values[baseEnvDispl++] = v; + env.values[0]->attrs->push_back(Attr(symbols.create(primOp.name), v)); return v; } -Value & EvalState::getBuiltin(const std::string & name) +Value & EvalBuiltins::get(const std::string & name) { - return *baseEnv.values[0]->attrs->find(symbols.create(name))->value; + return *env.values[0]->attrs->find(symbols.create(name))->value; } -std::optional EvalState::getDoc(Value & v) +std::optional EvalBuiltins::getDoc(Value & v) { if (v.isPrimOp()) { auto v2 = &v; @@ -764,7 +778,7 @@ static inline void mkThunk(Value & v, Env & env, Expr & expr) void EvalState::mkThunk_(Value & v, Expr & expr) { - mkThunk(v, baseEnv, expr); + mkThunk(v, builtins.env, expr); } @@ -927,7 +941,7 @@ void EvalState::evalFile(const SourcePath & path_, Value & v) ? makeDebugTraceStacker( *this, e, - this->baseEnv, + this->builtins.env, e.getPos() ? std::make_shared(positions[e.getPos()]) : nullptr, "while evaluating the file '%1%':", resolvedPath.to_string()) : nullptr; @@ -951,7 +965,7 @@ void EvalState::resetFileCache() void EvalState::eval(Expr & e, Value & v) { - e.eval(*this, baseEnv, v); + e.eval(*this, builtins.env, v); } @@ -2667,7 +2681,7 @@ SourcePath resolveExprPath(SourcePath path) Expr & EvalState::parseExprFromFile(const SourcePath & path) { - return parseExprFromFile(path, staticBaseEnv); + return parseExprFromFile(path, builtins.staticEnv); } @@ -2696,7 +2710,7 @@ Expr & EvalState::parseExprFromString( const FeatureSettings & featureSettings ) { - return parseExprFromString(std::move(s), basePath, staticBaseEnv, featureSettings); + return parseExprFromString(std::move(s), basePath, builtins.staticEnv, featureSettings); } @@ -2704,7 +2718,9 @@ Expr & EvalState::parseStdin() { //Activity act(*logger, lvlTalkative, "parsing standard input"); auto s = make_ref(drainFD(0)); - return *parse(s->data(), s->size(), Pos::Stdin{.source = s}, CanonPath::fromCwd(), staticBaseEnv); + return *parse( + s->data(), s->size(), Pos::Stdin{.source = s}, CanonPath::fromCwd(), builtins.staticEnv + ); } diff --git a/lix/libexpr/eval.hh b/lix/libexpr/eval.hh index c5beebdbe..def78977d 100644 --- a/lix/libexpr/eval.hh +++ b/lix/libexpr/eval.hh @@ -263,15 +263,88 @@ private: Statistics stats; }; +class EvalBuiltins +{ + EvalMemory & mem; + SymbolTable & symbols; + +public: + explicit EvalBuiltins( + EvalMemory & mem, + SymbolTable & symbols, + const SearchPath & searchPath, + const Path & storeDir, + size_t size = 128 + ); + + /** + * The base environment, containing the builtin functions and + * values. + */ + Env & env; + + /** + * The same, but used during parsing to resolve variables. + */ + std::shared_ptr staticEnv; // !!! should be private + + /** + * Name and documentation about every constant. + * + * Constants from primops are hard to crawl, and their docs will go + * here too. + */ + std::vector> constantInfos; + +private: + unsigned int baseEnvDispl = 0; + + void createBaseEnv(const SearchPath & searchPath, const Path & storeDir); + + Value * addConstant(const std::string & name, const Value & v, Constant info); + + void addConstant(const std::string & name, Value * v, Constant info); + + Value * addPrimOp(PrimOp && primOp); + + Value prepareNixPath(const SearchPath & searchPath); + +public: + Value & get(const std::string & name); + + struct Doc + { + Pos pos; + std::optional name; + size_t arity; + std::vector args; + /** + * Unlike the other `doc` fields in this file, this one should never be + * `null`. + */ + const char * doc; + }; + + std::optional getDoc(Value & v); +}; + class EvalState { + friend class EvalBuiltins; + public: SymbolTable symbols; PosTable positions; const StaticSymbols s; EvalMemory mem; +private: + SearchPath searchPath; + +public: + EvalBuiltins builtins; + /** * If set, force copying files to the Nix store even if they * already exist there. @@ -318,8 +391,6 @@ private: using FileEvalCache = GcMap; FileEvalCache fileEvalCache; - SearchPath searchPath; - std::map> searchPathResolved; /** @@ -529,58 +600,6 @@ public: */ SingleDerivedPath coerceToSingleDerivedPath(const PosIdx pos, Value & v, std::string_view errorCtx); -public: - - /** - * The base environment, containing the builtin functions and - * values. - */ - Env & baseEnv; - - /** - * The same, but used during parsing to resolve variables. - */ - std::shared_ptr staticBaseEnv; // !!! should be private - - /** - * Name and documentation about every constant. - * - * Constants from primops are hard to crawl, and their docs will go - * here too. - */ - std::vector> constantInfos; - -private: - - unsigned int baseEnvDispl = 0; - - void createBaseEnv(); - - Value * addConstant(const std::string & name, const Value & v, Constant info); - - void addConstant(const std::string & name, Value * v, Constant info); - - Value * addPrimOp(PrimOp && primOp); - -public: - - Value & getBuiltin(const std::string & name); - - struct Doc - { - Pos pos; - std::optional name; - size_t arity; - std::vector args; - /** - * Unlike the other `doc` fields in this file, this one should never be - * `null`. - */ - const char * doc; - }; - - std::optional getDoc(Value & v); - private: inline Value * lookupVar(Env * env, const ExprVar & var, bool noEval); diff --git a/lix/libexpr/primops.cc b/lix/libexpr/primops.cc index 2070c3486..7b9ea06cc 100644 --- a/lix/libexpr/primops.cc +++ b/lix/libexpr/primops.cc @@ -231,9 +231,11 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport"); Env * env = &state.mem.allocEnv(vScope->attrs->size()); - env->up = &state.baseEnv; + env->up = &state.builtins.env; - auto staticEnv = std::make_shared(nullptr, state.staticBaseEnv.get(), vScope->attrs->size()); + auto staticEnv = std::make_shared( + nullptr, state.builtins.staticEnv.get(), vScope->attrs->size() + ); unsigned int displ = 0; for (auto & attr : *vScope->attrs) { @@ -1375,7 +1377,7 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Va auto epath = state.mem.allocValue(); epath->mkPath(path + name); if (!readFileType) - readFileType = &state.getBuiltin("readFileType"); + readFileType = &state.builtins.get("readFileType"); attr.mkApp(readFileType, epath); } else { // This branch of the conditional is much more likely. @@ -2785,23 +2787,23 @@ RegisterPrimOp::RegisterPrimOp(PrimOp && primOp) } -static Value getNixPath(EvalState & state, SearchPath & searchPath) +Value EvalBuiltins::prepareNixPath(const SearchPath & searchPath) { Value v; - v = state.mem.newList(searchPath.elements.size()); + v = mem.newList(searchPath.elements.size()); int n = 0; for (auto & i : searchPath.elements) { - auto attrs = state.buildBindings(2); + auto attrs = mem.buildBindings(symbols, 2); attrs.alloc("path").mkString(i.path.s); attrs.alloc("prefix").mkString(i.prefix.s); - (v.listElems()[n++] = state.mem.allocValue())->mkAttrs(attrs); + (v.listElems()[n++] = mem.allocValue())->mkAttrs(attrs); } return v; } -void EvalState::createBaseEnv() +void EvalBuiltins::createBaseEnv(const SearchPath & searchPath, const Path & storeDir) { - baseEnv.up = 0; + env.up = 0; // constants include the magic `builtins` which must come first #include "register-builtin-constants.gen.inc" @@ -2838,7 +2840,7 @@ void EvalState::createBaseEnv() #include "primops/derivation.nix.gen.hh" ; auto & expr = *state.parse( - code, sizeof(code), Pos::Hidden{}, {CanonPath::root}, state.staticBaseEnv + code, sizeof(code), Pos::Hidden{}, {CanonPath::root}, state.builtins.staticEnv ); state.eval(expr, v); }, @@ -2857,11 +2859,10 @@ void EvalState::createBaseEnv() /* Now that we've added all primops, sort the `builtins' set, because attribute lookups expect it to be sorted. */ - baseEnv.values[0]->attrs->sort(); - - staticBaseEnv->sort(); - staticBaseEnv->isRoot = true; + env.values[0]->attrs->sort(); + staticEnv->sort(); + staticEnv->isRoot = true; } diff --git a/lix/nix/main.cc b/lix/nix/main.cc index b94e8151c..ac7b1af32 100644 --- a/lix/nix/main.cc +++ b/lix/nix/main.cc @@ -265,7 +265,7 @@ static void showHelp(std::vector subcommand, NixArgs & toplevel) vDump->mkString(toplevel.dumpCli()); auto vRes = state.mem.allocValue(); - state.callFunction(*vGenerateManpage, state.getBuiltin("false"), *vRes, noPos); + state.callFunction(*vGenerateManpage, state.builtins.get("false"), *vRes, noPos); state.callFunction(*vRes, *vDump, *vRes, noPos); auto attr = vRes->attrs->get(state.symbols.create(mdName + ".md")); @@ -414,7 +414,7 @@ void mainWrapped(int argc, char * * argv) auto res = nlohmann::json::object(); res["builtins"] = ({ auto builtinsJson = nlohmann::json::object(); - auto builtins = state.baseEnv.values[0]->attrs; + auto builtins = state.builtins.env.values[0]->attrs; for (auto & builtin : *builtins) { auto b = nlohmann::json::object(); if (!builtin.value->isPrimOp()) continue; @@ -430,7 +430,7 @@ void mainWrapped(int argc, char * * argv) }); res["constants"] = ({ auto constantsJson = nlohmann::json::object(); - for (auto & [name, info] : state.constantInfos) { + for (auto & [name, info] : state.builtins.constantInfos) { auto c = nlohmann::json::object(); if (!info.doc) continue; c["doc"] = trim(stripIndentation(info.doc));