Skip to content

Commit fa3d7e6

Browse files
committed
libexpr: don't allocate additional set in builtins.listToAttrs
1 parent 46853c4 commit fa3d7e6

File tree

1 file changed

+32
-10
lines changed

1 file changed

+32
-10
lines changed

src/libexpr/primops.cc

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2961,25 +2961,47 @@ static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value * * args
29612961
{
29622962
state.forceList(*args[0], pos, "while evaluating the argument passed to builtins.listToAttrs");
29632963

2964-
auto attrs = state.buildBindings(args[0]->listSize());
2964+
// Step 1. Sort the name-value attrsets in place using the memory we allocate for the result
2965+
size_t listSize = args[0]->listSize();
2966+
auto & bindings = *state.allocBindings(listSize);
2967+
using ElemPtr = decltype(&bindings[0].value);
29652968

2966-
std::set<Symbol> seen;
2967-
2968-
for (auto v2 : args[0]->listItems()) {
2969+
for (const auto & [n, v2] : enumerate(args[0]->listItems())) {
29692970
state.forceAttrs(*v2, pos, "while evaluating an element of the list passed to builtins.listToAttrs");
29702971

29712972
auto j = state.getAttr(state.sName, v2->attrs(), "in a {name=...; value=...;} pair");
29722973

29732974
auto name = state.forceStringNoCtx(*j->value, j->pos, "while evaluating the `name` attribute of an element of the list passed to builtins.listToAttrs");
2974-
29752975
auto sym = state.symbols.create(name);
2976-
if (seen.insert(sym).second) {
2977-
auto j2 = state.getAttr(state.sValue, v2->attrs(), "in a {name=...; value=...;} pair");
2978-
attrs.insert(sym, j2->value, j2->pos);
2979-
}
2976+
2977+
// (ab)use Attr to store a Value * * instead of a Value *, so that we can stabilize the sort using the Value * *
2978+
bindings[n] = Attr(sym, std::bit_cast<Value *>(&v2));
29802979
}
29812980

2982-
v.mkAttrs(attrs);
2981+
std::sort(&bindings[0], &bindings[listSize], [](const Attr & a, const Attr & b) {
2982+
// Note that .value is actually a Value * * that corresponds to the position in the list
2983+
return a < b || (!(a > b) && std::bit_cast<ElemPtr>(a.value) < std::bit_cast<ElemPtr>(b.value));
2984+
});
2985+
2986+
// Step 2. Unpack the bindings in place and skip name-value pairs with duplicate names
2987+
Symbol prev;
2988+
for (size_t n = 0; n < listSize; n++) {
2989+
auto attr = bindings[n];
2990+
if (prev == attr.name) {
2991+
continue;
2992+
}
2993+
// Note that .value is actually a Value * *; see earlier comments
2994+
Value * v2 = *std::bit_cast<ElemPtr>(attr.value);
2995+
2996+
auto j = state.getAttr(state.sValue, v2->attrs(), "in a {name=...; value=...;} pair");
2997+
prev = attr.name;
2998+
bindings.push_back({prev, j->value, j->pos});
2999+
}
3000+
// help GC and clear end of allocated array
3001+
for (size_t n = bindings.size(); n < listSize; n++) {
3002+
bindings[n] = Attr{};
3003+
}
3004+
v.mkAttrs(&bindings);
29833005
}
29843006

29853007
static RegisterPrimOp primop_listToAttrs({

0 commit comments

Comments
 (0)