diff --git a/lix/libexpr/eval-inline.hh b/lix/libexpr/eval-inline.hh index 0586b9e7f..38a6e941e 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 = reinterpret_cast(&env), .expr = &expr}; raw = tag(tThunk, thunk); } @@ -112,22 +112,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 c4ea87c4b..40d38f32f 100644 --- a/lix/libexpr/eval.cc +++ b/lix/libexpr/eval.cc @@ -2136,8 +2136,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 dc5d234e2..a2220c615 100644 --- a/lix/libexpr/nixexpr.cc +++ b/lix/libexpr/nixexpr.cc @@ -10,7 +10,7 @@ namespace nix { ExprBlackHole eBlackHole; -Value::Thunk Value::blackHole{nullptr, &eBlackHole}; +Value::Thunk Value::blackHole{0, &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 ab04fcfc8..cc123442b 100644 --- a/lix/libexpr/value.hh +++ b/lix/libexpr/value.hh @@ -243,7 +243,7 @@ struct NewValueAs struct Value { private: - uintptr_t raw; + mutable uintptr_t raw; static constexpr size_t TAG_BITS = 3; static constexpr size_t TAG_ALIGN = 1 << TAG_BITS; @@ -606,7 +606,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 @@ -658,13 +658,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 @@ -722,8 +740,31 @@ public: }; struct alignas(TAG_ALIGN) Thunk { - Env * env; + uintptr_t _env; Expr * expr; + + bool resolved() const + { + return expr == nullptr; + } + + void resolve(Value v) + { + _env = v.raw; + expr = nullptr; + } + + Env * env() const + { + return reinterpret_cast(_env); + } + + Value result() const + { + Value v; + v.raw = _env; + return v; + } }; /** @@ -735,6 +776,7 @@ public: */ inline ValueType type(bool invalidIsThunk = false) const { + again: switch (internalType()) { case tInt: return nInt; @@ -760,11 +802,19 @@ public: case Acb::tInt: return nInt; } - case tThunk: - return nThunk; - case tApp: - return app().target()->isPrimOp() ? nFunction : nThunk; + 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 @@ -921,21 +971,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();