1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-07 22:33:57 +02:00

libexpr: Factor out Payload union to a default implementation of ValueStorage

This factors out most of the value representation into a mixin class.
`finishValue` is now gone for good and replaced with a simple template
function `setStorage` which derives the type information/disriminator from
the type of the argument. Likewise, reading of the value goes through function
template `getStorage`.

An empty type `Null` is introduced to make the bijection InternalType <-> C++ type
complete.
This commit is contained in:
Sergei Zimmerman 2025-06-28 16:18:18 +03:00
parent 810455f1b8
commit c39cc00404
No known key found for this signature in database
GPG key ID: A9B0B557CA632325
3 changed files with 258 additions and 164 deletions

View file

@ -502,7 +502,7 @@ void EvalState::addConstant(const std::string & name, Value * v, Constant info)
/* Install value the base environment. */
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
getBuiltins().payload.attrs->push_back(Attr(symbols.create(name2), v));
const_cast<Bindings *>(getBuiltins().attrs())->push_back(Attr(symbols.create(name2), v));
}
}
@ -540,7 +540,7 @@ const PrimOp * Value::primOpAppPrimOp() const
void Value::mkPrimOp(PrimOp * p)
{
p->check();
finishValue(tPrimOp, { .primOp = p });
setStorage(p);
}
@ -572,7 +572,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
else {
staticBaseEnv->vars.emplace_back(envName, baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
getBuiltins().payload.attrs->push_back(Attr(symbols.create(primOp.name), v));
const_cast<Bindings *>(getBuiltins().attrs())->push_back(Attr(symbols.create(primOp.name), v));
}
return v;

View file

@ -3,6 +3,7 @@
#include <cassert>
#include <span>
#include <type_traits>
#include "nix/expr/eval-gc.hh"
#include "nix/expr/value/context.hh"
@ -170,24 +171,205 @@ public:
friend struct Value;
};
struct Value
namespace detail {
/**
* Implementation mixin class for defining the public types
* In can be inherited from by the actual ValueStorage implementations
* for free due to Empty Base Class Optimization (EBCO).
*/
struct ValueBase
{
/**
* Strings in the evaluator carry a so-called `context` which
* is a list of strings representing store paths. This is to
* allow users to write things like
*
* "--with-freetype2-library=" + freetype + "/lib"
*
* where `freetype` is a derivation (or a source to be copied
* to the store). If we just concatenated the strings without
* keeping track of the referenced store paths, then if the
* string is used as a derivation attribute, the derivation
* will not have the correct dependencies in its inputDrvs and
* inputSrcs.
* The semantics of the context is as follows: when a string
* with context C is used as a derivation attribute, then the
* derivations in C will be added to the inputDrvs of the
* derivation, and the other store paths in C will be added to
* the inputSrcs of the derivations.
* For canonicity, the store paths should be in sorted order.
*/
struct StringWithContext
{
const char * c_str;
const char ** context; // must be in sorted order
};
struct Path
{
SourceAccessor * accessor;
const char * path;
};
struct Null
{};
struct ClosureThunk
{
Env * env;
Expr * expr;
};
struct FunctionApplicationThunk
{
Value *left, *right;
};
/**
* Like FunctionApplicationThunk, but must be a distinct type in order to
* resolve overloads to `tPrimOpApp` instead of `tApp`.
* This type helps with the efficient implementation of arity>=2 primop calls.
*/
struct PrimOpApplicationThunk
{
Value *left, *right;
};
struct Lambda
{
Env * env;
ExprLambda * fun;
};
using SmallList = std::array<Value *, 2>;
struct List
{
size_t size;
Value * const * elems;
};
};
template<typename T>
struct PayloadTypeToInternalType
{};
/**
* All stored types must be distinct (not type aliases) for the purposes of
* overload resolution in setStorage. This ensures there's a bijection from
* InternalType <-> C++ type.
*/
#define NIX_VALUE_STORAGE_FOR_EACH_FIELD(MACRO) \
MACRO(NixInt, integer, tInt) \
MACRO(bool, boolean, tBool) \
MACRO(ValueBase::StringWithContext, string, tString) \
MACRO(ValueBase::Path, path, tPath) \
MACRO(ValueBase::Null, null_, tNull) \
MACRO(Bindings *, attrs, tAttrs) \
MACRO(ValueBase::List, bigList, tListN) \
MACRO(ValueBase::SmallList, smallList, tListSmall) \
MACRO(ValueBase::ClosureThunk, thunk, tThunk) \
MACRO(ValueBase::FunctionApplicationThunk, app, tApp) \
MACRO(ValueBase::Lambda, lambda, tLambda) \
MACRO(PrimOp *, primOp, tPrimOp) \
MACRO(ValueBase::PrimOpApplicationThunk, primOpApp, tPrimOpApp) \
MACRO(ExternalValueBase *, external, tExternal) \
MACRO(NixFloat, fpoint, tFloat)
#define NIX_VALUE_PAYLOAD_TYPE(T, FIELD_NAME, DISCRIMINATOR) \
template<> \
struct PayloadTypeToInternalType<T> \
{ \
static constexpr InternalType value = DISCRIMINATOR; \
};
NIX_VALUE_STORAGE_FOR_EACH_FIELD(NIX_VALUE_PAYLOAD_TYPE)
#undef NIX_VALUE_PAYLOAD_TYPE
template<typename T>
inline constexpr InternalType payloadTypeToInternalType = PayloadTypeToInternalType<T>::value;
}
/**
* Discriminated union of types stored in the value.
* The union discriminator is @ref InternalType enumeration.
*
* This class can be specialized with a non-type template parameter
* of pointer size for more optimized data layouts on when pointer alignment
* bits can be used for storing the discriminator.
*
* All specializations of this type need to implement getStorage, setStorage and
* getInternalType methods.
*/
template<std::size_t ptrSize>
class ValueStorage : public detail::ValueBase
{
protected:
using Payload = union
{
#define NIX_VALUE_STORAGE_DEFINE_FIELD(T, FIELD_NAME, DISCRIMINATOR) T FIELD_NAME;
NIX_VALUE_STORAGE_FOR_EACH_FIELD(NIX_VALUE_STORAGE_DEFINE_FIELD)
#undef NIX_VALUE_STORAGE_DEFINE_FIELD
};
Payload payload;
private:
InternalType internalType = tUninitialized;
public:
#define NIX_VALUE_STORAGE_GET_IMPL(K, FIELD_NAME, DISCRIMINATOR) \
void getStorage(K & val) const noexcept \
{ \
assert(internalType == DISCRIMINATOR); \
val = payload.FIELD_NAME; \
}
#define NIX_VALUE_STORAGE_SET_IMPL(K, FIELD_NAME, DISCRIMINATOR) \
void setStorage(K val) noexcept \
{ \
payload.FIELD_NAME = val; \
internalType = DISCRIMINATOR; \
}
NIX_VALUE_STORAGE_FOR_EACH_FIELD(NIX_VALUE_STORAGE_GET_IMPL)
NIX_VALUE_STORAGE_FOR_EACH_FIELD(NIX_VALUE_STORAGE_SET_IMPL)
#undef NIX_VALUE_STORAGE_SET_IMPL
#undef NIX_VALUE_STORAGE_GET_IMPL
#undef NIX_VALUE_STORAGE_FOR_EACH_FIELD
/** Get internal type currently occupying the storage. */
InternalType getInternalType() const noexcept
{
return internalType;
}
};
/** Check if currently stored value type is either of Vs. */
template<InternalType... Vs>
struct Value : public ValueStorage<sizeof(void *)>
{
friend std::string showType(const Value & v);
template<InternalType... discriminator>
bool isa() const noexcept
{
return ((getInternalType() == Vs) || ...);
return ((getInternalType() == discriminator) || ...);
}
friend std::string showType(const Value & v);
template<typename T>
T getStorage() const noexcept
{
if (getInternalType() != detail::payloadTypeToInternalType<T>) [[unlikely]]
unreachable();
T out;
ValueStorage::getStorage(out);
return out;
}
public:
@ -227,84 +409,6 @@ public:
return isa<tPrimOpApp>();
};
/**
* Strings in the evaluator carry a so-called `context` which
* is a list of strings representing store paths. This is to
* allow users to write things like
*
* "--with-freetype2-library=" + freetype + "/lib"
*
* where `freetype` is a derivation (or a source to be copied
* to the store). If we just concatenated the strings without
* keeping track of the referenced store paths, then if the
* string is used as a derivation attribute, the derivation
* will not have the correct dependencies in its inputDrvs and
* inputSrcs.
* The semantics of the context is as follows: when a string
* with context C is used as a derivation attribute, then the
* derivations in C will be added to the inputDrvs of the
* derivation, and the other store paths in C will be added to
* the inputSrcs of the derivations.
* For canonicity, the store paths should be in sorted order.
*/
struct StringWithContext
{
const char * c_str;
const char ** context; // must be in sorted order
};
struct Path
{
SourceAccessor * accessor;
const char * path;
};
struct ClosureThunk
{
Env * env;
Expr * expr;
};
struct FunctionApplicationThunk
{
Value *left, *right;
};
struct Lambda
{
Env * env;
ExprLambda * fun;
};
using Payload = union
{
NixInt integer;
bool boolean;
StringWithContext string;
Path path;
Bindings * attrs;
struct
{
size_t size;
Value * const * elems;
} bigList;
Value * smallList[2];
ClosureThunk thunk;
FunctionApplicationThunk app;
Lambda lambda;
PrimOp * primOp;
FunctionApplicationThunk primOpApp;
ExternalValueBase * external;
NixFloat fpoint;
};
Payload payload;
/**
* Returns the normal type of a Value. This only returns nThunk if
* the Value hasn't been forceValue'd
@ -350,40 +454,34 @@ public:
unreachable();
}
inline void finishValue(InternalType newType, Payload newPayload)
{
payload = newPayload;
internalType = newType;
}
/**
* A value becomes valid when it is initialized. We don't use this
* in the evaluator; only in the bindings, where the slight extra
* cost is warranted because of inexperienced callers.
*/
inline bool isValid() const
inline bool isValid() const noexcept
{
return !isa<tUninitialized>();
}
inline void mkInt(NixInt::Inner n)
inline void mkInt(NixInt::Inner n) noexcept
{
mkInt(NixInt{n});
}
inline void mkInt(NixInt n)
inline void mkInt(NixInt n) noexcept
{
finishValue(tInt, {.integer = n});
setStorage(NixInt{n});
}
inline void mkBool(bool b)
inline void mkBool(bool b) noexcept
{
finishValue(tBool, {.boolean = b});
setStorage(b);
}
inline void mkString(const char * s, const char ** context = 0)
inline void mkString(const char * s, const char ** context = 0) noexcept
{
finishValue(tString, {.string = {.c_str = s, .context = context}});
setStorage(StringWithContext{.c_str = s, .context = context});
}
void mkString(std::string_view s);
@ -395,55 +493,55 @@ public:
void mkPath(const SourcePath & path);
void mkPath(std::string_view path);
inline void mkPath(SourceAccessor * accessor, const char * path)
inline void mkPath(SourceAccessor * accessor, const char * path) noexcept
{
finishValue(tPath, {.path = {.accessor = accessor, .path = path}});
setStorage(Path{.accessor = accessor, .path = path});
}
inline void mkNull()
inline void mkNull() noexcept
{
finishValue(tNull, {});
setStorage(Null{});
}
inline void mkAttrs(Bindings * a)
inline void mkAttrs(Bindings * a) noexcept
{
finishValue(tAttrs, {.attrs = a});
setStorage(a);
}
Value & mkAttrs(BindingsBuilder & bindings);
void mkList(const ListBuilder & builder)
void mkList(const ListBuilder & builder) noexcept
{
if (builder.size == 1)
finishValue(tListSmall, {.smallList = {builder.inlineElems[0], nullptr}});
setStorage(std::array<Value *, 2>{builder.inlineElems[0], nullptr});
else if (builder.size == 2)
finishValue(tListSmall, {.smallList = {builder.inlineElems[0], builder.inlineElems[1]}});
setStorage(std::array<Value *, 2>{builder.inlineElems[0], builder.inlineElems[1]});
else
finishValue(tListN, {.bigList = {.size = builder.size, .elems = builder.elems}});
setStorage(List{.size = builder.size, .elems = builder.elems});
}
inline void mkThunk(Env * e, Expr * ex)
inline void mkThunk(Env * e, Expr * ex) noexcept
{
finishValue(tThunk, {.thunk = {.env = e, .expr = ex}});
setStorage(ClosureThunk{.env = e, .expr = ex});
}
inline void mkApp(Value * l, Value * r)
inline void mkApp(Value * l, Value * r) noexcept
{
finishValue(tApp, {.app = {.left = l, .right = r}});
setStorage(FunctionApplicationThunk{.left = l, .right = r});
}
inline void mkLambda(Env * e, ExprLambda * f)
inline void mkLambda(Env * e, ExprLambda * f) noexcept
{
finishValue(tLambda, {.lambda = {.env = e, .fun = f}});
setStorage(Lambda{.env = e, .fun = f});
}
inline void mkBlackhole();
void mkPrimOp(PrimOp * p);
inline void mkPrimOpApp(Value * l, Value * r)
inline void mkPrimOpApp(Value * l, Value * r) noexcept
{
finishValue(tPrimOpApp, {.primOpApp = {.left = l, .right = r}});
setStorage(PrimOpApplicationThunk{.left = l, .right = r});
}
/**
@ -451,35 +549,35 @@ public:
*/
const PrimOp * primOpAppPrimOp() const;
inline void mkExternal(ExternalValueBase * e)
inline void mkExternal(ExternalValueBase * e) noexcept
{
finishValue(tExternal, {.external = e});
setStorage(e);
}
inline void mkFloat(NixFloat n)
inline void mkFloat(NixFloat n) noexcept
{
finishValue(tFloat, {.fpoint = n});
setStorage(n);
}
bool isList() const
bool isList() const noexcept
{
return isa<tListSmall, tListN>();
}
std::span<Value * const> listItems() const
std::span<Value * const> listItems() const noexcept
{
assert(isList());
return std::span<Value * const>(listElems(), listSize());
}
Value * const * listElems() const
Value * const * listElems() const noexcept
{
return isa<tListSmall>() ? payload.smallList : payload.bigList.elems;
return isa<tListSmall>() ? payload.smallList.data() : getStorage<List>().elems;
}
size_t listSize() const
size_t listSize() const noexcept
{
return isa<tListSmall>() ? (payload.smallList[1] == nullptr ? 1 : 2) : payload.bigList.size;
return isa<tListSmall>() ? (getStorage<SmallList>()[1] == nullptr ? 1 : 2) : getStorage<List>().size;
}
PosIdx determinePos(const PosIdx pos) const;
@ -493,85 +591,82 @@ public:
SourcePath path() const
{
assert(isa<tPath>());
return SourcePath(ref(pathAccessor()->shared_from_this()), CanonPath(CanonPath::unchecked_t(), pathStr()));
}
std::string_view string_view() const
std::string_view string_view() const noexcept
{
assert(isa<tString>());
return std::string_view(payload.string.c_str);
return std::string_view(getStorage<StringWithContext>().c_str);
}
const char * c_str() const
const char * c_str() const noexcept
{
assert(isa<tString>());
return payload.string.c_str;
return getStorage<StringWithContext>().c_str;
}
const char ** context() const
const char ** context() const noexcept
{
return payload.string.context;
return getStorage<StringWithContext>().context;
}
ExternalValueBase * external() const
ExternalValueBase * external() const noexcept
{
return payload.external;
return getStorage<ExternalValueBase *>();
}
const Bindings * attrs() const
const Bindings * attrs() const noexcept
{
return payload.attrs;
return getStorage<Bindings *>();
}
const PrimOp * primOp() const
const PrimOp * primOp() const noexcept
{
return payload.primOp;
return getStorage<PrimOp *>();
}
bool boolean() const
bool boolean() const noexcept
{
return payload.boolean;
return getStorage<bool>();
}
NixInt integer() const
NixInt integer() const noexcept
{
return payload.integer;
return getStorage<NixInt>();
}
NixFloat fpoint() const
NixFloat fpoint() const noexcept
{
return payload.fpoint;
return getStorage<NixFloat>();
}
Lambda lambda() const
Lambda lambda() const noexcept
{
return payload.lambda;
return getStorage<Lambda>();
}
ClosureThunk thunk() const
ClosureThunk thunk() const noexcept
{
return payload.thunk;
return getStorage<ClosureThunk>();
}
FunctionApplicationThunk primOpApp() const
PrimOpApplicationThunk primOpApp() const noexcept
{
return payload.primOpApp;
return getStorage<PrimOpApplicationThunk>();
}
FunctionApplicationThunk app() const
FunctionApplicationThunk app() const noexcept
{
return payload.app;
return getStorage<FunctionApplicationThunk>();
}
const char * pathStr() const
const char * pathStr() const noexcept
{
return payload.path.path;
return getStorage<Path>().path;
}
SourceAccessor * pathAccessor() const
SourceAccessor * pathAccessor() const noexcept
{
return payload.path.accessor;
return getStorage<Path>().accessor;
}
};
@ -579,7 +674,7 @@ extern ExprBlackHole eBlackHole;
bool Value::isBlackhole() const
{
return isa<tThunk>() && thunk().expr == (Expr *) &eBlackHole;
return isThunk() && thunk().expr == (Expr *) &eBlackHole;
}
void Value::mkBlackhole()
@ -606,5 +701,4 @@ typedef std::shared_ptr<Value *> RootValue;
RootValue allocRootValue(Value * v);
void forceNoNullByte(std::string_view s, std::function<Pos()> = nullptr);
}

View file

@ -5050,7 +5050,7 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings)
/* Now that we've added all primops, sort the `builtins' set,
because attribute lookups expect it to be sorted. */
getBuiltins().payload.attrs->sort();
const_cast<Bindings *>(getBuiltins().attrs())->sort();
staticBaseEnv->sort();