Add a ListBuilder helper for constructing list values

Previously, `state.mkList()` would set the type of the value to tList
and allocate the list vector, but it would not initialize the values
in the list. This has two problems:

* If an exception occurs, the list is left in an undefined state.

* More importantly, for multithreaded evaluation, if a value
  transitions from thunk to non-thunk, it should be final (i.e. other
  threads should be able to access the value safely).

To address this, there now is a `ListBuilder` class (analogous to
`BindingsBuilder`) to build the list vector prior to the call to
`Value::mkList()`. Typical usage:

   auto list = state.buildList(size);
   for (auto & v : list)
       v = ... set value ...;
   vRes.mkList(list);
This commit is contained in:
Eelco Dolstra 2024-03-14 19:10:31 +01:00
parent bff5c94184
commit fecff520d7
10 changed files with 228 additions and 157 deletions

View file

@ -435,7 +435,8 @@ EvalState::EvalState(
static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes"); static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes");
vEmptyList.mkList(0); vEmptyList.mkList(buildList(0));
vNull.mkNull();
/* Initialise the Nix expression search path. */ /* Initialise the Nix expression search path. */
if (!evalSettings.pureEval) { if (!evalSettings.pureEval) {
@ -923,12 +924,11 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
} }
} }
void EvalState::mkList(Value & v, size_t size) ListBuilder::ListBuilder(EvalState & state, size_t size)
: size(size)
, elems(size <= 2 ? inlineElems : (Value * *) allocBytes(size * sizeof(Value *)))
{ {
v.mkList(size); state.nrListElems += size;
if (size > 2)
v.bigList.elems = (Value * *) allocBytes(size * sizeof(Value *));
nrListElems += size;
} }
@ -1353,9 +1353,10 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
void ExprList::eval(EvalState & state, Env & env, Value & v) void ExprList::eval(EvalState & state, Env & env, Value & v)
{ {
state.mkList(v, elems.size()); auto list = state.buildList(elems.size());
for (auto [n, v2] : enumerate(v.listItems())) for (const auto & [n, v2] : enumerate(list))
const_cast<Value * &>(v2) = elems[n]->maybeThunk(state, env); v2 = elems[n]->maybeThunk(state, env);
v.mkList(list);
} }
@ -1963,14 +1964,15 @@ void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Po
return; return;
} }
mkList(v, len); auto list = buildList(len);
auto out = v.listElems(); auto out = list.elems;
for (size_t n = 0, pos = 0; n < nrLists; ++n) { for (size_t n = 0, pos = 0; n < nrLists; ++n) {
auto l = lists[n]->listSize(); auto l = lists[n]->listSize();
if (l) if (l)
memcpy(out + pos, lists[n]->listElems(), l * sizeof(Value *)); memcpy(out + pos, lists[n]->listElems(), l * sizeof(Value *));
pos += l; pos += l;
} }
v.mkList(list);
} }

View file

