diff --git a/lix/libexpr/eval-inline.hh b/lix/libexpr/eval-inline.hh index 3f134e1f0..b3f1a7a85 100644 --- a/lix/libexpr/eval-inline.hh +++ b/lix/libexpr/eval-inline.hh @@ -13,7 +13,7 @@ namespace nix { inline Value::Value(app_t, EvalMemory & mem, Value & lhs, Value & rhs) { auto app = static_cast(mem.allocBytes(sizeof(Value::App) + sizeof(Value *))); - app->_left = &lhs; + app->_left = reinterpret_cast(&lhs); app->_n = 1; app->_args[0] = &rhs; raw = tag(tApp, app); @@ -22,7 +22,7 @@ inline Value::Value(app_t, EvalMemory & mem, Value & lhs, Value & rhs) inline Value::Value(app_t, EvalMemory & mem, Value & lhs, std::span args) { auto app = static_cast(mem.allocBytes(sizeof(Value::App) + args.size_bytes())); - app->_left = &lhs; + app->_left = reinterpret_cast(&lhs); app->_n = args.size(); memcpy(app->_args, args.data(), args.size_bytes()); raw = tag(tApp, app); @@ -31,7 +31,7 @@ inline Value::Value(app_t, EvalMemory & mem, Value & lhs, std::span arg inline Value::Value(thunk_t, EvalMemory & mem, Env & env, Expr & expr) { auto thunk = mem.allocType(); - *thunk = {.env = &env, .expr = &expr}; + *thunk = {._env = &env, .expr = &expr}; raw = tag(tThunk, thunk); } @@ -113,22 +113,33 @@ Env & EvalMemory::allocEnv(size_t size) void EvalState::forceValue(Value & v, const PosIdx pos) { if (v.isThunk()) { - const auto backup = v; - Env * env = v.thunk().env; - Expr & expr = *v.thunk().expr; - v = Value{NewValueAs::blackhole}; - try { - expr.eval(*this, *env, v); - } catch (...) { - v = backup; - tryFixupBlackHolePos(v, pos); - throw; + auto & thunk = v.thunk(); + if (thunk.resolved()) { + v = thunk.result(); + } else { + const auto backup = v; + Env * env = v.thunk().env(); + Expr & expr = *v.thunk().expr; + v = Value{NewValueAs::blackhole}; + try { + expr.eval(*this, *env, v); + backup.thunk().resolve(v); + } catch (...) { + v = backup; + tryFixupBlackHolePos(v, pos); + throw; + } } } else if (v.isApp()) { auto & app = v.app(); - auto target = app.target(); - if (!target->isPrimOp() || target->primOp()->arity <= app.totalArgs()) { - callFunction(*v.app().left(), v.app().args(), v, pos); + if (app.resolved()) { + v = app.result(); + } else { + auto target = app.target(); + if (!target->isPrimOp() || target->primOp()->arity <= app.totalArgs()) { + callFunction(*v.app().left(), v.app().args(), v, pos); + app.resolve(v); + } } } } diff --git a/lix/libexpr/eval.cc b/lix/libexpr/eval.cc index f2bd7a4c8..a941ba723 100644 --- a/lix/libexpr/eval.cc +++ b/lix/libexpr/eval.cc @@ -2154,8 +2154,14 @@ void EvalState::forceValueDeep(Value & v) try { // If the value is a thunk, we're evaling. Otherwise no trace necessary. auto dts = ctx.debug && i.value->isThunk() - ? makeDebugTraceStacker(*this, *i.value->thunk().expr, *i.value->thunk().env, ctx.positions[i.pos], - "while evaluating the attribute '%1%'", ctx.symbols[i.name]) + ? makeDebugTraceStacker( + *this, + *i.value->thunk().expr, + *i.value->thunk().env(), + ctx.positions[i.pos], + "while evaluating the attribute '%1%'", + ctx.symbols[i.name] + ) : nullptr; recurse(*i.value); diff --git a/lix/libexpr/nixexpr.cc b/lix/libexpr/nixexpr.cc index 4df157791..3c137bd94 100644 --- a/lix/libexpr/nixexpr.cc +++ b/lix/libexpr/nixexpr.cc @@ -11,7 +11,7 @@ namespace nix { ExprBlackHole eBlackHole; static Env nullEnv; -Value::Thunk Value::blackHole{&nullEnv, &eBlackHole}; +Value::Thunk Value::blackHole{{&nullEnv}, &eBlackHole}; // FIXME: remove, because *symbols* are abstract and do not have a single // textual representation; see printIdentifier() diff --git a/lix/libexpr/value.hh b/lix/libexpr/value.hh index 693f45b28..e460ff538 100644 --- a/lix/libexpr/value.hh +++ b/lix/libexpr/value.hh @@ -245,7 +245,7 @@ struct NewValueAs struct Value { private: - uintptr_t raw; + mutable uintptr_t raw; public: static constexpr size_t TAG_BITS = 3; @@ -613,7 +613,7 @@ public: } inline bool isPrimOpApp() const { - return internalType() == tApp && app().target()->isPrimOp(); + return internalType() == tApp && !app().resolved() && app().target()->isPrimOp(); } struct alignas(TAG_ALIGN) List @@ -665,13 +665,31 @@ public: struct alignas(TAG_ALIGN) App { - Value * _left; + uintptr_t _left; size_t _n; Value * _args[0]; + bool resolved() const + { + return _n == 0; + } + + void resolve(Value v) + { + _left = v.raw; + _n = 0; + } + Value * left() const { - return _left; + return reinterpret_cast(_left); + } + + Value result() const + { + Value v; + v.raw = _left; + return v; } Value * target() const @@ -727,11 +745,6 @@ public: Env * env; ExprLambda * fun; }; - struct alignas(TAG_ALIGN) Thunk - { - Env * env; - Expr * expr; - }; /** * Returns the normal type of a Value. This only returns nThunk if @@ -740,43 +753,7 @@ public: * @param invalidIsThunk Instead of aborting an an invalid (probably * 0, so uninitialized) internal type, return `nThunk`. */ - inline ValueType type(bool invalidIsThunk = false) const - { - switch (internalType()) { - case tInt: - return nInt; - case tBool: - return nBool; - case tString: - return untag()->isPath() ? nPath : nString; - case tAttrs: - return nAttrs; - case tList: - return nList; - case tAuxiliary: - switch (untag()->type) { - case Acb::tExternal: - return nExternal; - case Acb::tFloat: - return nFloat; - case Acb::tNull: - return nNull; - case Acb::tPrimOp: - case Acb::tLambda: - return nFunction; - case Acb::tInt: - return nInt; - } - case tThunk: - return nThunk; - case tApp: - return app().target()->isPrimOp() ? nFunction : nThunk; - } - if (invalidIsThunk) - return nThunk; - else - abort(); - } + inline ValueType type(bool invalidIsThunk = false) const; inline void mkInt(NixInt::Inner n) { @@ -928,21 +905,16 @@ public: return untag(); } - const auto & thunk() const + Thunk & thunk() const { - return *untag(); + return *untag(); } - App & app() + App & app() const { return *untag(); } - const App & app() const - { - return *untag(); - } - const auto & lambda() const { return *untag(); @@ -972,6 +944,90 @@ public: } }; +struct alignas(Value::TAG_ALIGN) Value::Thunk +{ + union { + Env * _env; + Value _result; + }; + Expr * expr; + + bool resolved() const + { + return expr == nullptr; + } + + void resolve(Value v) + { + _result = v; + expr = nullptr; + } + + Env * env() const + { + return _env; + } + + Value result() const + { + return _result; + } +}; + +/** + * Returns the normal type of a Value. This only returns nThunk if + * the Value hasn't been forceValue'd + * + * @param invalidIsThunk Instead of aborting an an invalid (probably + * 0, so uninitialized) internal type, return `nThunk`. + */ +inline ValueType Value::type(bool invalidIsThunk) const +{ +again: + switch (internalType()) { + case tInt: + return nInt; + case tBool: + return nBool; + case tString: + return untag()->isPath() ? nPath : nString; + case tAttrs: + return nAttrs; + case tList: + return nList; + case tAuxiliary: + switch (untag()->type) { + case Acb::tExternal: + return nExternal; + case Acb::tFloat: + return nFloat; + case Acb::tNull: + return nNull; + case Acb::tPrimOp: + case Acb::tLambda: + return nFunction; + case Acb::tInt: + return nInt; + } + case tThunk: + if (thunk().resolved()) { + raw = thunk().result().raw; + goto again; + } + return nThunk; + case tApp: + if (app().resolved()) { + raw = app().result().raw; + goto again; + } + return app().target()->isPrimOp() ? nFunction : nThunk; + } + if (invalidIsThunk) + return nThunk; + else + abort(); +} + using ValueVector = GcVector; using PrimOp = Value::PrimOp;