1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-11 13:06:01 +01:00

Use hybrid C / Pascal strings in the evaluator

Replace the null-terminated C-style strings in Value with hybrid C /
Pascal strings, where the length is stored in the allocation before the
data, and there is still a null byte at the end for the sake of C
interopt.

Co-Authored-By: Taeer Bar-Yam <taeer@bar-yam.me>
Co-Authored-By: Sergei Zimmerman <sergei@zimmerman.foo>
This commit is contained in:
Aspen Smith 2025-09-12 20:45:20 -04:00 committed by John Ericson
parent 8c113f80f3
commit 3bf8c76072
14 changed files with 279 additions and 100 deletions

View file

@ -1,5 +1,6 @@
#include "nix/expr/tests/libexpr.hh" #include "nix/expr/tests/libexpr.hh"
#include "nix/expr/value-to-json.hh" #include "nix/expr/value-to-json.hh"
#include "nix/expr/static-string-data.hh"
namespace nix { namespace nix {
// Testing the conversion to JSON // Testing the conversion to JSON
@ -54,7 +55,7 @@ TEST_F(JSONValueTest, IntNegative)
TEST_F(JSONValueTest, String) TEST_F(JSONValueTest, String)
{ {
Value v; Value v;
v.mkStringNoCopy("test"); v.mkStringNoCopy("test"_sds);
ASSERT_EQ(getJSONValue(v), "\"test\""); ASSERT_EQ(getJSONValue(v), "\"test\"");
} }
@ -62,7 +63,7 @@ TEST_F(JSONValueTest, StringQuotes)
{ {
Value v; Value v;
v.mkStringNoCopy("test\""); v.mkStringNoCopy("test\""_sds);
ASSERT_EQ(getJSONValue(v), "\"test\\\"\""); ASSERT_EQ(getJSONValue(v), "\"test\\\"\"");
} }

View file

@ -1,4 +1,5 @@
#include "nix/expr/tests/libexpr.hh" #include "nix/expr/tests/libexpr.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/expr/value.hh" #include "nix/expr/value.hh"
#include "nix/expr/print.hh" #include "nix/expr/print.hh"
@ -35,14 +36,14 @@ TEST_F(ValuePrintingTests, tBool)
TEST_F(ValuePrintingTests, tString) TEST_F(ValuePrintingTests, tString)
{ {
Value vString; Value vString;
vString.mkStringNoCopy("some-string"); vString.mkStringNoCopy("some-string"_sds);
test(vString, "\"some-string\""); test(vString, "\"some-string\"");
} }
TEST_F(ValuePrintingTests, tPath) TEST_F(ValuePrintingTests, tPath)
{ {
Value vPath; Value vPath;
vPath.mkStringNoCopy("/foo"); vPath.mkStringNoCopy("/foo"_sds);
test(vPath, "\"/foo\""); test(vPath, "\"/foo\"");
} }
@ -289,10 +290,10 @@ TEST_F(StringPrintingTests, maxLengthTruncation)
TEST_F(ValuePrintingTests, attrsTypeFirst) TEST_F(ValuePrintingTests, attrsTypeFirst)
{ {
Value vType; Value vType;
vType.mkStringNoCopy("puppy"); vType.mkStringNoCopy("puppy"_sds);
Value vApple; Value vApple;
vApple.mkStringNoCopy("apple"); vApple.mkStringNoCopy("apple"_sds);
BindingsBuilder builder = state.buildBindings(10); BindingsBuilder builder = state.buildBindings(10);
builder.insert(state.symbols.create("type"), &vType); builder.insert(state.symbols.create("type"), &vType);
@ -333,7 +334,7 @@ TEST_F(ValuePrintingTests, ansiColorsBool)
TEST_F(ValuePrintingTests, ansiColorsString) TEST_F(ValuePrintingTests, ansiColorsString)
{ {
Value v; Value v;
v.mkStringNoCopy("puppy"); v.mkStringNoCopy("puppy"_sds);
test(v, ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL, PrintOptions{.ansiColors = true}); test(v, ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL, PrintOptions{.ansiColors = true});
} }
@ -341,7 +342,7 @@ TEST_F(ValuePrintingTests, ansiColorsString)
TEST_F(ValuePrintingTests, ansiColorsStringElided) TEST_F(ValuePrintingTests, ansiColorsStringElided)
{ {
Value v; Value v;
v.mkStringNoCopy("puppy"); v.mkStringNoCopy("puppy"_sds);
test( test(
v, v,
@ -389,7 +390,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrs)
TEST_F(ValuePrintingTests, ansiColorsDerivation) TEST_F(ValuePrintingTests, ansiColorsDerivation)
{ {
Value vDerivation; Value vDerivation;
vDerivation.mkStringNoCopy("derivation"); vDerivation.mkStringNoCopy("derivation"_sds);
BindingsBuilder builder = state.buildBindings(10); BindingsBuilder builder = state.buildBindings(10);
builder.insert(state.s.type, &vDerivation); builder.insert(state.s.type, &vDerivation);
@ -412,7 +413,7 @@ TEST_F(ValuePrintingTests, ansiColorsError)
{ {
Value throw_ = state.getBuiltin("throw"); Value throw_ = state.getBuiltin("throw");
Value message; Value message;
message.mkStringNoCopy("uh oh!"); message.mkStringNoCopy("uh oh!"_sds);
Value vError; Value vError;
vError.mkApp(&throw_, &message); vError.mkApp(&throw_, &message);
@ -429,12 +430,12 @@ TEST_F(ValuePrintingTests, ansiColorsDerivationError)
{ {
Value throw_ = state.getBuiltin("throw"); Value throw_ = state.getBuiltin("throw");
Value message; Value message;
message.mkStringNoCopy("uh oh!"); message.mkStringNoCopy("uh oh!"_sds);
Value vError; Value vError;
vError.mkApp(&throw_, &message); vError.mkApp(&throw_, &message);
Value vDerivation; Value vDerivation;
vDerivation.mkStringNoCopy("derivation"); vDerivation.mkStringNoCopy("derivation"_sds);
BindingsBuilder builder = state.buildBindings(10); BindingsBuilder builder = state.buildBindings(10);
builder.insert(state.s.type, &vDerivation); builder.insert(state.s.type, &vDerivation);

View file

@ -1,4 +1,5 @@
#include "nix/expr/value.hh" #include "nix/expr/value.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/store/tests/libstore.hh" #include "nix/store/tests/libstore.hh"
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -27,17 +28,17 @@ TEST_F(ValueTest, staticString)
{ {
Value vStr1; Value vStr1;
Value vStr2; Value vStr2;
vStr1.mkStringNoCopy("foo"); vStr1.mkStringNoCopy("foo"_sds);
vStr2.mkStringNoCopy("foo"); vStr2.mkStringNoCopy("foo"_sds);
auto sd1 = vStr1.string_view(); auto & sd1 = vStr1.string_data();
auto sd2 = vStr2.string_view(); auto & sd2 = vStr2.string_data();
// The strings should be the same // The strings should be the same
ASSERT_EQ(sd1, sd2); ASSERT_EQ(sd1.view(), sd2.view());
// The strings should also be backed by the same (static) allocation // The strings should also be backed by the same (static) allocation
ASSERT_EQ(sd1.data(), sd2.data()); ASSERT_EQ(&sd1, &sd2);
} }
} // namespace nix } // namespace nix

View file

@ -147,7 +147,7 @@ struct AttrDb
for (auto * elem : *context) { for (auto * elem : *context) {
if (!first) if (!first)
ctx.push_back(' '); ctx.push_back(' ');
ctx.append(elem); ctx.append(elem->view());
first = false; first = false;
} }
state->insertAttributeWithContext.use()(key.first)(symbols[key.second])(AttrType::String) (s) (ctx) state->insertAttributeWithContext.use()(key.first)(symbols[key.second])(AttrType::String) (s) (ctx)

View file

@ -3,6 +3,7 @@
#include "nix/expr/primops.hh" #include "nix/expr/primops.hh"
#include "nix/expr/print-options.hh" #include "nix/expr/print-options.hh"
#include "nix/expr/symbol-table.hh" #include "nix/expr/symbol-table.hh"
#include "nix/expr/value.hh"
#include "nix/util/exit.hh" #include "nix/util/exit.hh"
#include "nix/util/types.hh" #include "nix/util/types.hh"
#include "nix/util/util.hh" #include "nix/util/util.hh"
@ -28,6 +29,8 @@
#include "parser-tab.hh" #include "parser-tab.hh"
#include <algorithm> #include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <cstring> #include <cstring>
@ -48,6 +51,9 @@ using json = nlohmann::json;
namespace nix { namespace nix {
/**
* Just for doc strings. Not for regular string values.
*/
static char * allocString(size_t size) static char * allocString(size_t size)
{ {
char * t; char * t;
@ -61,6 +67,9 @@ static char * allocString(size_t size)
// string allocations. // string allocations.
// This function handles makeImmutableString(std::string_view()) by returning // This function handles makeImmutableString(std::string_view()) by returning
// the empty string. // the empty string.
/**
* Just for doc strings. Not for regular string values.
*/
static const char * makeImmutableString(std::string_view s) static const char * makeImmutableString(std::string_view s)
{ {
const size_t size = s.size(); const size_t size = s.size();
@ -72,6 +81,25 @@ static const char * makeImmutableString(std::string_view s)
return t; return t;
} }
StringData & StringData::alloc(size_t size)
{
void * t = GC_MALLOC_ATOMIC(sizeof(StringData) + size + 1);
if (!t)
throw std::bad_alloc();
auto res = new (t) StringData(size);
return *res;
}
const StringData & StringData::make(std::string_view s)
{
if (s.empty())
return ""_sds;
auto & res = alloc(s.size());
std::memcpy(&res.data_, s.data(), s.size());
res.data_[s.size()] = '\0';
return res;
}
RootValue allocRootValue(Value * v) RootValue allocRootValue(Value * v)
{ {
return std::allocate_shared<Value *>(traceable_allocator<Value *>(), v); return std::allocate_shared<Value *>(traceable_allocator<Value *>(), v);
@ -585,7 +613,9 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
.name = name, .name = name,
.arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though... .arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though...
.args = {}, .args = {},
.doc = makeImmutableString(s.view()), // NOTE: memory leak when compiled without GC /* N.B. Can't use StringData here, because that would lead to an interior pointer.
NOTE: memory leak when compiled without GC. */
.doc = makeImmutableString(s.view()),
}; };
} }
if (isFunctor(v)) { if (isFunctor(v)) {
@ -819,7 +849,7 @@ DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
void Value::mkString(std::string_view s) void Value::mkString(std::string_view s)
{ {
mkStringNoCopy(makeImmutableString(s)); mkStringNoCopy(StringData::make(s));
} }
Value::StringWithContext::Context * Value::StringWithContext::Context::fromBuilder(const NixStringContext & context) Value::StringWithContext::Context * Value::StringWithContext::Context::fromBuilder(const NixStringContext & context)
@ -829,23 +859,23 @@ Value::StringWithContext::Context * Value::StringWithContext::Context::fromBuild
auto ctx = new (allocBytes(sizeof(Context) + context.size() * sizeof(value_type))) Context(context.size()); auto ctx = new (allocBytes(sizeof(Context) + context.size() * sizeof(value_type))) Context(context.size());
std::ranges::transform( std::ranges::transform(
context, ctx->elems, [](const NixStringContextElem & elt) { return makeImmutableString(elt.to_string()); }); context, ctx->elems, [](const NixStringContextElem & elt) { return &StringData::make(elt.to_string()); });
return ctx; return ctx;
} }
void Value::mkString(std::string_view s, const NixStringContext & context) void Value::mkString(std::string_view s, const NixStringContext & context)
{ {
mkStringNoCopy(makeImmutableString(s), Value::StringWithContext::Context::fromBuilder(context)); mkStringNoCopy(StringData::make(s), Value::StringWithContext::Context::fromBuilder(context));
} }
void Value::mkStringMove(const char * s, const NixStringContext & context) void Value::mkStringMove(const StringData & s, const NixStringContext & context)
{ {
mkStringNoCopy(s, Value::StringWithContext::Context::fromBuilder(context)); mkStringNoCopy(s, Value::StringWithContext::Context::fromBuilder(context));
} }
void Value::mkPath(const SourcePath & path) void Value::mkPath(const SourcePath & path)
{ {
mkPath(&*path.accessor, makeImmutableString(path.path.abs())); mkPath(&*path.accessor, StringData::make(path.path.abs()));
} }
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
@ -2099,21 +2129,21 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
.atPos(pos) .atPos(pos)
.withFrame(env, *this) .withFrame(env, *this)
.debugThrow(); .debugThrow();
std::string result_str; std::string resultStr;
result_str.reserve(sSize); resultStr.reserve(sSize);
for (const auto & part : strings) { for (const auto & part : strings) {
result_str += *part; resultStr += *part;
} }
v.mkPath(state.rootPath(CanonPath(result_str))); v.mkPath(state.rootPath(CanonPath(resultStr)));
} else { } else {
char * result_str = allocString(sSize + 1); auto & resultStr = StringData::alloc(sSize);
char * tmp = result_str; auto * tmp = resultStr.data();
for (const auto & part : strings) { for (const auto & part : strings) {
memcpy(tmp, part->data(), part->size()); std::memcpy(tmp, part->data(), part->size());
tmp += part->size(); tmp += part->size();
} }
*tmp = 0; *tmp = '\0';
v.mkStringMove(result_str, context); v.mkStringMove(resultStr, context);
} }
} }
@ -2288,7 +2318,7 @@ void copyContext(const Value & v, NixStringContext & context, const Experimental
{ {
if (auto * ctx = v.context()) if (auto * ctx = v.context())
for (auto * elem : *ctx) for (auto * elem : *ctx)
context.insert(NixStringContextElem::parse(elem, xpSettings)); context.insert(NixStringContextElem::parse(elem->view(), xpSettings));
} }
std::string_view EvalState::forceString( std::string_view EvalState::forceString(

View file

@ -31,6 +31,7 @@ headers = [ config_pub_h ] + files(
'print.hh', 'print.hh',
'repl-exit-status.hh', 'repl-exit-status.hh',
'search-path.hh', 'search-path.hh',
'static-string-data.hh',
'symbol-table.hh', 'symbol-table.hh',
'value-to-json.hh', 'value-to-json.hh',
'value-to-xml.hh', 'value-to-xml.hh',

View file

@ -3,6 +3,7 @@
#include <map> #include <map>
#include <span> #include <span>
#include <memory>
#include <vector> #include <vector>
#include <memory_resource> #include <memory_resource>
#include <algorithm> #include <algorithm>
@ -11,6 +12,7 @@
#include "nix/expr/value.hh" #include "nix/expr/value.hh"
#include "nix/expr/symbol-table.hh" #include "nix/expr/symbol-table.hh"
#include "nix/expr/eval-error.hh" #include "nix/expr/eval-error.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/util/pos-idx.hh" #include "nix/util/pos-idx.hh"
#include "nix/expr/counter.hh" #include "nix/expr/counter.hh"
#include "nix/util/pos-table.hh" #include "nix/util/pos-table.hh"
@ -186,22 +188,18 @@ struct ExprString : Expr
* This is only for strings already allocated in our polymorphic allocator, * This is only for strings already allocated in our polymorphic allocator,
* or that live at least that long (e.g. c++ string literals) * or that live at least that long (e.g. c++ string literals)
*/ */
ExprString(const char * s) ExprString(const StringData & s)
{ {
v.mkStringNoCopy(s); v.mkStringNoCopy(s);
}; };
ExprString(std::pmr::polymorphic_allocator<char> & alloc, std::string_view sv) ExprString(std::pmr::polymorphic_allocator<char> & alloc, std::string_view sv)
{ {
auto len = sv.length(); if (sv.size() == 0) {
if (len == 0) { v.mkStringNoCopy(""_sds);
v.mkStringNoCopy("");
return; return;
} }
char * s = alloc.allocate(len + 1); v.mkStringNoCopy(StringData::make(*alloc.resource(), sv));
sv.copy(s, len);
s[len] = '\0';
v.mkStringNoCopy(s);
}; };
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
@ -216,11 +214,7 @@ struct ExprPath : Expr
ExprPath(std::pmr::polymorphic_allocator<char> & alloc, ref<SourceAccessor> accessor, std::string_view sv) ExprPath(std::pmr::polymorphic_allocator<char> & alloc, ref<SourceAccessor> accessor, std::string_view sv)
: accessor(accessor) : accessor(accessor)
{ {
auto len = sv.length(); v.mkPath(&*accessor, StringData::make(*alloc.resource(), sv));
char * s = alloc.allocate(len + 1);
sv.copy(s, len);
s[len] = '\0';
v.mkPath(&*accessor, s);
} }
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;

View file

@ -4,6 +4,8 @@
#include <limits> #include <limits>
#include "nix/expr/eval.hh" #include "nix/expr/eval.hh"
#include "nix/expr/value.hh"
#include "nix/expr/static-string-data.hh"
namespace nix { namespace nix {
@ -240,7 +242,7 @@ inline Expr *
ParserState::stripIndentation(const PosIdx pos, std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> && es) ParserState::stripIndentation(const PosIdx pos, std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> && es)
{ {
if (es.empty()) if (es.empty())
return exprs.add<ExprString>(""); return exprs.add<ExprString>(""_sds);
/* Figure out the minimum indentation. Note that by design /* Figure out the minimum indentation. Note that by design
whitespace-only final lines are not taken into account. (So whitespace-only final lines are not taken into account. (So
@ -332,7 +334,7 @@ ParserState::stripIndentation(const PosIdx pos, std::vector<std::pair<PosIdx, st
// If there is nothing at all, return the empty string directly. // If there is nothing at all, return the empty string directly.
// This also ensures that equivalent empty strings result in the same ast, which is helpful when testing formatters. // This also ensures that equivalent empty strings result in the same ast, which is helpful when testing formatters.
if (es2.size() == 0) { if (es2.size() == 0) {
auto * const result = exprs.add<ExprString>(""); auto * const result = exprs.add<ExprString>(""_sds);
return result; return result;
} }

View file

@ -0,0 +1,44 @@
#pragma once
///@file
#include "nix/expr/value.hh"
namespace nix {
template<size_t N>
struct StringData::Static
{
/**
* @note Must be first to make layout compatible with StringData.
*/
const size_t size = N - 1;
char data[N];
consteval Static(const char (&str)[N])
{
static_assert(N > 0);
if (str[size] != '\0')
throw;
std::copy_n(str, N, data);
}
operator const StringData &() const &
{
static_assert(sizeof(decltype(*this)) >= sizeof(StringData));
static_assert(alignof(decltype(*this)) == alignof(StringData));
/* NOTE: This cast is somewhat on the fence of what's legal in C++.
The question boils down to whether flexible array members are
layout compatible with fixed-size arrays. This is a gray area, since
FAMs are not standard anyway.
*/
return *reinterpret_cast<const StringData *>(this);
}
};
template<StringData::Static S>
const StringData & operator""_sds()
{
return S;
}
} // namespace nix

View file

@ -3,6 +3,7 @@
#include <memory_resource> #include <memory_resource>
#include "nix/expr/value.hh" #include "nix/expr/value.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/util/chunked-vector.hh" #include "nix/util/chunked-vector.hh"
#include "nix/util/error.hh" #include "nix/util/error.hh"
@ -16,7 +17,6 @@ class SymbolValue : protected Value
friend class SymbolStr; friend class SymbolStr;
friend class SymbolTable; friend class SymbolTable;
uint32_t size_;
uint32_t idx; uint32_t idx;
SymbolValue() = default; SymbolValue() = default;
@ -24,7 +24,7 @@ class SymbolValue : protected Value
public: public:
operator std::string_view() const noexcept operator std::string_view() const noexcept
{ {
return {c_str(), size_}; return string_view();
} }
}; };
@ -96,13 +96,13 @@ class SymbolStr
SymbolValueStore & store; SymbolValueStore & store;
std::string_view s; std::string_view s;
std::size_t hash; std::size_t hash;
std::pmr::polymorphic_allocator<char> & alloc; std::pmr::memory_resource & resource;
Key(SymbolValueStore & store, std::string_view s, std::pmr::polymorphic_allocator<char> & stringAlloc) Key(SymbolValueStore & store, std::string_view s, std::pmr::memory_resource & stringMemory)
: store(store) : store(store)
, s(s) , s(s)
, hash(HashType{}(s)) , hash(HashType{}(s))
, alloc(stringAlloc) , resource(stringMemory)
{ {
} }
}; };
@ -122,14 +122,10 @@ public:
// for multi-threaded implementations: lock store and allocator here // for multi-threaded implementations: lock store and allocator here
const auto & [v, idx] = key.store.add(SymbolValue{}); const auto & [v, idx] = key.store.add(SymbolValue{});
if (size == 0) { if (size == 0) {
v.mkStringNoCopy("", nullptr); v.mkStringNoCopy(""_sds, nullptr);
} else { } else {
auto s = key.alloc.allocate(size + 1); v.mkStringNoCopy(StringData::make(key.resource, key.s));
memcpy(s, key.s.data(), size);
s[size] = '\0';
v.mkStringNoCopy(s, nullptr);
} }
v.size_ = size;
v.idx = idx; v.idx = idx;
this->s = &v; this->s = &v;
} }
@ -139,6 +135,12 @@ public:
return *s == s2; return *s == s2;
} }
[[gnu::always_inline]]
const StringData & string_data() const noexcept
{
return s->string_data();
}
[[gnu::always_inline]] [[gnu::always_inline]]
const char * c_str() const noexcept const char * c_str() const noexcept
{ {
@ -155,13 +157,17 @@ public:
[[gnu::always_inline]] [[gnu::always_inline]]
bool empty() const noexcept bool empty() const noexcept
{ {
return s->size_ == 0; auto * p = &s->string_data();
// Save a dereference in the sentinel value case
if (p == &""_sds)
return true;
return p->size() == 0;
} }
[[gnu::always_inline]] [[gnu::always_inline]]
size_t size() const noexcept size_t size() const noexcept
{ {
return s->size_; return s->string_data().size();
} }
[[gnu::always_inline]] [[gnu::always_inline]]
@ -259,7 +265,6 @@ private:
* During its lifetime the monotonic buffer holds all strings and nodes, if the symbol set is node based. * During its lifetime the monotonic buffer holds all strings and nodes, if the symbol set is node based.
*/ */
std::pmr::monotonic_buffer_resource buffer; std::pmr::monotonic_buffer_resource buffer;
std::pmr::polymorphic_allocator<char> stringAlloc{&buffer};
SymbolStr::SymbolValueStore store{16}; SymbolStr::SymbolValueStore store{16};
/** /**
@ -282,7 +287,7 @@ public:
// Most symbols are looked up more than once, so we trade off insertion performance // Most symbols are looked up more than once, so we trade off insertion performance
// for lookup performance. // for lookup performance.
// FIXME: make this thread-safe. // FIXME: make this thread-safe.
return Symbol(*symbols.insert(SymbolStr::Key{store, s, stringAlloc}).first); return Symbol(*symbols.insert(SymbolStr::Key{store, s, buffer}).first);
} }
std::vector<SymbolStr> resolve(const std::vector<Symbol> & symbols) const std::vector<SymbolStr> resolve(const std::vector<Symbol> & symbols) const

View file

@ -1,8 +1,14 @@
#pragma once #pragma once
///@file ///@file
#include <bit>
#include <cassert> #include <cassert>
#include <cstddef>
#include <cstring>
#include <memory>
#include <memory_resource>
#include <span> #include <span>
#include <string_view>
#include <type_traits> #include <type_traits>
#include <concepts> #include <concepts>
@ -186,6 +192,91 @@ public:
friend struct Value; friend struct Value;
}; };
class StringData
{
public:
using size_type = std::size_t;
size_type size_;
char data_[];
/*
* This in particular ensures that we cannot have a `StringData`
* that we use by value, which is just what we want!
*
* Dynamically sized types aren't a thing in C++ and even flexible array
* members are a language extension and beyond the realm of standard C++.
* Technically, sizeof data_ member is 0 and the intended way to use flexible
* array members is to allocate sizeof(StrindData) + count * sizeof(char) bytes
* and the compiler will consider alignment restrictions for the FAM.
*
*/
StringData(StringData &&) = delete;
StringData & operator=(StringData &&) = delete;
StringData(const StringData &) = delete;
StringData & operator=(const StringData &) = delete;
~StringData() = default;
private:
StringData() = delete;
explicit StringData(size_type size)
: size_(size)
{
}
public:
/**
* Allocate StringData on the (possibly) GC-managed heap and copy
* the contents of s to it.
*/
static const StringData & make(std::string_view s);
/**
* Allocate StringData on the (possibly) GC-managed heap.
* @param size Length of the string (without the NUL terminator).
*/
static StringData & alloc(size_t size);
size_t size() const
{
return size_;
}
char * data() noexcept
{
return data_;
}
const char * data() const noexcept
{
return data_;
}
const char * c_str() const noexcept
{
return data_;
}
constexpr std::string_view view() const noexcept
{
return std::string_view(data_, size_);
}
template<size_t N>
struct Static;
static StringData & make(std::pmr::memory_resource & resource, std::string_view s)
{
auto & res =
*new (resource.allocate(sizeof(StringData) + s.size() + 1, alignof(StringData))) StringData(s.size());
std::memcpy(res.data_, s.data(), s.size());
res.data_[s.size()] = '\0';
return res;
}
};
namespace detail { namespace detail {
/** /**
@ -219,7 +310,7 @@ struct ValueBase
*/ */
struct StringWithContext struct StringWithContext
{ {
const char * c_str; const StringData * str;
/** /**
* The type of the context itself. * The type of the context itself.
@ -234,7 +325,7 @@ struct ValueBase
*/ */
struct Context struct Context
{ {
using value_type = const char *; using value_type = const StringData *;
using size_type = std::size_t; using size_type = std::size_t;
using iterator = const value_type *; using iterator = const value_type *;
@ -285,7 +376,7 @@ struct ValueBase
struct Path struct Path
{ {
SourceAccessor * accessor; SourceAccessor * accessor;
const char * path; const StringData * path;
}; };
struct Null struct Null
@ -646,13 +737,13 @@ protected:
void getStorage(StringWithContext & string) const noexcept void getStorage(StringWithContext & string) const noexcept
{ {
string.context = untagPointer<decltype(string.context)>(payload[0]); string.context = untagPointer<decltype(string.context)>(payload[0]);
string.c_str = std::bit_cast<const char *>(payload[1]); string.str = std::bit_cast<const StringData *>(payload[1]);
} }
void getStorage(Path & path) const noexcept void getStorage(Path & path) const noexcept
{ {
path.accessor = untagPointer<decltype(path.accessor)>(payload[0]); path.accessor = untagPointer<decltype(path.accessor)>(payload[0]);
path.path = std::bit_cast<const char *>(payload[1]); path.path = std::bit_cast<const StringData *>(payload[1]);
} }
void setStorage(NixInt integer) noexcept void setStorage(NixInt integer) noexcept
@ -697,7 +788,7 @@ protected:
void setStorage(StringWithContext string) noexcept void setStorage(StringWithContext string) noexcept
{ {
setUntaggablePayload<pdString>(string.context, string.c_str); setUntaggablePayload<pdString>(string.context, string.str);
} }
void setStorage(Path path) noexcept void setStorage(Path path) noexcept
@ -1050,22 +1141,22 @@ public:
setStorage(b); setStorage(b);
} }
void mkStringNoCopy(const char * s, const Value::StringWithContext::Context * context = nullptr) noexcept void mkStringNoCopy(const StringData & s, const Value::StringWithContext::Context * context = nullptr) noexcept
{ {
setStorage(StringWithContext{.c_str = s, .context = context}); setStorage(StringWithContext{.str = &s, .context = context});
} }
void mkString(std::string_view s); void mkString(std::string_view s);
void mkString(std::string_view s, const NixStringContext & context); void mkString(std::string_view s, const NixStringContext & context);
void mkStringMove(const char * s, const NixStringContext & context); void mkStringMove(const StringData & s, const NixStringContext & context);
void mkPath(const SourcePath & path); void mkPath(const SourcePath & path);
inline void mkPath(SourceAccessor * accessor, const char * path) noexcept inline void mkPath(SourceAccessor * accessor, const StringData & path) noexcept
{ {
setStorage(Path{.accessor = accessor, .path = path}); setStorage(Path{.accessor = accessor, .path = &path});
} }
inline void mkNull() noexcept inline void mkNull() noexcept
@ -1163,17 +1254,23 @@ public:
SourcePath path() const SourcePath path() const
{ {
return SourcePath(ref(pathAccessor()->shared_from_this()), CanonPath(CanonPath::unchecked_t(), pathStr())); return SourcePath(
ref(pathAccessor()->shared_from_this()), CanonPath(CanonPath::unchecked_t(), std::string(pathStrView())));
} }
std::string_view string_view() const noexcept const StringData & string_data() const noexcept
{ {
return std::string_view{getStorage<StringWithContext>().c_str}; return *getStorage<StringWithContext>().str;
} }
const char * c_str() const noexcept const char * c_str() const noexcept
{ {
return getStorage<StringWithContext>().c_str; return getStorage<StringWithContext>().str->data();
}
std::string_view string_view() const noexcept
{
return string_data().view();
} }
const Value::StringWithContext::Context * context() const noexcept const Value::StringWithContext::Context * context() const noexcept
@ -1233,12 +1330,12 @@ public:
const char * pathStr() const noexcept const char * pathStr() const noexcept
{ {
return getStorage<Path>().path; return getStorage<Path>().path->c_str();
} }
std::string_view pathStrView() const noexcept std::string_view pathStrView() const noexcept
{ {
return std::string_view{getStorage<Path>().path}; return getStorage<Path>().path->view();
} }
SourceAccessor * pathAccessor() const noexcept SourceAccessor * pathAccessor() const noexcept

View file

@ -5,6 +5,7 @@
#include "nix/expr/eval-settings.hh" #include "nix/expr/eval-settings.hh"
#include "nix/expr/gc-small-vector.hh" #include "nix/expr/gc-small-vector.hh"
#include "nix/expr/json-to-value.hh" #include "nix/expr/json-to-value.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/store/globals.hh" #include "nix/store/globals.hh"
#include "nix/store/names.hh" #include "nix/store/names.hh"
#include "nix/store/path-references.hh" #include "nix/store/path-references.hh"
@ -487,34 +488,34 @@ static void prim_typeOf(EvalState & state, const PosIdx pos, Value ** args, Valu
state.forceValue(*args[0], pos); state.forceValue(*args[0], pos);
switch (args[0]->type()) { switch (args[0]->type()) {
case nInt: case nInt:
v.mkStringNoCopy("int"); v.mkStringNoCopy("int"_sds);
break; break;
case nBool: case nBool:
v.mkStringNoCopy("bool"); v.mkStringNoCopy("bool"_sds);
break; break;
case nString: case nString:
v.mkStringNoCopy("string"); v.mkStringNoCopy("string"_sds);
break; break;
case nPath: case nPath:
v.mkStringNoCopy("path"); v.mkStringNoCopy("path"_sds);
break; break;
case nNull: case nNull:
v.mkStringNoCopy("null"); v.mkStringNoCopy("null"_sds);
break; break;
case nAttrs: case nAttrs:
v.mkStringNoCopy("set"); v.mkStringNoCopy("set"_sds);
break; break;
case nList: case nList:
v.mkStringNoCopy("list"); v.mkStringNoCopy("list"_sds);
break; break;
case nFunction: case nFunction:
v.mkStringNoCopy("lambda"); v.mkStringNoCopy("lambda"_sds);
break; break;
case nExternal: case nExternal:
v.mkString(args[0]->external()->typeOf()); v.mkString(args[0]->external()->typeOf());
break; break;
case nFloat: case nFloat:
v.mkStringNoCopy("float"); v.mkStringNoCopy("float"_sds);
break; break;
case nThunk: case nThunk:
unreachable(); unreachable();
@ -2024,9 +2025,9 @@ static void prim_dirOf(EvalState & state, const PosIdx pos, Value ** args, Value
pos, *args[0], context, "while evaluating the first argument passed to 'builtins.dirOf'", false, false); pos, *args[0], context, "while evaluating the first argument passed to 'builtins.dirOf'", false, false);
auto pos = path->rfind('/'); auto pos = path->rfind('/');
if (pos == path->npos) if (pos == path->npos)
v.mkStringMove(".", context); v.mkStringMove("."_sds, context);
else if (pos == 0) else if (pos == 0)
v.mkStringMove("/", context); v.mkStringMove("/"_sds, context);
else else
v.mkString(path->substr(0, pos), context); v.mkString(path->substr(0, pos), context);
} }
@ -2309,10 +2310,10 @@ static const Value & fileTypeToString(EvalState & state, SourceAccessor::Type ty
static const Constants stringValues = []() { static const Constants stringValues = []() {
Constants res; Constants res;
res.regular.mkStringNoCopy("regular"); res.regular.mkStringNoCopy("regular"_sds);
res.directory.mkStringNoCopy("directory"); res.directory.mkStringNoCopy("directory"_sds);
res.symlink.mkStringNoCopy("symlink"); res.symlink.mkStringNoCopy("symlink"_sds);
res.unknown.mkStringNoCopy("unknown"); res.unknown.mkStringNoCopy("unknown"_sds);
return res; return res;
}(); }();
@ -4463,7 +4464,7 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value ** args, V
if (len == 0) { if (len == 0) {
state.forceValue(*args[2], pos); state.forceValue(*args[2], pos);
if (args[2]->type() == nString) { if (args[2]->type() == nString) {
v.mkStringNoCopy("", args[2]->context()); v.mkStringNoCopy(""_sds, args[2]->context());
return; return;
} }
} }

View file

@ -1,5 +1,6 @@
#include "nix/expr/primops.hh" #include "nix/expr/primops.hh"
#include "nix/expr/eval-inline.hh" #include "nix/expr/eval-inline.hh"
#include "nix/expr/static-string-data.hh"
#include "expr-config-private.hh" #include "expr-config-private.hh"
@ -136,7 +137,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value ** args, Va
normalizeDatetimeFormat(t); normalizeDatetimeFormat(t);
#endif #endif
auto attrs = state.buildBindings(2); auto attrs = state.buildBindings(2);
attrs.alloc("_type").mkStringNoCopy("timestamp"); attrs.alloc("_type").mkStringNoCopy("timestamp"_sds);
std::ostringstream s; std::ostringstream s;
s << t; s << t;
auto str = s.view(); auto str = s.view();

View file

@ -9,6 +9,7 @@
#include "nix/expr/eval-inline.hh" #include "nix/expr/eval-inline.hh"
#include "nix/store/profiles.hh" #include "nix/store/profiles.hh"
#include "nix/expr/print-ambiguous.hh" #include "nix/expr/print-ambiguous.hh"
#include "nix/expr/static-string-data.hh"
#include <limits> #include <limits>
#include <sstream> #include <sstream>
@ -56,7 +57,7 @@ bool createUserEnv(
auto attrs = state.buildBindings(7 + outputs.size()); auto attrs = state.buildBindings(7 + outputs.size());
attrs.alloc(state.s.type).mkStringNoCopy("derivation"); attrs.alloc(state.s.type).mkStringNoCopy("derivation"_sds);
attrs.alloc(state.s.name).mkString(i.queryName()); attrs.alloc(state.s.name).mkString(i.queryName());
auto system = i.querySystem(); auto system = i.querySystem();
if (!system.empty()) if (!system.empty())