From 0b5b14ddc799877c64e2c8d426cf87bfeddb9533 Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Sun, 28 Sep 2025 00:02:20 +0200 Subject: [PATCH] libexpr: add multi-arg app nodes these behave like the old chains of app nodes, but they can store more than one argument per node. for tApp values themselves this is not all that useful, but if we could share tApp and tPrimOpApp backing storage we could avoid creating and traversing the linked lists of values that are currently needed to represent partially applied builtin functions. Change-Id: I5a2a02d9733e1e0be5443459e2998d62fd3b9a5b --- lix/libexpr/eval-inline.hh | 23 ++++++++++++-- lix/libexpr/eval.cc | 1 + lix/libexpr/primops.cc | 14 ++++----- lix/libexpr/value.hh | 50 +++++++++++++++++++++++++------ tests/unit/libexpr/value/print.cc | 2 +- 5 files changed, 69 insertions(+), 21 deletions(-) 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»"); }