From ddfca6e81b3685ca17b28a9063caf5c58d412dda Mon Sep 17 00:00:00 2001 From: Qyriad Date: Tue, 16 Jul 2024 11:25:05 -0600 Subject: [PATCH] libexpr: implement actual constructors for nix::Value Change-Id: Iebc2bb4e4ea5e93045afe47677df756de4ec4d05 --- src/libexpr/eval.cc | 9 +- src/libexpr/value.hh | 398 +++++++++++++++++++++++++++++++++++++++- src/libutil/concepts.hh | 22 +++ src/libutil/meson.build | 1 + 4 files changed, 421 insertions(+), 9 deletions(-) create mode 100644 src/libutil/concepts.hh diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index c0e7a9a2e..a925ce2d8 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -494,6 +494,14 @@ std::ostream & operator<<(std::ostream & output, PrimOp & primOp) } +Value::Value(primop_t, PrimOp & primop) + : internalType(tPrimOp) + , primOp(&primop) + , _primop_pad(0) +{ + primop.check(); +} + PrimOp * Value::primOpAppPrimOp() const { Value * left = primOpApp.left; @@ -506,7 +514,6 @@ PrimOp * Value::primOpAppPrimOp() const return left->primOp; } - void Value::mkPrimOp(PrimOp * p) { p->check(); diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index c35f88f8d..57485aa0a 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -3,6 +3,9 @@ #include #include +#include +#include +#include #include "gc-alloc.hh" #include "symbol-table.hh" @@ -11,6 +14,7 @@ #include "source-path.hh" #include "print-options.hh" #include "checked-arithmetic.hh" +#include "concepts.hh" #include @@ -132,6 +136,55 @@ class ExternalValueBase std::ostream & operator << (std::ostream & str, const ExternalValueBase & v); +extern ExprBlackHole eBlackHole; + +struct NewValueAs +{ + struct integer_t { }; + constexpr static integer_t integer{}; + + struct floating_t { }; + constexpr static floating_t floating{}; + + struct boolean_t { }; + constexpr static boolean_t boolean{}; + + struct string_t { }; + constexpr static string_t string{}; + + struct path_t { }; + constexpr static path_t path{}; + + struct list_t { }; + constexpr static list_t list{}; + + struct attrs_t { }; + constexpr static attrs_t attrs{}; + + struct thunk_t { }; + constexpr static thunk_t thunk{}; + + struct null_t { }; + constexpr static null_t null{}; + + struct app_t { }; + constexpr static app_t app{}; + + struct primop_t { }; + constexpr static primop_t primop{}; + + struct primOpApp_t { }; + constexpr static primOpApp_t primOpApp{}; + + struct lambda_t { }; + constexpr static lambda_t lambda{}; + + struct external_t { }; + constexpr static external_t external{}; + + struct blackhole_t { }; + constexpr static blackhole_t blackhole{}; +}; struct Value { @@ -142,6 +195,315 @@ private: public: + // Discount `using NewValueAs::*;` +#define USING_VALUETYPE(name) using name = NewValueAs::name + USING_VALUETYPE(integer_t); + USING_VALUETYPE(floating_t); + USING_VALUETYPE(boolean_t); + USING_VALUETYPE(string_t); + USING_VALUETYPE(path_t); + USING_VALUETYPE(list_t); + USING_VALUETYPE(attrs_t); + USING_VALUETYPE(thunk_t); + USING_VALUETYPE(primop_t); + USING_VALUETYPE(app_t); + USING_VALUETYPE(null_t); + USING_VALUETYPE(primOpApp_t); + USING_VALUETYPE(lambda_t); + USING_VALUETYPE(external_t); + USING_VALUETYPE(blackhole_t); +#undef USING_VALUETYPE + + /// Default constructor which is still used in the codebase but should not + /// be used in new code. Zero initializes its members. + [[deprecated]] Value() + : internalType(static_cast(0)) + , _empty{ 0, 0 } + { } + + /// Constructs a nix language value of type "int", with the integral value + /// of @ref i. + Value(integer_t, NixInt i) + : internalType(tInt) + , _empty{ 0, 0 } + { + // the NixInt ctor here is is special because NixInt has a ctor too, so + // we're not allowed to have it as an anonymous aggreagte member. we do + // however still have the option to clear the data members using _empty + // and leaving the second word of data cleared by setting only integer. + integer = i; + } + + /// Constructs a nix language value of type "float", with the floating + /// point value of @ref f. + Value(floating_t, NixFloat f) + : internalType(tFloat) + , fpoint(f) + , _float_pad(0) + { } + + /// Constructs a nix language value of type "bool", with the boolean + /// value of @ref b. + Value(boolean_t, bool b) + : internalType(tBool) + , boolean(b) + , _bool_pad(0) + { } + + /// Constructs a nix language value of type "string", with the value of the + /// C-string pointed to by @ref strPtr, and optionally with an array of + /// string context pointed to by @ref contextPtr. + /// + /// Neither the C-string nor the context array are copied; this constructor + /// assumes suitable memory has already been allocated (with the GC if + /// enabled), and string and context data copied into that memory. + Value(string_t, char const * strPtr, char const ** contextPtr = nullptr) + : internalType(tString) + , string({ .s = strPtr, .context = contextPtr }) + { } + + /// Constructx a nix language value of type "string", with a copy of the + /// string data viewed by @ref copyFrom. + /// + /// The string data *is* copied from @ref copyFrom, and this constructor + /// performs a dynamic (GC) allocation to do so. + Value(string_t, std::string_view copyFrom, NixStringContext const & context = {}) + : internalType(tString) + , string({ .s = gcCopyStringIfNeeded(copyFrom), .context = nullptr }) + { + if (context.empty()) { + // It stays nullptr. + return; + } + + // Copy the context. + this->string.context = gcAllocType(context.size() + 1); + + size_t n = 0; + for (NixStringContextElem const & contextElem : context) { + this->string.context[n] = gcCopyStringIfNeeded(contextElem.to_string()); + n += 1; + } + + // Terminator sentinel. + this->string.context[n] = nullptr; + } + + /// Constructx a nix language value of type "string", with the value of the + /// C-string pointed to by @ref strPtr, and optionally with a set of string + /// context @ref context. + /// + /// The C-string is not copied; this constructor assumes suitable memory + /// has already been allocated (with the GC if enabled), and string data + /// has been copied into that memory. The context data *is* copied from + /// @ref context, and this constructor performs a dynamic (GC) allocation + /// to do so. + Value(string_t, char const * strPtr, NixStringContext const & context) + : internalType(tString) + , string({ .s = strPtr, .context = nullptr }) + { + if (context.empty()) { + // It stays nullptr + return; + } + + // Copy the context. + this->string.context = gcAllocType(context.size() + 1); + + size_t n = 0; + for (NixStringContextElem const & contextElem : context) { + this->string.context[n] = gcCopyStringIfNeeded(contextElem.to_string()); + n += 1; + } + + // Terminator sentinel. + this->string.context[n] = nullptr; + } + + /// Constructs a nix language value of type "path", with the value of the + /// C-string pointed to by @ref strPtr. + /// + /// The C-string is not copied; this constructor assumes suitable memory + /// has already been allocated (with the GC if enabled), and string data + /// has been copied into that memory. + Value(path_t, char const * strPtr) + : internalType(tPath) + , _path(strPtr) + , _path_pad(0) + { } + + /// Constructs a nix language value of type "path", with the path + /// @ref path. + /// + /// The data from @ref path *is* copied, and this constructor performs a + /// dynamic (GC) allocation to do so. + Value(path_t, SourcePath const & path) + : internalType(tPath) + , _path(gcCopyStringIfNeeded(path.path.abs())) + , _path_pad(0) + { } + + /// Constructs a nix language value of type "list", with element array + /// @ref items. + /// + /// Generally, the data in @ref items is neither deep copied nor shallow + /// copied. This construct assumes the std::span @ref items is a region of + /// memory that has already been allocated (with the GC if enabled), and + /// an array of valid Value pointers has been copied into that memory. + /// + /// Howver, as an implementation detail, if @ref items is only 2 items or + /// smaller, the list is stored inline, and the Value pointers in + /// @ref items are shallow copied into this structure, without dynamically + /// allocating memory. + Value(list_t, std::span items) + { + if (items.size() == 1) { + this->internalType = tList1; + this->smallList[0] = items[0]; + this->smallList[1] = nullptr; + } else if (items.size() == 2) { + this->internalType = tList2; + this->smallList[0] = items[0]; + this->smallList[1] = items[1]; + } else { + this->internalType = tListN; + this->bigList.size = items.size(); + this->bigList.elems = items.data(); + } + } + + /// Constructs a nix language value of type "list", with an element array + /// initialized by applying @ref transformer to each element in @ref items. + /// + /// This allows "in-place" construction of a nix list when some logic is + /// needed to get each Value pointer. This constructor dynamically (GC) + /// allocates memory for the size of @ref items, and the Value pointers + /// returned by @ref transformer are shallow copied into it. + template< + std::ranges::sized_range SizedIterableT, + InvocableR TransformerT + > + Value(list_t, SizedIterableT & items, TransformerT const & transformer) + { + if (items.size() == 1) { + this->internalType = tList1; + this->smallList[0] = transformer(*items.begin()); + this->smallList[1] = nullptr; + } else if (items.size() == 2) { + this->internalType = tList2; + auto it = items.begin(); + this->smallList[0] = transformer(*it); + it++; + this->smallList[1] = transformer(*it); + } else { + this->internalType = tListN; + this->bigList.size = items.size(); + this->bigList.elems = gcAllocType(items.size()); + auto it = items.begin(); + for (size_t i = 0; i < items.size(); i++, it++) { + this->bigList.elems[i] = transformer(*it); + } + } + } + + /// Constructs a nix language value of the singleton type "null". + Value(null_t) + : internalType(tNull) + , _empty{0, 0} + { } + + /// Constructs a nix language value of type "set", with the attribute + /// bindings pointed to by @ref bindings. + /// + /// The bindings are not not copied; this constructor assumes @ref bindings + /// has already been suitably allocated by something like nix::buildBindings. + Value(attrs_t, Bindings * bindings) + : internalType(tAttrs) + , attrs(bindings) + , _attrs_pad(0) + { } + + /// Constructs a nix language lazy delayed computation, or "thunk". + /// + /// The thunk stores the environment it will be computed in @ref env, and + /// the expression that will need to be evaluated @ref expr. + Value(thunk_t, Env & env, Expr & expr) + : internalType(tThunk) + , thunk({ .env = &env, .expr = &expr }) + { } + + /// Constructs a nix language value of type "lambda", which represents + /// a builtin, primitive operation ("primop"), from the primop + /// implemented by @ref primop. + Value(primop_t, PrimOp & primop); + + /// Constructs a nix language value of type "lambda", which represents a + /// partially applied primop. + Value(primOpApp_t, Value & lhs, Value & rhs) + : internalType(tPrimOpApp) + , primOpApp({ .left = &lhs, .right = &rhs }) + { } + + /// Constructs a nix language value of type "lambda", which represents a + /// lazy partial application of another lambda. + Value(app_t, Value & lhs, Value & rhs) + : internalType(tApp) + , app({ .left = &lhs, .right = &rhs }) + { } + + /// Constructs a nix language value of type "external", which is only used + /// by plugins. Do any existing plugins even use this mechanism? + Value(external_t, ExternalValueBase & external) + : internalType(tExternal) + , external(&external) + , _external_pad(0) + { } + + /// Constructs a nix language value of type "lambda", which represents a + /// run of the mill lambda defined in nix code. + /// + /// This takes the environment the lambda is closed over @ref env, and + /// the lambda expression itself @ref lambda, which will not be evaluated + /// until it is applied. + Value(lambda_t, Env & env, ExprLambda & lambda) + : internalType(tLambda) + , lambda({ .env = &env, .fun = &lambda }) + { } + + /// Constructs an evil thunk, whose evaluation represents infinite recursion. + explicit Value(blackhole_t) + : internalType(tThunk) + , thunk({ .env = nullptr, .expr = reinterpret_cast(&eBlackHole) }) + { } + + Value(Value const & rhs) = default; + + /// Move constructor. Does the same thing as the copy constructor, but + /// also zeroes out the other Value. + Value(Value && rhs) + : internalType(rhs.internalType) + , _empty{ 0, 0 } + { + *this = std::move(rhs); + } + + Value & operator=(Value const & rhs) = default; + + /// Move assignment operator. + /// Does the same thing as the copy assignment operator, but also zeroes out + /// the rhs. + inline Value & operator=(Value && rhs) + { + *this = static_cast(rhs); + if (this != &rhs) { + // Kill `rhs`, because non-destructive move lol. + rhs.internalType = static_cast(0); + rhs._empty[0] = 0; + rhs._empty[1] = 0; + } + return *this; + } + void print(EvalState &state, std::ostream &str, PrintOptions options = PrintOptions {}); // Functions needed to distinguish the type @@ -160,8 +522,15 @@ public: union { + /// Dummy field, which takes up as much space as the largest union variants + /// to set the union's memory to zeroed memory. + uintptr_t _empty[2]; + NixInt integer; - bool boolean; + struct { + bool boolean; + uintptr_t _bool_pad; + }; /** * Strings in the evaluator carry a so-called `context` which @@ -190,8 +559,14 @@ public: const char * * context; // must be in sorted order } string; - const char * _path; - Bindings * attrs; + struct { + const char * _path; + uintptr_t _path_pad; + }; + struct { + Bindings * attrs; + uintptr_t _attrs_pad; + }; struct { size_t size; Value * * elems; @@ -208,12 +583,21 @@ public: Env * env; ExprLambda * fun; } lambda; - PrimOp * primOp; + struct { + PrimOp * primOp; + uintptr_t _primop_pad; + }; struct { Value * left, * right; } primOpApp; - ExternalValueBase * external; - NixFloat fpoint; + struct { + ExternalValueBase * external; + uintptr_t _external_pad; + }; + struct { + NixFloat fpoint; + uintptr_t _float_pad; + }; }; /** @@ -449,8 +833,6 @@ public: }; -extern ExprBlackHole eBlackHole; - bool Value::isBlackhole() const { return internalType == tThunk && thunk.expr == (Expr*) &eBlackHole; diff --git a/src/libutil/concepts.hh b/src/libutil/concepts.hh new file mode 100644 index 000000000..48bd1dbe1 --- /dev/null +++ b/src/libutil/concepts.hh @@ -0,0 +1,22 @@ +#pragma once +/// @file Defines C++ 20 concepts that std doesn't have. + +#include + +namespace nix +{ + +/// Like std::invocable<>, but also constrains the return type as well. +/// +/// Somehow, there is no std concept to do this, even though there is a type trait +/// for it. +/// +/// @tparam CallableT The type you want to constrain to be callable, and to return +/// @p ReturnT when called with @p Args as arguments. +/// +/// @tparam ReturnT The type the callable should return when called. +/// @tparam Args The arguments the callable should accept to return @p ReturnT. +template +concept InvocableR = std::is_invocable_r_v; + +} diff --git a/src/libutil/meson.build b/src/libutil/meson.build index c860e7e00..01fe65207 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -58,6 +58,7 @@ libutil_headers = files( 'comparator.hh', 'compression.hh', 'compute-levels.hh', + 'concepts.hh', 'config-impl.hh', 'config.hh', 'current-process.hh',