Skip to content

Commit 318eea0

Browse files
Ericson2314xokdvium
andcommitted
Encapsulate and slightly optimize string contexts
These steps are done (originally in order, but I squashed it as the end result is still pretty small, and the churn in the code comments was a bit annoying to keep straight). 1. Create proper struct type for string contexts on the heap This will make it easier to change this type in the future. 2. Make `Value::StringWithContext` iterable This make some for loops a lot more terse. 3. Encapsulate `Value::StringWithContext::Context::elems` It turns out the iterators we just exposed are sufficient. 4. Make `StringWithContext::Context` length-prefixed instead Rather than having a null pointer at the end, have a `size_t` at the beginning. This is the exact same size (note that null pointer is longer than null byte) and thus takes no more space! Also, see the new TODO on naming. The thing we already so-named is a builder type for string contexts, not the on-heap type. The `fromBuilder` static method reflects what the names ought to be too. Co-authored-by: Sergei Zimmerman <[email protected]>
1 parent 6ebaba5 commit 318eea0

File tree

4 files changed

+97
-23
lines changed

4 files changed

+97
-23
lines changed

src/libexpr/eval-cache.cc

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,19 @@ struct AttrDb
136136
});
137137
}
138138

139-
AttrId setString(AttrKey key, std::string_view s, const char ** context = nullptr)
139+
AttrId setString(AttrKey key, std::string_view s, const Value::StringWithContext::Context * context = nullptr)
140140
{
141141
return doSQLite([&]() {
142142
auto state(_state->lock());
143143

144144
if (context) {
145145
std::string ctx;
146-
for (const char ** p = context; *p; ++p) {
147-
if (p != context)
146+
bool first = true;
147+
for (auto * elem : *context) {
148+
if (!first)
148149
ctx.push_back(' ');
149-
ctx.append(*p);
150+
ctx.append(elem);
151+
first = false;
150152
}
151153
state->insertAttributeWithContext.use()(key.first)(symbols[key.second])(AttrType::String) (s) (ctx)
152154
.exec();

src/libexpr/eval.cc

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <sys/time.h>
3737
#include <fstream>
3838
#include <functional>
39+
#include <ranges>
3940

4041
#include <nlohmann/json.hpp>
4142
#include <boost/container/small_vector.hpp>
@@ -821,28 +822,25 @@ void Value::mkString(std::string_view s)
821822
mkStringNoCopy(makeImmutableString(s));
822823
}
823824

824-
static const char ** encodeContext(const NixStringContext & context)
825+
Value::StringWithContext::Context * Value::StringWithContext::Context::fromBuilder(const NixStringContext & context)
825826
{
826-
if (!context.empty()) {
827-
size_t n = 0;
828-
auto ctx = (const char **) allocBytes((context.size() + 1) * sizeof(char *));
829-
for (auto & i : context) {
830-
ctx[n++] = makeImmutableString({i.to_string()});
831-
}
832-
ctx[n] = nullptr;
833-
return ctx;
834-
} else
827+
if (context.empty())
835828
return nullptr;
829+
830+
auto ctx = new (allocBytes(sizeof(Context) + context.size() * sizeof(value_type))) Context(context.size());
831+
std::ranges::transform(
832+
context, ctx->elems, [](const NixStringContextElem & elt) { return makeImmutableString(elt.to_string()); });
833+
return ctx;
836834
}
837835

838836
void Value::mkString(std::string_view s, const NixStringContext & context)
839837
{
840-
mkStringNoCopy(makeImmutableString(s), encodeContext(context));
838+
mkStringNoCopy(makeImmutableString(s), Value::StringWithContext::Context::fromBuilder(context));
841839
}
842840

843841
void Value::mkStringMove(const char * s, const NixStringContext & context)
844842
{
845-
mkStringNoCopy(s, encodeContext(context));
843+
mkStringNoCopy(s, Value::StringWithContext::Context::fromBuilder(context));
846844
}
847845

848846
void Value::mkPath(const SourcePath & path)
@@ -2287,9 +2285,9 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string
22872285

22882286
void copyContext(const Value & v, NixStringContext & context, const ExperimentalFeatureSettings & xpSettings)
22892287
{
2290-
if (v.context())
2291-
for (const char ** p = v.context(); *p; ++p)
2292-
context.insert(NixStringContextElem::parse(*p, xpSettings));
2288+
if (auto * ctx = v.context())
2289+
for (auto * elem : *ctx)
2290+
context.insert(NixStringContextElem::parse(elem, xpSettings));
22932291
}
22942292

22952293
std::string_view EvalState::forceString(
@@ -2309,7 +2307,9 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::s
23092307
auto s = forceString(v, pos, errorCtx);
23102308
if (v.context()) {
23112309
error<EvalError>(
2312-
"the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string_view(), v.context()[0])
2310+
"the string '%1%' is not allowed to refer to a store path (such as '%2%')",
2311+
v.string_view(),
2312+
*v.context()->begin())
23132313
.withTrace(pos, errorCtx)
23142314
.debugThrow();
23152315
}

src/libexpr/include/nix/expr/value.hh

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,66 @@ struct ValueBase
220220
struct StringWithContext
221221
{
222222
const char * c_str;
223-
const char ** context; // must be in sorted order
223+
224+
/**
225+
* The type of the context itself.
226+
*
227+
* Currently, it is length-prefixed array of pointers to
228+
* null-terminated strings. The strings are specially formatted
229+
* to represent a flattening of the recursive sum type that is a
230+
* context element.
231+
*
232+
* @See NixStringContext for an more easily understood type,
233+
* that of the "builder" for this data structure.
234+
*/
235+
struct Context
236+
{
237+
using value_type = const char *;
238+
using size_type = std::size_t;
239+
using iterator = const value_type *;
240+
241+
Context(size_type size)
242+
: size_(size)
243+
{
244+
}
245+
246+
private:
247+
/**
248+
* Number of items in the array
249+
*/
250+
size_type size_;
251+
252+
/**
253+
* @pre must be in sorted order
254+
*/
255+
value_type elems[];
256+
257+
public:
258+
iterator begin() const
259+
{
260+
return elems;
261+
}
262+
263+
iterator end() const
264+
{
265+
return elems + size();
266+
}
267+
268+
size_type size() const
269+
{
270+
return size_;
271+
}
272+
273+
/**
274+
* @return null pointer when context.empty()
275+
*/
276+
static Context * fromBuilder(const NixStringContext & context);
277+
};
278+
279+
/**
280+
* May be null for a string without context.
281+
*/
282+
const Context * context;
224283
};
225284

226285
struct Path
@@ -991,7 +1050,7 @@ public:
9911050
setStorage(b);
9921051
}
9931052

994-
void mkStringNoCopy(const char * s, const char ** context = 0) noexcept
1053+
void mkStringNoCopy(const char * s, const Value::StringWithContext::Context * context = nullptr) noexcept
9951054
{
9961055
setStorage(StringWithContext{.c_str = s, .context = context});
9971056
}
@@ -1117,7 +1176,7 @@ public:
11171176
return getStorage<StringWithContext>().c_str;
11181177
}
11191178

1120-
const char ** context() const noexcept
1179+
const Value::StringWithContext::Context * context() const noexcept
11211180
{
11221181
return getStorage<StringWithContext>().context;
11231182
}

src/libexpr/include/nix/expr/value/context.hh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ public:
2424
}
2525
};
2626

27+
/**
28+
* @todo This should be renamed to `StringContextBuilderElem`, since:
29+
*
30+
* 1. We use `*Builder` for off-heap temporary data structures
31+
*
32+
* 2. The `Nix*` is totally redundant. (And my mistake from a long time
33+
* ago.)
34+
*/
2735
struct NixStringContextElem
2836
{
2937
/**
@@ -77,6 +85,11 @@ struct NixStringContextElem
7785
std::string to_string() const;
7886
};
7987

88+
/**
89+
* @todo This should be renamed to `StringContextBuilder`.
90+
*
91+
* @see NixStringContextElem for explanation why.
92+
*/
8093
typedef std::set<NixStringContextElem> NixStringContext;
8194

8295
} // namespace nix

0 commit comments

Comments
 (0)