From af86b744675f9e0aef4071b8116de5d536db272b Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Sun, 28 Sep 2025 00:02:21 +0200 Subject: [PATCH] libexpr: tag Value::Acb Value is already tagged. Value::Acb blocks are allocated for lambdas (so we can fit the value tag into the three bits we have available), but the current layout is rather wasteful for this purpose. the type bits can be stored together with parts of pointers, which in the lambda case will be the scope the lambda captures. the expr could also be used, but Env is a gc-allocated item and thus guaranteed to be aligned properly for tagging Change-Id: Ia685875387c7795bc4a00d73d1ce3cfea84e7297 --- lix/libexpr/eval-inline.hh | 2 +- lix/libexpr/eval.cc | 16 +++++++--- lix/libexpr/eval.hh | 2 +- lix/libexpr/value.hh | 65 ++++++++++++++++++++++++++++++-------- 4 files changed, 65 insertions(+), 20 deletions(-) diff --git a/lix/libexpr/eval-inline.hh b/lix/libexpr/eval-inline.hh index 38a6e941e..2c56b9688 100644 --- a/lix/libexpr/eval-inline.hh +++ b/lix/libexpr/eval-inline.hh @@ -38,7 +38,7 @@ inline Value::Value(thunk_t, EvalMemory & mem, Env & env, Expr & expr) inline Value::Value(lambda_t, EvalMemory & mem, Env & env, ExprLambda & lambda) { auto lp = mem.allocType(); - *lp = Lambda{{Acb::tLambda}, &env, &lambda}; + new (lp) Lambda{env, lambda}; raw = tag(tAuxiliary, lp); } diff --git a/lix/libexpr/eval.cc b/lix/libexpr/eval.cc index 40d38f32f..44a19a2b0 100644 --- a/lix/libexpr/eval.cc +++ b/lix/libexpr/eval.cc @@ -103,7 +103,7 @@ std::string showType(const Value & v) case tAuxiliary: #pragma GCC diagnostic push #pragma GCC diagnostic error "-Wswitch-enum" - switch (v.auxiliary()->type) { + switch (v.auxiliary()->type()) { case Value::Acb::tExternal: return v.external()->showType(); case Value::Acb::tFloat: @@ -1606,7 +1606,7 @@ void EvalState::callFunction(Value & fun, std::span args, Value & vRes, ExprLambda & lambda(*vCur.lambda().fun); - Env & env2 = lambda.pattern->match(lambda, *this, *vCur.lambda().env, args[0], pos); + Env & env2 = lambda.pattern->match(lambda, *this, *vCur.lambda().env(), args[0], pos); ctx.stats.nrFunctionCalls++; if (ctx.stats.countCalls) ctx.stats.addCall(lambda); @@ -1803,12 +1803,18 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res, PosI if (j) { attrs.insert(*j); } else if (!i.def) { - ctx.errors.make(R"(cannot evaluate a function that has an argument without a value ('%1%') + ctx.errors + .make( + R"(cannot evaluate a function that has an argument without a value ('%1%') Lix attempted to evaluate a function as a top level expression; in this case it must have its arguments supplied either by default values, or passed explicitly with '--arg' or '--argstr'. See -https://docs.lix.systems/manual/lix/stable/language/constructs.html#functions)", ctx.symbols[i.name]) - .atPos(i.pos).withFrame(*fun.lambda().env, *fun.lambda().fun).debugThrow(); +https://docs.lix.systems/manual/lix/stable/language/constructs.html#functions)", + ctx.symbols[i.name] + ) + .atPos(i.pos) + .withFrame(*fun.lambda().env(), *fun.lambda().fun) + .debugThrow(); } } } diff --git a/lix/libexpr/eval.hh b/lix/libexpr/eval.hh index 505ef0386..c714c4098 100644 --- a/lix/libexpr/eval.hh +++ b/lix/libexpr/eval.hh @@ -63,7 +63,7 @@ struct Constant using ValMap = GcMap; -struct Env +struct alignas(Value::Acb::TAG_ALIGN) Env { Env * up; Value * values[0]; diff --git a/lix/libexpr/value.hh b/lix/libexpr/value.hh index cc123442b..9b09ada09 100644 --- a/lix/libexpr/value.hh +++ b/lix/libexpr/value.hh @@ -341,7 +341,7 @@ public: raw = tInt | (uintptr_t(i.value) << TAG_BITS); } else { auto ip = gcAllocType(); - ip->type = Acb::tInt; + ip->raw = Acb::tInt; ip->value = i; raw = tag(tAuxiliary, ip); } @@ -352,7 +352,7 @@ public: Value(floating_t, NixFloat f) { auto fp = gcAllocType(); - fp->type = Acb::tFloat; + fp->raw = Acb::tFloat; fp->value = f; raw = tag(tAuxiliary, fp); } @@ -533,7 +533,7 @@ public: Value(external_t, ExternalValueBase & external) { auto ext = gcAllocType(); - ext->type = Acb::tExternal; + ext->raw = Acb::tExternal; ext->external = &external; raw = tag(tAuxiliary, ext); } @@ -598,11 +598,11 @@ public: // type() == nFunction inline bool isLambda() const { - return internalType() == tAuxiliary && auxiliary()->type == Acb::tLambda; + return internalType() == tAuxiliary && auxiliary()->type() == Acb::tLambda; }; inline bool isPrimOp() const { - return internalType() == tAuxiliary && auxiliary()->type == Acb::tPrimOp; + return internalType() == tAuxiliary && auxiliary()->type() == Acb::tPrimOp; } inline bool isPrimOpApp() const { @@ -705,14 +705,47 @@ public: /// these blocks are usually heap-allocated in GC memory space. struct alignas(TAG_ALIGN) Acb { - enum { + enum Type { tExternal, tFloat, tNull, tPrimOp, tLambda, tInt, - } type; + }; + + uintptr_t raw; + + static constexpr size_t TAG_BITS = 3; + static constexpr size_t TAG_ALIGN = 1 << TAG_BITS; + static constexpr uintptr_t TAG_MASK = (1 << TAG_BITS) - 1; + + static uintptr_t tag(Type t, auto v) + { + if constexpr (std::is_null_pointer_v) { + return t; + } else if constexpr (std::is_pointer_v) { + return (reinterpret_cast(v)) | t; + } else { + return (static_cast(v) << TAG_BITS) | t; + } + } + + template + T untag() const + { + if constexpr (std::is_pointer_v) { + static_assert(alignof(T) >= TAG_BITS); + return reinterpret_cast(raw & ~TAG_MASK); + } else { + return static_cast((raw & ~TAG_MASK) >> TAG_BITS); + } + } + + Type type() const + { + return Type(raw & TAG_MASK); + } }; struct External : Acb { @@ -735,8 +768,14 @@ public: struct Lambda : Acb { - Env * env; ExprLambda * fun; + + Lambda(Env & env, ExprLambda & fun) : Acb{tag(tLambda, &env)}, fun(&fun) {} + + Env * env() const + { + return untag(); + } }; struct alignas(TAG_ALIGN) Thunk { @@ -789,7 +828,7 @@ public: case tList: return nList; case tAuxiliary: - switch (untag()->type) { + switch (untag()->type()) { case Acb::tExternal: return nExternal; case Acb::tFloat: @@ -951,7 +990,7 @@ public: memcpy(&tmp, &raw, sizeof(tmp)); return NixInt(tmp >> 3); } else { - assert(internalType() == tAuxiliary && untag()->type == Acb::tInt); + assert(internalType() == tAuxiliary && untag()->type() == Acb::tInt); return untag()->value; } } @@ -988,19 +1027,19 @@ public: const PrimOp * primOp() const { - assert(internalType() == tAuxiliary && untag()->type == Acb::tPrimOp); + assert(internalType() == tAuxiliary && untag()->type() == Acb::tPrimOp); return untag(); } const ExternalValueBase * external() const { - assert(internalType() == tAuxiliary && untag()->type == Acb::tExternal); + assert(internalType() == tAuxiliary && untag()->type() == Acb::tExternal); return untag()->external; } NixFloat fpoint() const { - assert(internalType() == tAuxiliary && untag()->type == Acb::tFloat); + assert(internalType() == tAuxiliary && untag()->type() == Acb::tFloat); return untag()->value; }