diff --git a/lix/libexpr/eval-inline.hh b/lix/libexpr/eval-inline.hh index 6a9aaac5e..e8f4d154b 100644 --- a/lix/libexpr/eval-inline.hh +++ b/lix/libexpr/eval-inline.hh @@ -5,9 +5,27 @@ #include "lix/libexpr/eval.hh" #include "lix/libexpr/eval-error.hh" #include "lix/libexpr/gc-alloc.hh" +#include "value.hh" +#include namespace nix { +inline Value::Value(app_t, EvalMemory & mem, Value & lhs, std::span args) + : internalType(tApp) +{ + if (args.size() == 1) { + _app._left = reinterpret_cast(&lhs); + _app._right = args[0]; + } else { + auto app = + static_cast(mem.allocBytes(sizeof(Value::AppN) + args.size_bytes())); + app->nargs = args.size(); + memcpy(app->args, args.data(), args.size_bytes()); + _app._left = reinterpret_cast(&lhs) | 1; + _app._appn = app; + } +} + [[gnu::always_inline]] void * EvalMemory::allocBytes(size_t size) { @@ -89,12 +107,11 @@ void EvalState::forceValue(Value & v, const PosIdx pos) tryFixupBlackHolePos(v, pos); throw; } + } else if (v.isApp()) { + callFunction(*v.app().left(), v.app().args(), v, pos); } - else if (v.isApp()) - callFunction(*v.app().left, *v.app().right, v, pos); } - [[gnu::always_inline]] inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx) { diff --git a/lix/libexpr/eval.cc b/lix/libexpr/eval.cc index 981054bb1..703e8742b 100644 --- a/lix/libexpr/eval.cc +++ b/lix/libexpr/eval.cc @@ -157,6 +157,7 @@ void initLibExpr() /* Don't look for interior pointers. This reduces the odds of misdetection a bit. */ GC_set_all_interior_pointers(0); + GC_REGISTER_DISPLACEMENT(1); /* We don't have any roots in data segments, so don't scan from there. */ diff --git a/lix/libexpr/primops.cc b/lix/libexpr/primops.cc index da3d356ac..eb85ada76 100644 --- a/lix/libexpr/primops.cc +++ b/lix/libexpr/primops.cc @@ -1955,10 +1955,9 @@ static void prim_mapAttrs(EvalState & state, Value * * args, Value & v) auto attrs = state.ctx.buildBindings(args[1]->attrs()->size()); for (auto & i : *args[1]->attrs()) { - Value * vFun2 = state.ctx.mem.allocValue(); auto vName = const_cast(state.ctx.symbols[i.name].toValuePtr()); - vFun2->mkApp(args[0], vName); - attrs.alloc(i.name).mkApp(vFun2, i.value); + Value * appArgs[] = {vName, i.value}; + attrs.alloc(i.name) = {NewValueAs::app, state.ctx.mem, *args[0], appArgs}; } v.mkAttrs(attrs.alreadySorted()); @@ -1997,13 +1996,12 @@ static void prim_zipAttrsWith(EvalState & state, Value * * args, Value & v) /* Construct a `fn name list` function call value. */ auto name = const_cast(state.ctx.symbols[sym].toValuePtr()); - auto call1 = state.ctx.mem.allocValue(); - call1->mkApp(args[0], name); - auto call2 = state.ctx.mem.allocValue(); - call2->mkApp(call1, list); + Value * callArgs[] = {name, list}; + auto call = state.ctx.mem.allocValue(); + *call = {NewValueAs::app, state.ctx.mem, *args[0], callArgs}; /* Insert it inside the returned attribute set. */ - attrs.insert(sym, call2); + attrs.insert(sym, call); } /* Populate the lists inside the attribute set */ diff --git a/lix/libexpr/value.hh b/lix/libexpr/value.hh index a8465af6a..54fa9de64 100644 --- a/lix/libexpr/value.hh +++ b/lix/libexpr/value.hh @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,7 @@ namespace nix { class BindingsBuilder; +class EvalMemory; typedef enum { tInt = 1, @@ -440,8 +442,13 @@ public: /// lazy partial application of another lambda. Value(app_t, Value & lhs, Value & rhs) : internalType(tApp) - , _app({ .left = &lhs, .right = &rhs }) - { } + , _app{._left = reinterpret_cast(&lhs), ._right = &rhs} + { + } + + /// Constructs a nix language value of type "lambda", which represents a + /// lazy partial application of another lambda. + Value(app_t, EvalMemory & mem, Value & lhs, std::span args); /// Constructs a nix language value of type "external", which is only used /// by plugins. Do any existing plugins even use this mechanism? @@ -562,6 +569,17 @@ public: } }; + struct AppN + { + size_t nargs; + Value * args[0]; + + std::span argsSpan() + { + return {args, nargs}; + } + }; + union { /// Dummy field, which takes up as much space as the largest union variants @@ -589,9 +607,25 @@ public: Expr * expr; } _thunk; struct { - Value * left, * right; + uintptr_t _left; + union + { + Value * _right; + AppN * _appn; + }; + + Value * left() const + { + return reinterpret_cast(_left & ~uintptr_t(1)); + } + + std::span args() + { + return _left & 1 ? _appn->argsSpan() : std::span{&_right, 1}; + } } _app; - struct { + struct + { Env * env; ExprLambda * fun; } _lambda; @@ -647,7 +681,7 @@ public: */ inline void clearValue() { - _app.left = _app.right = 0; + _empty[0] = _empty[1] = 0; } inline void mkInt(NixInt::Inner n) @@ -716,9 +750,7 @@ public: inline void mkApp(Value * l, Value * r) { - internalType = tApp; - _app.left = l; - _app.right = r; + *this = {NewValueAs::app, *l, *r}; } inline void mkLambda(Env * e, ExprLambda * f) @@ -849,7 +881,7 @@ public: return _thunk; } - const auto & app() const + auto & app() { return _app; } diff --git a/tests/unit/libexpr/value/print.cc b/tests/unit/libexpr/value/print.cc index 8e16deff8..381d3a97d 100644 --- a/tests/unit/libexpr/value/print.cc +++ b/tests/unit/libexpr/value/print.cc @@ -102,7 +102,7 @@ TEST_F(ValuePrintingTests, vThunk) TEST_F(ValuePrintingTests, vApp) { Value vApp; - vApp.mkApp(nullptr, nullptr); + vApp.mkApp(&vApp, &vApp); test(vApp, "«thunk»"); }