Skip to content

Commit a786c9e

Browse files
authored
Merge pull request #14442 from glittershark/pascal-strings
Use hybrid C / Pascal strings in the evaluator
2 parents cbe8ec7 + 3bf8c76 commit a786c9e

File tree

15 files changed

+280
-100
lines changed

15 files changed

+280
-100
lines changed

src/libexpr-test-support/include/nix/expr/tests/libexpr.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ MATCHER(IsAttrs, "")
104104
MATCHER_P(IsStringEq, s, fmt("The string is equal to \"%1%\"", s))
105105
{
106106
if (arg.type() != nString) {
107+
*result_listener << "Expected a string got " << arg.type();
107108
return false;
108109
}
109110
return arg.string_view() == s;

src/libexpr-tests/json.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "nix/expr/tests/libexpr.hh"
22
#include "nix/expr/value-to-json.hh"
3+
#include "nix/expr/static-string-data.hh"
34

45
namespace nix {
56
// Testing the conversion to JSON
@@ -54,15 +55,15 @@ TEST_F(JSONValueTest, IntNegative)
5455
TEST_F(JSONValueTest, String)
5556
{
5657
Value v;
57-
v.mkStringNoCopy("test");
58+
v.mkStringNoCopy("test"_sds);
5859
ASSERT_EQ(getJSONValue(v), "\"test\"");
5960
}
6061

6162
TEST_F(JSONValueTest, StringQuotes)
6263
{
6364
Value v;
6465

65-
v.mkStringNoCopy("test\"");
66+
v.mkStringNoCopy("test\""_sds);
6667
ASSERT_EQ(getJSONValue(v), "\"test\\\"\"");
6768
}
6869

src/libexpr-tests/value/print.cc

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "nix/expr/tests/libexpr.hh"
2+
#include "nix/expr/static-string-data.hh"
23

34
#include "nix/expr/value.hh"
45
#include "nix/expr/print.hh"
@@ -35,14 +36,14 @@ TEST_F(ValuePrintingTests, tBool)
3536
TEST_F(ValuePrintingTests, tString)
3637
{
3738
Value vString;
38-
vString.mkStringNoCopy("some-string");
39+
vString.mkStringNoCopy("some-string"_sds);
3940
test(vString, "\"some-string\"");
4041
}
4142

4243
TEST_F(ValuePrintingTests, tPath)
4344
{
4445
Value vPath;
45-
vPath.mkStringNoCopy("/foo");
46+
vPath.mkStringNoCopy("/foo"_sds);
4647
test(vPath, "\"/foo\"");
4748
}
4849

@@ -289,10 +290,10 @@ TEST_F(StringPrintingTests, maxLengthTruncation)
289290
TEST_F(ValuePrintingTests, attrsTypeFirst)
290291
{
291292
Value vType;
292-
vType.mkStringNoCopy("puppy");
293+
vType.mkStringNoCopy("puppy"_sds);
293294

294295
Value vApple;
295-
vApple.mkStringNoCopy("apple");
296+
vApple.mkStringNoCopy("apple"_sds);
296297

297298
BindingsBuilder builder = state.buildBindings(10);
298299
builder.insert(state.symbols.create("type"), &vType);
@@ -333,15 +334,15 @@ TEST_F(ValuePrintingTests, ansiColorsBool)
333334
TEST_F(ValuePrintingTests, ansiColorsString)
334335
{
335336
Value v;
336-
v.mkStringNoCopy("puppy");
337+
v.mkStringNoCopy("puppy"_sds);
337338

338339
test(v, ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL, PrintOptions{.ansiColors = true});
339340
}
340341

341342
TEST_F(ValuePrintingTests, ansiColorsStringElided)
342343
{
343344
Value v;
344-
v.mkStringNoCopy("puppy");
345+
v.mkStringNoCopy("puppy"_sds);
345346

346347
test(
347348
v,
@@ -389,7 +390,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrs)
389390
TEST_F(ValuePrintingTests, ansiColorsDerivation)
390391
{
391392
Value vDerivation;
392-
vDerivation.mkStringNoCopy("derivation");
393+
vDerivation.mkStringNoCopy("derivation"_sds);
393394

394395
BindingsBuilder builder = state.buildBindings(10);
395396
builder.insert(state.s.type, &vDerivation);
@@ -412,7 +413,7 @@ TEST_F(ValuePrintingTests, ansiColorsError)
412413
{
413414
Value throw_ = state.getBuiltin("throw");
414415
Value message;
415-
message.mkStringNoCopy("uh oh!");
416+
message.mkStringNoCopy("uh oh!"_sds);
416417
Value vError;
417418
vError.mkApp(&throw_, &message);
418419

@@ -429,12 +430,12 @@ TEST_F(ValuePrintingTests, ansiColorsDerivationError)
429430
{
430431
Value throw_ = state.getBuiltin("throw");
431432
Value message;
432-
message.mkStringNoCopy("uh oh!");
433+
message.mkStringNoCopy("uh oh!"_sds);
433434
Value vError;
434435
vError.mkApp(&throw_, &message);
435436

436437
Value vDerivation;
437-
vDerivation.mkStringNoCopy("derivation");
438+
vDerivation.mkStringNoCopy("derivation"_sds);
438439

439440
BindingsBuilder builder = state.buildBindings(10);
440441
builder.insert(state.s.type, &vDerivation);

src/libexpr-tests/value/value.cc

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "nix/expr/value.hh"
2+
#include "nix/expr/static-string-data.hh"
23

34
#include "nix/store/tests/libstore.hh"
45
#include <gtest/gtest.h>
@@ -27,17 +28,17 @@ TEST_F(ValueTest, staticString)
2728
{
2829
Value vStr1;
2930
Value vStr2;
30-
vStr1.mkStringNoCopy("foo");
31-
vStr2.mkStringNoCopy("foo");
31+
vStr1.mkStringNoCopy("foo"_sds);
32+
vStr2.mkStringNoCopy("foo"_sds);
3233

33-
auto sd1 = vStr1.string_view();
34-
auto sd2 = vStr2.string_view();
34+
auto & sd1 = vStr1.string_data();
35+
auto & sd2 = vStr2.string_data();
3536

3637
// The strings should be the same
37-
ASSERT_EQ(sd1, sd2);
38+
ASSERT_EQ(sd1.view(), sd2.view());
3839

3940
// The strings should also be backed by the same (static) allocation
40-
ASSERT_EQ(sd1.data(), sd2.data());
41+
ASSERT_EQ(&sd1, &sd2);
4142
}
4243

4344
} // namespace nix

src/libexpr/eval-cache.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ struct AttrDb
147147
for (auto * elem : *context) {
148148
if (!first)
149149
ctx.push_back(' ');
150-
ctx.append(elem);
150+
ctx.append(elem->view());
151151
first = false;
152152
}
153153
state->insertAttributeWithContext.use()(key.first)(symbols[key.second])(AttrType::String) (s) (ctx)

src/libexpr/eval.cc

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "nix/expr/primops.hh"
44
#include "nix/expr/print-options.hh"
55
#include "nix/expr/symbol-table.hh"
6+
#include "nix/expr/value.hh"
67
#include "nix/util/exit.hh"
78
#include "nix/util/types.hh"
89
#include "nix/util/util.hh"
@@ -28,6 +29,8 @@
2829
#include "parser-tab.hh"
2930

3031
#include <algorithm>
32+
#include <cstddef>
33+
#include <cstdlib>
3134
#include <iostream>
3235
#include <sstream>
3336
#include <cstring>
@@ -48,6 +51,9 @@ using json = nlohmann::json;
4851

4952
namespace nix {
5053

54+
/**
55+
* Just for doc strings. Not for regular string values.
56+
*/
5157
static char * allocString(size_t size)
5258
{
5359
char * t;
@@ -61,6 +67,9 @@ static char * allocString(size_t size)
6167
// string allocations.
6268
// This function handles makeImmutableString(std::string_view()) by returning
6369
// the empty string.
70+
/**
71+
* Just for doc strings. Not for regular string values.
72+
*/
6473
static const char * makeImmutableString(std::string_view s)
6574
{
6675
const size_t size = s.size();
@@ -72,6 +81,25 @@ static const char * makeImmutableString(std::string_view s)
7281
return t;
7382
}
7483

84+
StringData & StringData::alloc(size_t size)
85+
{
86+
void * t = GC_MALLOC_ATOMIC(sizeof(StringData) + size + 1);
87+
if (!t)
88+
throw std::bad_alloc();
89+
auto res = new (t) StringData(size);
90+
return *res;
91+
}
92+
93+
const StringData & StringData::make(std::string_view s)
94+
{
95+
if (s.empty())
96+
return ""_sds;
97+
auto & res = alloc(s.size());
98+
std::memcpy(&res.data_, s.data(), s.size());
99+
res.data_[s.size()] = '\0';
100+
return res;
101+
}
102+
75103
RootValue allocRootValue(Value * v)
76104
{
77105
return std::allocate_shared<Value *>(traceable_allocator<Value *>(), v);
@@ -585,7 +613,9 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
585613
.name = name,
586614
.arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though...
587615
.args = {},
588-
.doc = makeImmutableString(s.view()), // NOTE: memory leak when compiled without GC
616+
/* N.B. Can't use StringData here, because that would lead to an interior pointer.
617+
NOTE: memory leak when compiled without GC. */
618+
.doc = makeImmutableString(s.view()),
589619
};
590620
}
591621
if (isFunctor(v)) {
@@ -819,7 +849,7 @@ DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
819849

820850
void Value::mkString(std::string_view s)
821851
{
822-
mkStringNoCopy(makeImmutableString(s));
852+
mkStringNoCopy(StringData::make(s));
823853
}
824854

825855
Value::StringWithContext::Context * Value::StringWithContext::Context::fromBuilder(const NixStringContext & context)
@@ -829,23 +859,23 @@ Value::StringWithContext::Context * Value::StringWithContext::Context::fromBuild
829859

830860
auto ctx = new (allocBytes(sizeof(Context) + context.size() * sizeof(value_type))) Context(context.size());
831861
std::ranges::transform(
832-
context, ctx->elems, [](const NixStringContextElem & elt) { return makeImmutableString(elt.to_string()); });
862+
context, ctx->elems, [](const NixStringContextElem & elt) { return &StringData::make(elt.to_string()); });
833863
return ctx;
834864
}
835865

836866
void Value::mkString(std::string_view s, const NixStringContext & context)
837867
{
838-
mkStringNoCopy(makeImmutableString(s), Value::StringWithContext::Context::fromBuilder(context));
868+
mkStringNoCopy(StringData::make(s), Value::StringWithContext::Context::fromBuilder(context));
839869
}
840870

841-
void Value::mkStringMove(const char * s, const NixStringContext & context)
871+
void Value::mkStringMove(const StringData & s, const NixStringContext & context)
842872
{
843873
mkStringNoCopy(s, Value::StringWithContext::Context::fromBuilder(context));
844874
}
845875

846876
void Value::mkPath(const SourcePath & path)
847877
{
848-
mkPath(&*path.accessor, makeImmutableString(path.path.abs()));
878+
mkPath(&*path.accessor, StringData::make(path.path.abs()));
849879
}
850880

851881
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
@@ -2099,21 +2129,21 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
20992129
.atPos(pos)
21002130
.withFrame(env, *this)
21012131
.debugThrow();
2102-
std::string result_str;
2103-
result_str.reserve(sSize);
2132+
std::string resultStr;
2133+
resultStr.reserve(sSize);
21042134
for (const auto & part : strings) {
2105-
result_str += *part;
2135+
resultStr += *part;
21062136
}
2107-
v.mkPath(state.rootPath(CanonPath(result_str)));
2137+
v.mkPath(state.rootPath(CanonPath(resultStr)));
21082138
} else {
2109-
char * result_str = allocString(sSize + 1);
2110-
char * tmp = result_str;
2139+
auto & resultStr = StringData::alloc(sSize);
2140+
auto * tmp = resultStr.data();
21112141
for (const auto & part : strings) {
2112-
memcpy(tmp, part->data(), part->size());
2142+
std::memcpy(tmp, part->data(), part->size());
21132143
tmp += part->size();
21142144
}
2115-
*tmp = 0;
2116-
v.mkStringMove(result_str, context);
2145+
*tmp = '\0';
2146+
v.mkStringMove(resultStr, context);
21172147
}
21182148
}
21192149

@@ -2288,7 +2318,7 @@ void copyContext(const Value & v, NixStringContext & context, const Experimental
22882318
{
22892319
if (auto * ctx = v.context())
22902320
for (auto * elem : *ctx)
2291-
context.insert(NixStringContextElem::parse(elem, xpSettings));
2321+
context.insert(NixStringContextElem::parse(elem->view(), xpSettings));
22922322
}
22932323

22942324
std::string_view EvalState::forceString(

src/libexpr/include/nix/expr/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ headers = [ config_pub_h ] + files(
3131
'print.hh',
3232
'repl-exit-status.hh',
3333
'search-path.hh',
34+
'static-string-data.hh',
3435
'symbol-table.hh',
3536
'value-to-json.hh',
3637
'value-to-xml.hh',

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

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <map>
55
#include <span>
6+
#include <memory>
67
#include <vector>
78
#include <memory_resource>
89
#include <algorithm>
@@ -11,6 +12,7 @@
1112
#include "nix/expr/value.hh"
1213
#include "nix/expr/symbol-table.hh"
1314
#include "nix/expr/eval-error.hh"
15+
#include "nix/expr/static-string-data.hh"
1416
#include "nix/util/pos-idx.hh"
1517
#include "nix/expr/counter.hh"
1618
#include "nix/util/pos-table.hh"
@@ -186,22 +188,18 @@ struct ExprString : Expr
186188
* This is only for strings already allocated in our polymorphic allocator,
187189
* or that live at least that long (e.g. c++ string literals)
188190
*/
189-
ExprString(const char * s)
191+
ExprString(const StringData & s)
190192
{
191193
v.mkStringNoCopy(s);
192194
};
193195

194196
ExprString(std::pmr::polymorphic_allocator<char> & alloc, std::string_view sv)
195197
{
196-
auto len = sv.length();
197-
if (len == 0) {
198-
v.mkStringNoCopy("");
198+
if (sv.size() == 0) {
199+
v.mkStringNoCopy(""_sds);
199200
return;
200201
}
201-
char * s = alloc.allocate(len + 1);
202-
sv.copy(s, len);
203-
s[len] = '\0';
204-
v.mkStringNoCopy(s);
202+
v.mkStringNoCopy(StringData::make(*alloc.resource(), sv));
205203
};
206204

207205
Value * maybeThunk(EvalState & state, Env & env) override;
@@ -216,11 +214,7 @@ struct ExprPath : Expr
216214
ExprPath(std::pmr::polymorphic_allocator<char> & alloc, ref<SourceAccessor> accessor, std::string_view sv)
217215
: accessor(accessor)
218216
{
219-
auto len = sv.length();
220-
char * s = alloc.allocate(len + 1);
221-
sv.copy(s, len);
222-
s[len] = '\0';
223-
v.mkPath(&*accessor, s);
217+
v.mkPath(&*accessor, StringData::make(*alloc.resource(), sv));
224218
}
225219

226220
Value * maybeThunk(EvalState & state, Env & env) override;

0 commit comments

Comments
 (0)