@ -186,6 +186,11 @@ public:
*/ */
Value vEmptyList; Value vEmptyList;
/**
* Null constant.
*/
Value vNull;
/** /**
* The accessor for the root filesystem. * The accessor for the root filesystem.
*/ */
@ -615,7 +620,11 @@ public:
return BindingsBuilder(*this, allocBindings(capacity)); return BindingsBuilder(*this, allocBindings(capacity));
} }
void mkList(Value & v, size_t length); ListBuilder buildList(size_t size)
{
return ListBuilder(*this, size);
}
void mkThunk_(Value & v, Expr * expr); void mkThunk_(Value & v, Expr * expr);
void mkPos(Value & v, PosIdx pos); void mkPos(Value & v, PosIdx pos);
@ -756,6 +765,7 @@ private:
friend void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v); friend void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v);
friend struct Value; friend struct Value;
friend class ListBuilder;
}; };
struct DebugTraceStacker { struct DebugTraceStacker {

View file

@ -57,11 +57,10 @@ class JSONSax : nlohmann::json_sax<json> {
ValueVector values; ValueVector values;
std::unique_ptr<JSONState> resolve(EvalState & state) override std::unique_ptr<JSONState> resolve(EvalState & state) override
{ {
Value & v = parent->value(state); auto list = state.buildList(values.size());
state.mkList(v, values.size()); for (const auto & [n, v2] : enumerate(list))
for (size_t n = 0; n < values.size(); ++n) { v2 = values[n];
v.listElems()[n] = values[n]; parent->value(state).mkList(list);
}
return std::move(parent); return std::move(parent);
} }
void add() override { void add() override {

View file

@ -187,13 +187,13 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
NixStringContextElem::DrvDeep { .drvPath = *storePath }, NixStringContextElem::DrvDeep { .drvPath = *storePath },
}); });
attrs.alloc(state.sName).mkString(drv.env["name"]); attrs.alloc(state.sName).mkString(drv.env["name"]);
auto & outputsVal = attrs.alloc(state.sOutputs);
state.mkList(outputsVal, drv.outputs.size());
auto list = state.buildList(drv.outputs.size());
for (const auto & [i, o] : enumerate(drv.outputs)) { for (const auto & [i, o] : enumerate(drv.outputs)) {
mkOutputString(state, attrs, *storePath, o); mkOutputString(state, attrs, *storePath, o);
(outputsVal.listElems()[i] = state.allocValue())->mkString(o.first); (list[i] = state.allocValue())->mkString(o.first);
} }
attrs.alloc(state.sOutputs).mkList(list);
auto w = state.allocValue(); auto w = state.allocValue();
w->mkAttrs(attrs); w->mkAttrs(attrs);
@ -694,10 +694,10 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
} }
/* Create the result list. */ /* Create the result list. */
state.mkList(v, res.size()); auto list = state.buildList(res.size());
unsigned int n = 0; for (const auto & [n, i] : enumerate(res))
for (auto & i : res) list[n] = i;
v.listElems()[n++] = i; v.mkList(list);
} }
static RegisterPrimOp primop_genericClosure(PrimOp { static RegisterPrimOp primop_genericClosure(PrimOp {
@ -2423,14 +2423,15 @@ static void prim_attrNames(EvalState & state, const PosIdx pos, Value * * args,
{ {
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrNames"); state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrNames");
state.mkList(v, args[0]->attrs->size()); auto list = state.buildList(args[0]->attrs->size());
size_t n = 0; for (const auto & [n, i] : enumerate(*args[0]->attrs))
for (auto & i : *args[0]->attrs) (list[n] = state.allocValue())->mkString(state.symbols[i.name]);
(v.listElems()[n++] = state.allocValue())->mkString(state.symbols[i.name]);
std::sort(v.listElems(), v.listElems() + n, std::sort(list.begin(), list.end(),
[](Value * v1, Value * v2) { return strcmp(v1->c_str(), v2->c_str()) < 0; }); [](Value * v1, Value * v2) { return strcmp(v1->c_str(), v2->c_str()) < 0; });
v.mkList(list);
} }
static RegisterPrimOp primop_attrNames({ static RegisterPrimOp primop_attrNames({
@ -2450,21 +2451,22 @@ static void prim_attrValues(EvalState & state, const PosIdx pos, Value * * args,
{ {
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrValues"); state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrValues");
state.mkList(v, args[0]->attrs->size()); auto list = state.buildList(args[0]->attrs->size());
unsigned int n = 0; for (const auto & [n, i] : enumerate(*args[0]->attrs))
for (auto & i : *args[0]->attrs) list[n] = (Value *) &i;
v.listElems()[n++] = (Value *) &i;
std::sort(v.listElems(), v.listElems() + n, std::sort(list.begin(), list.end(),
[&](Value * v1, Value * v2) { [&](Value * v1, Value * v2) {
std::string_view s1 = state.symbols[((Attr *) v1)->name], std::string_view s1 = state.symbols[((Attr *) v1)->name],
s2 = state.symbols[((Attr *) v2)->name]; s2 = state.symbols[((Attr *) v2)->name];
return s1 < s2; return s1 < s2;
}); });
for (unsigned int i = 0; i < n; ++i) for (auto & v : list)
v.listElems()[i] = ((Attr *) v.listElems()[i])->value; v = ((Attr *) v)->value;
v.mkList(list);
} }
static RegisterPrimOp primop_attrValues({ static RegisterPrimOp primop_attrValues({
@ -2805,9 +2807,10 @@ static void prim_catAttrs(EvalState & state, const PosIdx pos, Value * * args, V
res[found++] = i->value; res[found++] = i->value;
} }
state.mkList(v, found); auto list = state.buildList(found);
for (unsigned int n = 0; n < found; ++n) for (unsigned int n = 0; n < found; ++n)
v.listElems()[n] = res[n]; list[n] = res[n];
v.mkList(list);
} }
static RegisterPrimOp primop_catAttrs({ static RegisterPrimOp primop_catAttrs({
@ -2908,43 +2911,50 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg
// attribute with the merge function application. this way we need not // attribute with the merge function application. this way we need not
// use (slightly slower) temporary storage the GC does not know about. // use (slightly slower) temporary storage the GC does not know about.
std::map<Symbol, std::pair<size_t, Value * *>> attrsSeen; struct Item
{
size_t size = 0;
size_t pos = 0;
std::optional<ListBuilder> list;
};
std::map<Symbol, Item> attrsSeen;
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.zipAttrsWith"); state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.zipAttrsWith");
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.zipAttrsWith"); state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.zipAttrsWith");
const auto listSize = args[1]->listSize(); const auto listItems = args[1]->listItems();
const auto listElems = args[1]->listElems();
for (unsigned int n = 0; n < listSize; ++n) { for (auto & vElem : listItems) {
Value * vElem = listElems[n];
state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith"); state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith");
for (auto & attr : *vElem->attrs) for (auto & attr : *vElem->attrs)
attrsSeen[attr.name].first++; attrsSeen.try_emplace(attr.name).first->second.size++;
}
for (auto & [sym, elem] : attrsSeen)
elem.list.emplace(state.buildList(elem.size));
for (auto & vElem : listItems) {
for (auto & attr : *vElem->attrs) {
auto & item = attrsSeen.at(attr.name);
(*item.list)[item.pos++] = attr.value;
}
} }
auto attrs = state.buildBindings(attrsSeen.size()); auto attrs = state.buildBindings(attrsSeen.size());
for (auto & [sym, elem] : attrsSeen) { for (auto & [sym, elem] : attrsSeen) {
auto & list = attrs.alloc(sym);
state.mkList(list, elem.first);
elem.second = list.listElems();
}
v.mkAttrs(attrs.alreadySorted());
for (unsigned int n = 0; n < listSize; ++n) {
Value * vElem = listElems[n];
for (auto & attr : *vElem->attrs)
*attrsSeen[attr.name].second++ = attr.value;
}
for (auto & attr : *v.attrs) {
auto name = state.allocValue(); auto name = state.allocValue();
name->mkString(state.symbols[attr.name]); name->mkString(state.symbols[sym]);
auto call1 = state.allocValue(); auto call1 = state.allocValue();
call1->mkApp(args[0], name); call1->mkApp(args[0], name);
auto call2 = state.allocValue(); auto call2 = state.allocValue();
call2->mkApp(call1, attr.value); auto arg = state.allocValue();
attr.value = call2; arg->mkList(*elem.list);
call2->mkApp(call1, arg);
attrs.insert(sym, call2);
} }
v.mkAttrs(attrs.alreadySorted());
} }
static RegisterPrimOp primop_zipAttrsWith({ static RegisterPrimOp primop_zipAttrsWith({
@ -3055,9 +3065,10 @@ static void prim_tail(EvalState & state, const PosIdx pos, Value * * args, Value
if (args[0]->listSize() == 0) if (args[0]->listSize() == 0)
state.error<EvalError>("'tail' called on an empty list").atPos(pos).debugThrow(); state.error<EvalError>("'tail' called on an empty list").atPos(pos).debugThrow();
state.mkList(v, args[0]->listSize() - 1); auto list = state.buildList(args[0]->listSize() - 1);
for (unsigned int n = 0; n < v.listSize(); ++n) for (const auto & [n, v] : enumerate(list))
v.listElems()[n] = args[0]->listElems()[n + 1]; v = args[0]->listElems()[n + 1];
v.mkList(list);
} }
static RegisterPrimOp primop_tail({ static RegisterPrimOp primop_tail({
@ -3088,10 +3099,11 @@ static void prim_map(EvalState & state, const PosIdx pos, Value * * args, Value
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.map"); state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.map");
state.mkList(v, args[1]->listSize()); auto list = state.buildList(args[1]->listSize());
for (unsigned int n = 0; n < v.listSize(); ++n) for (const auto & [n, v] : enumerate(list))
(v.listElems()[n] = state.allocValue())->mkApp( (v = state.allocValue())->mkApp(
args[0], args[1]->listElems()[n]); args[0], args[1]->listElems()[n]);
v.mkList(list);
} }
static RegisterPrimOp primop_map({ static RegisterPrimOp primop_map({
@ -3140,8 +3152,9 @@ static void prim_filter(EvalState & state, const PosIdx pos, Value * * args, Val
if (same) if (same)
v = *args[1]; v = *args[1];
else { else {
state.mkList(v, k); auto list = state.buildList(k);
for (unsigned int n = 0; n < k; ++n) v.listElems()[n] = vs[n]; for (const auto & [n, v] : enumerate(list)) v = vs[n];
v.mkList(list);
} }
} }
@ -3316,12 +3329,13 @@ static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Va
// as evaluating map without accessing any values makes little sense. // as evaluating map without accessing any values makes little sense.
state.forceFunction(*args[0], noPos, "while evaluating the first argument passed to builtins.genList"); state.forceFunction(*args[0], noPos, "while evaluating the first argument passed to builtins.genList");
state.mkList(v, len); auto list = state.buildList(len);
for (unsigned int n = 0; n < (unsigned int) len; ++n) { for (const auto & [n, v] : enumerate(list)) {
auto arg = state.allocValue(); auto arg = state.allocValue();
arg->mkInt(n); arg->mkInt(n);
(v.listElems()[n] = state.allocValue())->mkApp(args[0], arg); (v = state.allocValue())->mkApp(args[0], arg);
} }
v.mkList(list);
} }
static RegisterPrimOp primop_genList({ static RegisterPrimOp primop_genList({
@ -3355,11 +3369,10 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.sort"); state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.sort");
state.mkList(v, len); auto list = state.buildList(len);
for (unsigned int n = 0; n < len; ++n) { for (const auto & [n, v] : enumerate(list))
state.forceValue(*args[1]->listElems()[n], pos); state.forceValue(*(v = args[1]->listElems()[n]), pos);
v.listElems()[n] = args[1]->listElems()[n]; v.mkList(list);
}
auto comparator = [&](Value * a, Value * b) { auto comparator = [&](Value * a, Value * b) {
/* Optimization: if the comparator is lessThan, bypass /* Optimization: if the comparator is lessThan, bypass
@ -3424,17 +3437,17 @@ static void prim_partition(EvalState & state, const PosIdx pos, Value * * args,
auto attrs = state.buildBindings(2); auto attrs = state.buildBindings(2);
auto & vRight = attrs.alloc(state.sRight);
auto rsize = right.size(); auto rsize = right.size();
state.mkList(vRight, rsize); auto rlist = state.buildList(rsize);
if (rsize) if (rsize)
memcpy(vRight.listElems(), right.data(), sizeof(Value *) * rsize); memcpy(rlist.elems, right.data(), sizeof(Value *) * rsize);
attrs.alloc(state.sRight).mkList(rlist);
auto & vWrong = attrs.alloc(state.sWrong);
auto wsize = wrong.size(); auto wsize = wrong.size();
state.mkList(vWrong, wsize); auto wlist = state.buildList(wsize);
if (wsize) if (wsize)
memcpy(vWrong.listElems(), wrong.data(), sizeof(Value *) * wsize); memcpy(wlist.elems, wrong.data(), sizeof(Value *) * wsize);
attrs.alloc(state.sWrong).mkList(wlist);
v.mkAttrs(attrs); v.mkAttrs(attrs);
} }
@ -3481,10 +3494,10 @@ static void prim_groupBy(EvalState & state, const PosIdx pos, Value * * args, Va
auto attrs2 = state.buildBindings(attrs.size()); auto attrs2 = state.buildBindings(attrs.size());
for (auto & i : attrs) { for (auto & i : attrs) {
auto & list = attrs2.alloc(i.first);
auto size = i.second.size(); auto size = i.second.size();
state.mkList(list, size); auto list = state.buildList(size);
memcpy(list.listElems(), i.second.data(), sizeof(Value *) * size); memcpy(list.elems, i.second.data(), sizeof(Value *) * size);
attrs2.alloc(i.first).mkList(list);
} }
v.mkAttrs(attrs2.alreadySorted()); v.mkAttrs(attrs2.alreadySorted());
@ -3531,14 +3544,15 @@ static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args,
len += lists[n].listSize(); len += lists[n].listSize();
} }
state.mkList(v, len); auto list = state.buildList(len);
auto out = v.listElems(); auto out = list.elems;
for (unsigned int n = 0, pos = 0; n < nrLists; ++n) { for (unsigned int n = 0, pos = 0; n < nrLists; ++n) {
auto l = lists[n].listSize(); auto l = lists[n].listSize();
if (l) if (l)
memcpy(out + pos, lists[n].listElems(), l * sizeof(Value *)); memcpy(out + pos, lists[n].listElems(), l * sizeof(Value *));
pos += l; pos += l;
} }
v.mkList(list);
} }
static RegisterPrimOp primop_concatMap({ static RegisterPrimOp primop_concatMap({
@ -3986,14 +4000,13 @@ void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v)
} }
// the first match is the whole string // the first match is the whole string
const size_t len = match.size() - 1; auto list = state.buildList(match.size() - 1);
state.mkList(v, len); for (const auto & [i, v2] : enumerate(list))
for (size_t i = 0; i < len; ++i) { if (!match[i + 1].matched)
if (!match[i+1].matched) (v2 = state.allocValue())->mkNull();
(v.listElems()[i] = state.allocValue())->mkNull();
else else
(v.listElems()[i] = state.allocValue())->mkString(match[i + 1].str()); (v2 = state.allocValue())->mkString(match[i + 1].str());
} v.mkList(list);
} catch (std::regex_error & e) { } catch (std::regex_error & e) {
if (e.code() == std::regex_constants::error_space) { if (e.code() == std::regex_constants::error_space) {
@ -4062,11 +4075,12 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
// Any matches results are surrounded by non-matching results. // Any matches results are surrounded by non-matching results.
const size_t len = std::distance(begin, end); const size_t len = std::distance(begin, end);
state.mkList(v, 2 * len + 1); auto list = state.buildList(2 * len + 1);
size_t idx = 0; size_t idx = 0;
if (len == 0) { if (len == 0) {
v.listElems()[idx++] = args[1]; list[0] = args[1];
v.mkList(list);
return; return;
} }
@ -4075,28 +4089,31 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto match = *i; auto match = *i;
// Add a string for non-matched characters. // Add a string for non-matched characters.
(v.listElems()[idx++] = state.allocValue())->mkString(match.prefix().str()); (list[idx++] = state.allocValue())->mkString(match.prefix().str());
// Add a list for matched substrings. // Add a list for matched substrings.
const size_t slen = match.size() - 1; const size_t slen = match.size() - 1;
auto elem = v.listElems()[idx++] = state.allocValue();
// Start at 1, beacause the first match is the whole string. // Start at 1, beacause the first match is the whole string.
state.mkList(*elem, slen); auto list2 = state.buildList(slen);
for (size_t si = 0; si < slen; ++si) { for (const auto & [si, v2] : enumerate(list2)) {
if (!match[si + 1].matched) if (!match[si + 1].matched)
(elem->listElems()[si] = state.allocValue())->mkNull(); v2 = &state.vNull;
else else
(elem->listElems()[si] = state.allocValue())->mkString(match[si + 1].str()); (v2 = state.allocValue())->mkString(match[si + 1].str());
} }
(list[idx++] = state.allocValue())->mkList(list2);
// Add a string for non-matched suffix characters. // Add a string for non-matched suffix characters.
if (idx == 2 * len) if (idx == 2 * len)
(v.listElems()[idx++] = state.allocValue())->mkString(match.suffix().str()); (list[idx++] = state.allocValue())->mkString(match.suffix().str());
} }
assert(idx == 2 * len + 1); assert(idx == 2 * len + 1);
v.mkList(list);
} catch (std::regex_error & e) { } catch (std::regex_error & e) {
if (e.code() == std::regex_constants::error_space) { if (e.code() == std::regex_constants::error_space) {
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
@ -4316,9 +4333,10 @@ static void prim_splitVersion(EvalState & state, const PosIdx pos, Value * * arg
break; break;
components.emplace_back(component); components.emplace_back(component);
} }
state.mkList(v, components.size()); auto list = state.buildList(components.size());
for (const auto & [n, component] : enumerate(components)) for (const auto & [n, component] : enumerate(components))
(v.listElems()[n] = state.allocValue())->mkString(std::move(component)); (list[n] = state.allocValue())->mkString(std::move(component));
v.mkList(list);
} }
static RegisterPrimOp primop_splitVersion({ static RegisterPrimOp primop_splitVersion({
@ -4559,14 +4577,14 @@ void EvalState::createBaseEnv()
}); });
/* Add a value containing the current Nix expression search path. */ /* Add a value containing the current Nix expression search path. */
mkList(v, searchPath.elements.size()); auto list = buildList(searchPath.elements.size());
int n = 0; for (const auto & [n, i] : enumerate(searchPath.elements)) {
for (auto & i : searchPath.elements) {
auto attrs = buildBindings(2); auto attrs = buildBindings(2);
attrs.alloc("path").mkString(i.path.s); attrs.alloc("path").mkString(i.path.s);
attrs.alloc("prefix").mkString(i.prefix.s); attrs.alloc("prefix").mkString(i.prefix.s);
(v.listElems()[n++] = allocValue())->mkAttrs(attrs); (list[n] = allocValue())->mkAttrs(attrs);
} }
v.mkList(list);
addConstant("__nixPath", v, { addConstant("__nixPath", v, {
.type = nList, .type = nList,
.doc = R"( .doc = R"(

View file

@ -207,10 +207,10 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
if (info.second.allOutputs) if (info.second.allOutputs)
infoAttrs.alloc(sAllOutputs).mkBool(true); infoAttrs.alloc(sAllOutputs).mkBool(true);
if (!info.second.outputs.empty()) { if (!info.second.outputs.empty()) {
auto & outputsVal = infoAttrs.alloc(state.sOutputs); auto list = state.buildList(info.second.outputs.size());
state.mkList(outputsVal, info.second.outputs.size());
for (const auto & [i, output] : enumerate(info.second.outputs)) for (const auto & [i, output] : enumerate(info.second.outputs))
(outputsVal.listElems()[i] = state.allocValue())->mkString(output); (list[i] = state.allocValue())->mkString(output);
infoAttrs.alloc(state.sOutputs).mkList(list);
} }
attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs); attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs);
} }

View file

@ -38,10 +38,10 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
{ {
auto array = toml::get<std::vector<toml::value>>(t); auto array = toml::get<std::vector<toml::value>>(t);
size_t size = array.size(); auto list = state.buildList(array.size());
state.mkList(v, size); for (const auto & [n, v] : enumerate(list))
for (size_t i = 0; i < size; ++i) visit(*(v = state.allocValue()), array[n]);
visit(*(v.listElems()[i] = state.allocValue()), array[i]); v.mkList(list);
} }
break;; break;;
case toml::value_t::boolean: case toml::value_t::boolean:

View file

@ -18,6 +18,7 @@
namespace nix { namespace nix {
struct Value;
class BindingsBuilder; class BindingsBuilder;
@ -134,6 +135,34 @@ class ExternalValueBase
std::ostream & operator << (std::ostream & str, const ExternalValueBase & v); std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
class ListBuilder
{
const size_t size;
Value * inlineElems[2] = {nullptr, nullptr};
public:
Value * * elems;
ListBuilder(EvalState & state, size_t size);
ListBuilder(ListBuilder && x)
: size(x.size)
, inlineElems{x.inlineElems[0], x.inlineElems[1]}
, elems(size <= 2 ? inlineElems : x.elems)
{ }
Value * & operator [](size_t n)
{
return elems[n];
}
typedef Value * * iterator;
iterator begin() { return &elems[0]; }
iterator end() { return &elems[size]; }
friend class Value;
};
struct Value struct Value
{ {
private: private:
@ -323,16 +352,20 @@ public:
Value & mkAttrs(BindingsBuilder & bindings); Value & mkAttrs(BindingsBuilder & bindings);
inline void mkList(size_t size) void mkList(const ListBuilder & builder)
{ {
clearValue(); clearValue();
if (size == 1) if (builder.size == 1) {
smallList[0] = builder.inlineElems[0];
internalType = tList1; internalType = tList1;
else if (size == 2) } else if (builder.size == 2) {
smallList[0] = builder.inlineElems[0];
smallList[1] = builder.inlineElems[1];
internalType = tList2; internalType = tList2;
else { } else {
bigList.size = builder.size;
bigList.elems = builder.elems;
internalType = tListN; internalType = tListN;
bigList.size = size;
} }
} }

View file

@ -172,7 +172,7 @@ static void loadSourceExpr(EvalState & state, const SourcePath & path, Value & v
directory). */ directory). */
else if (st.type == InputAccessor::tDirectory) { else if (st.type == InputAccessor::tDirectory) {
auto attrs = state.buildBindings(maxAttrs); auto attrs = state.buildBindings(maxAttrs);
state.mkList(attrs.alloc("_combineChannels"), 0); attrs.insert(state.symbols.create("_combineChannels"), &state.vEmptyList);
StringSet seen; StringSet seen;
getAllExprs(state, path, seen, attrs); getAllExprs(state, path, seen, attrs);
v.mkAttrs(attrs); v.mkAttrs(attrs);

View file

@ -49,10 +49,8 @@ bool createUserEnv(EvalState & state, PackageInfos & elems,
/* Construct the whole top level derivation. */ /* Construct the whole top level derivation. */
StorePathSet references; StorePathSet references;
Value manifest; auto list = state.buildList(elems.size());
state.mkList(manifest, elems.size()); for (const auto & [n, i] : enumerate(elems)) {
size_t n = 0;
for (auto & i : elems) {
/* Create a pseudo-derivation containing the name, system, /* Create a pseudo-derivation containing the name, system,
output paths, and optionally the derivation path, as well output paths, and optionally the derivation path, as well
as the meta attributes. */ as the meta attributes. */
@ -72,10 +70,9 @@ bool createUserEnv(EvalState & state, PackageInfos & elems,
attrs.alloc(state.sDrvPath).mkString(state.store->printStorePath(*drvPath)); attrs.alloc(state.sDrvPath).mkString(state.store->printStorePath(*drvPath));
// Copy each output meant for installation. // Copy each output meant for installation.
auto & vOutputs = attrs.alloc(state.sOutputs); auto outputsList = state.buildList(outputs.size());
state.mkList(vOutputs, outputs.size());
for (const auto & [m, j] : enumerate(outputs)) { for (const auto & [m, j] : enumerate(outputs)) {
(vOutputs.listElems()[m] = state.allocValue())->mkString(j.first); (outputsList[m] = state.allocValue())->mkString(j.first);
auto outputAttrs = state.buildBindings(2); auto outputAttrs = state.buildBindings(2);
outputAttrs.alloc(state.sOutPath).mkString(state.store->printStorePath(*j.second)); outputAttrs.alloc(state.sOutPath).mkString(state.store->printStorePath(*j.second));
attrs.alloc(j.first).mkAttrs(outputAttrs); attrs.alloc(j.first).mkAttrs(outputAttrs);
@ -87,6 +84,7 @@ bool createUserEnv(EvalState & state, PackageInfos & elems,
references.insert(*j.second); references.insert(*j.second);
} }
attrs.alloc(state.sOutputs).mkList(outputsList);
// Copy the meta attributes. // Copy the meta attributes.
auto meta = state.buildBindings(metaNames.size()); auto meta = state.buildBindings(metaNames.size());
@ -98,11 +96,14 @@ bool createUserEnv(EvalState & state, PackageInfos & elems,
attrs.alloc(state.sMeta).mkAttrs(meta); attrs.alloc(state.sMeta).mkAttrs(meta);
(manifest.listElems()[n++] = state.allocValue())->mkAttrs(attrs); (list[n] = state.allocValue())->mkAttrs(attrs);
if (drvPath) references.insert(*drvPath); if (drvPath) references.insert(*drvPath);
} }
Value manifest;
manifest.mkList(list);
/* Also write a copy of the list of user environment elements to /* Also write a copy of the list of user environment elements to
the store; we need it for future modifications of the the store; we need it for future modifications of the
environment. */ environment. */

View file

@ -79,11 +79,11 @@ TEST_F(ValuePrintingTests, tList)
Value vTwo; Value vTwo;
vTwo.mkInt(2); vTwo.mkInt(2);
auto list = state.buildList(3);
list.elems[0] = &vOne;
list.elems[1] = &vTwo;
Value vList; Value vList;
state.mkList(vList, 5); vList.mkList(list);
vList.bigList.elems[0] = &vOne;
vList.bigList.elems[1] = &vTwo;
vList.bigList.size = 3;
test(vList, "[ 1 2 «nullptr» ]"); test(vList, "[ 1 2 «nullptr» ]");
} }
@ -249,12 +249,12 @@ TEST_F(ValuePrintingTests, depthList)
Value vNested; Value vNested;
vNested.mkAttrs(builder2.finish()); vNested.mkAttrs(builder2.finish());
auto list = state.buildList(3);
list.elems[0] = &vOne;
list.elems[1] = &vTwo;
list.elems[2] = &vNested;
Value vList; Value vList;
state.mkList(vList, 5); vList.mkList(list);
vList.bigList.elems[0] = &vOne;
vList.bigList.elems[1] = &vTwo;
vList.bigList.elems[2] = &vNested;
vList.bigList.size = 3;
test(vList, "[ 1 2 { ... } ]", PrintOptions { .maxDepth = 1 }); test(vList, "[ 1 2 { ... } ]", PrintOptions { .maxDepth = 1 });
test(vList, "[ 1 2 { nested = { ... }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 2 }); test(vList, "[ 1 2 { nested = { ... }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 2 });
@ -539,11 +539,11 @@ TEST_F(ValuePrintingTests, ansiColorsList)
Value vTwo; Value vTwo;
vTwo.mkInt(2); vTwo.mkInt(2);
auto list = state.buildList(3);
list.elems[0] = &vOne;
list.elems[1] = &vTwo;
Value vList; Value vList;
state.mkList(vList, 5); vList.mkList(list);
vList.bigList.elems[0] = &vOne;
vList.bigList.elems[1] = &vTwo;
vList.bigList.size = 3;
test(vList, test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_CYAN "2" ANSI_NORMAL " " ANSI_MAGENTA "«nullptr»" ANSI_NORMAL " ]", "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_CYAN "2" ANSI_NORMAL " " ANSI_MAGENTA "«nullptr»" ANSI_NORMAL " ]",
@ -670,11 +670,11 @@ TEST_F(ValuePrintingTests, ansiColorsListRepeated)
Value vEmpty; Value vEmpty;
vEmpty.mkAttrs(emptyBuilder.finish()); vEmpty.mkAttrs(emptyBuilder.finish());
auto list = state.buildList(2);
list.elems[0] = &vEmpty;
list.elems[1] = &vEmpty;
Value vList; Value vList;
state.mkList(vList, 3); vList.mkList(list);
vList.bigList.elems[0] = &vEmpty;
vList.bigList.elems[1] = &vEmpty;
vList.bigList.size = 2;
test(vList, test(vList,
"[ { } " ANSI_MAGENTA "«repeated»" ANSI_NORMAL " ]", "[ { } " ANSI_MAGENTA "«repeated»" ANSI_NORMAL " ]",
@ -690,11 +690,11 @@ TEST_F(ValuePrintingTests, listRepeated)
Value vEmpty; Value vEmpty;
vEmpty.mkAttrs(emptyBuilder.finish()); vEmpty.mkAttrs(emptyBuilder.finish());
auto list = state.buildList(2);
list.elems[0] = &vEmpty;
list.elems[1] = &vEmpty;
Value vList; Value vList;
state.mkList(vList, 3); vList.mkList(list);
vList.bigList.elems[0] = &vEmpty;
vList.bigList.elems[1] = &vEmpty;
vList.bigList.size = 2;
test(vList, "[ { } «repeated» ]", PrintOptions { }); test(vList, "[ { } «repeated» ]", PrintOptions { });
test(vList, test(vList,
@ -750,11 +750,12 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
Value vTwo; Value vTwo;
vTwo.mkInt(2); vTwo.mkInt(2);
{
auto list = state.buildList(2);
list.elems[0] = &vOne;
list.elems[1] = &vTwo;
Value vList; Value vList;
state.mkList(vList, 4); vList.mkList(list);
vList.bigList.elems[0] = &vOne;
vList.bigList.elems[1] = &vTwo;
vList.bigList.size = 2;
test(vList, test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«1 item elided»" ANSI_NORMAL " ]", "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«1 item elided»" ANSI_NORMAL " ]",
@ -762,12 +763,18 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
.ansiColors = true, .ansiColors = true,
.maxListItems = 1 .maxListItems = 1
}); });
}
Value vThree; Value vThree;
vThree.mkInt(3); vThree.mkInt(3);
vList.bigList.elems[2] = &vThree; {
vList.bigList.size = 3; auto list = state.buildList(3);
list.elems[0] = &vOne;
list.elems[1] = &vTwo;
list.elems[2] = &vThree;
Value vList;
vList.mkList(list);
test(vList, test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«2 items elided»" ANSI_NORMAL " ]", "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«2 items elided»" ANSI_NORMAL " ]",
@ -775,6 +782,7 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
.ansiColors = true, .ansiColors = true,
.maxListItems = 1 .maxListItems = 1
}); });
}
} }
} // namespace nix } // namespace nix