Compare commits

...

34 Commits

Author SHA1 Message Date
Eelco Dolstra
a87ca51842 nix eval-hydra-jobs: Support parallel evaluation
Example usage:

  $ nix eval-hydra-jobs -f '<nixpkgs/pkgs/top-level/release.nix>' '' \
      --max-memory-size 2048 --workers 8
2020-02-14 22:36:13 +01:00
Eelco Dolstra
ae5deb16d0 Fix debug line 2020-02-14 22:33:50 +01:00
Eelco Dolstra
3ce1ecd63b Add some GC diagnostics functions 2020-02-13 23:16:01 +01:00
Eelco Dolstra
4d74e67aa8 Add 'nix eval-hydra-jobs' command 2020-02-13 19:35:40 +01:00
Eelco Dolstra
594ae92644 Fix build 2020-02-13 19:35:31 +01:00
Eelco Dolstra
c31b3d6c5c Merge remote-tracking branch 'origin/master' into precise-gc 2020-02-13 17:16:16 +01:00
Eelco Dolstra
eb319c8078 nix list-tarballs: Revive 2020-02-13 16:21:58 +01:00
Eelco Dolstra
ca2dee6e7d Merge remote-tracking branch 'origin/master' into precise-gc 2020-02-13 16:16:03 +01:00
Eelco Dolstra
d53b8d7211 Doh 2020-02-12 21:47:12 +01:00
Eelco Dolstra
488ce10b4e Merge remote-tracking branch 'origin/master' into precise-gc 2020-02-12 20:41:17 +01:00
Eelco Dolstra
351fbfcb9b Include GC stats in printStats 2019-04-29 23:24:14 +02:00
Eelco Dolstra
c6ff34bd86 Measure GC duration 2019-04-29 23:04:50 +02:00
Eelco Dolstra
54e1ac6102 Slight optimization 2019-04-29 23:04:50 +02:00
Eelco Dolstra
abbbad5679 Add 'nix list-tarballs' command
E.g.

  $ nix list-tarballs nixpkgs.hello
  http://tarballs.nixos.org/stdenv-linux/x86_64/4907fc9e8d0d82b28b3c56e3a478a2882f1d700f/bootstrap-tools.tar.xz
  ...
  mirror://gnu/hello/hello-2.10.tar.gz

  $ nix list-tarballs --json -f '<nixpkgs/maintainers/scripts/all-tarballs.nix>'

(BTW the latter returns about 6000 files more than find-tarballs.nix.)
2019-04-29 23:04:50 +02:00
Eelco Dolstra
b19b221f98 SourceExprCommand: Print evaluator stats 2019-04-29 23:04:50 +02:00
Eelco Dolstra
76325ce5cd DerivationOutput::parseHashInfo: Return a tuple 2019-04-29 22:11:14 +02:00
Eelco Dolstra
c3b55a96a7 Bindings::get(): Add convenience method
This allows writing attribute lookups as

    if (auto name = value.attrs->get(state.sName))
      ...
2019-04-29 20:32:43 +02:00
Eelco Dolstra
6d118419f2 Garbage-collect paths 2019-04-29 16:27:56 +02:00
Eelco Dolstra
2995f9c48f Garbage-collect strings 2019-04-29 16:27:56 +02:00
Eelco Dolstra
9b822de4ef Inline allocValue() 2019-04-29 16:27:56 +02:00
Eelco Dolstra
14f7a60755 Keep some stats 2019-04-24 13:46:34 +02:00
Eelco Dolstra
80accdcebe Remove Boehm GC dependency 2019-04-24 00:07:42 +02:00
Eelco Dolstra
69adbf5c77 Rename 2019-04-23 22:50:01 +02:00
Eelco Dolstra
35b76b21ee Size -> size_t 2019-04-23 22:47:30 +02:00
Eelco Dolstra
ba36d43d46 Freelist improvements 2019-04-23 22:44:17 +02:00
Eelco Dolstra
93b3d25bbb Move parseSize() to libutil 2019-04-23 22:33:10 +02:00
Eelco Dolstra
f7f73cf5ae Allow disabling some GC debug checks 2019-04-23 13:56:38 +02:00
Eelco Dolstra
a38a7b495c Use Value::misc to store strings
This allows strings < 23 characters (up from 16) to be stored directly
in Value. On a NixOS 19.03 system configuration evaluation, this
allows 1060588 out of 1189295 (89%) strings to be stored in Value.
2019-04-23 12:54:12 +02:00
Eelco Dolstra
742a8046de Store short strings in Values
The vast majority of strings are < 16 bytes, and so can be stored
directly in a Value. This saves a heap allocation and an indirection.
2019-04-23 12:20:27 +02:00
Eelco Dolstra
2160258cc4 Store contexts as symbols
This provides some deduplication since most contexts are used multiple
times.

Also, store singleton contexts directly in the Value object. This
saves a 16-byte Context object. This is useful because the vast
majority of contexts are singletons, e.g. 23723 out of 26138 in a
NixOS 19.03 system configuration.
2019-04-23 11:07:47 +02:00
Eelco Dolstra
e392ff53e9 Remove a word from Env 2019-04-23 01:11:50 +02:00
Eelco Dolstra
ae5b76a5a4 Checkpoint 2019-04-23 00:39:14 +02:00
Eelco Dolstra
7c716b4c49 Checkpoint 2019-04-22 23:25:47 +02:00
Eelco Dolstra
4237414f4d Checkpoint 2019-04-15 18:40:35 +02:00
48 changed files with 2249 additions and 766 deletions

View File

@@ -1,5 +1,4 @@
AR = @AR@
BDW_GC_LIBS = @BDW_GC_LIBS@
BUILD_SHARED_LIBS = @BUILD_SHARED_LIBS@
CC = @CC@
CFLAGS = @CFLAGS@

View File

@@ -255,17 +255,6 @@ if test -n "$enable_s3"; then
fi
# Whether to use the Boehm garbage collector.
AC_ARG_ENABLE(gc, AC_HELP_STRING([--enable-gc],
[enable garbage collection in the Nix expression evaluator (requires Boehm GC) [default=yes]]),
gc=$enableval, gc=yes)
if test "$gc" = yes; then
PKG_CHECK_MODULES([BDW_GC], [bdw-gc])
CXXFLAGS="$BDW_GC_CFLAGS $CXXFLAGS"
AC_DEFINE(HAVE_BOEHMGC, 1, [Whether to use the Boehm garbage collector.])
fi
# documentation generation switch
AC_ARG_ENABLE(doc-gen, AC_HELP_STRING([--disable-doc-gen],
[disable documentation generation]),

View File

@@ -50,14 +50,6 @@
or higher. If your distribution does not provide it, please install
it from <link xlink:href="http://www.sqlite.org/" />.</para></listitem>
<listitem><para>The <link
xlink:href="http://www.hboehm.info/gc/">Boehm
garbage collector</link> to reduce the evaluators memory
consumption (optional). To enable it, install
<literal>pkgconfig</literal> and the Boehm garbage collector, and
pass the flag <option>--enable-gc</option> to
<command>configure</command>.</para></listitem>
<listitem><para>The <literal>boost</literal> library of version
1.66.0 or higher. It can be obtained from the official web site
<link xlink:href="https://www.boost.org/" />.</para></listitem>

View File

@@ -48,7 +48,7 @@ rec {
buildDeps =
[ curl
bzip2 xz brotli zlib editline
openssl pkgconfig sqlite boehmgc
openssl pkgconfig sqlite
libarchive
boost
nlohmann_json

View File

@@ -32,7 +32,7 @@ static Strings parseAttrPath(const string & s)
}
Value * findAlongAttrPath(EvalState & state, const string & attrPath,
Ptr<Value> findAlongAttrPath(EvalState & state, const string & attrPath,
Bindings & autoArgs, Value & vIn)
{
Strings tokens = parseAttrPath(attrPath);
@@ -40,7 +40,7 @@ Value * findAlongAttrPath(EvalState & state, const string & attrPath,
Error attrError =
Error(format("attribute selection path '%1%' does not match expression") % attrPath);
Value * v = &vIn;
Ptr<Value> v(&vIn);
for (auto & attr : tokens) {
@@ -50,7 +50,7 @@ Value * findAlongAttrPath(EvalState & state, const string & attrPath,
if (string2Int(attr, attrIndex)) apType = apIndex;
/* Evaluate the expression. */
Value * vNew = state.allocValue();
auto vNew = state.allocValue();
state.autoCallFunction(autoArgs, *v, *vNew);
v = vNew;
state.forceValue(*v);
@@ -71,7 +71,7 @@ Value * findAlongAttrPath(EvalState & state, const string & attrPath,
Bindings::iterator a = v->attrs->find(state.symbols.create(attr));
if (a == v->attrs->end())
throw Error(format("attribute '%1%' in selection path '%2%' not found") % attr % attrPath);
v = &*a->value;
v = a->value;
}
else if (apType == apIndex) {
@@ -97,8 +97,7 @@ Pos findDerivationFilename(EvalState & state, Value & v, std::string what)
{
Value * v2;
try {
auto dummyArgs = state.allocBindings(0);
v2 = findAlongAttrPath(state, "meta.position", *dummyArgs, v);
v2 = findAlongAttrPath(state, "meta.position", *state.emptyBindings, v);
} catch (Error &) {
throw Error("package '%s' has no source location information", what);
}

View File

@@ -7,7 +7,7 @@
namespace nix {
Value * findAlongAttrPath(EvalState & state, const string & attrPath,
Ptr<Value> findAlongAttrPath(EvalState & state, const string & attrPath,
Bindings & autoArgs, Value & vIn);
/* Heuristic to find the filename and lineno or a nix value. */

View File

@@ -7,28 +7,28 @@
namespace nix {
/* Allocate a new array of attributes for an attribute set with a specific
capacity. The space is implicitly reserved after the Bindings
structure. */
Bindings * EvalState::allocBindings(size_t capacity)
/* Allocate a new array of attributes for an attribute set with a
specific capacity. The space is implicitly reserved after the
Bindings structure. */
Ptr<Bindings> Bindings::allocBindings(size_t capacity)
{
if (capacity > std::numeric_limits<Bindings::size_t>::max())
if (capacity >= 1UL << Object::miscBytes * 8)
throw Error("attribute set of size %d is too big", capacity);
return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings((Bindings::size_t) capacity);
return gc.alloc<Bindings>(Bindings::wordsFor(capacity), capacity);
}
void EvalState::mkAttrs(Value & v, size_t capacity)
{
if (capacity == 0) {
v = vEmptySet;
return;
v.attrs = emptyBindings;
v.type = tAttrs;
} else {
v.attrs = Bindings::allocBindings(capacity);
v.type = tAttrs;
nrAttrsets++;
nrAttrsInAttrsets += capacity;
}
clearValue(v);
v.type = tAttrs;
v.attrs = allocBindings(capacity);
nrAttrsets++;
nrAttrsInAttrsets += capacity;
}
@@ -37,7 +37,7 @@ void EvalState::mkAttrs(Value & v, size_t capacity)
this attribute. */
Value * EvalState::allocAttr(Value & vAttrs, const Symbol & name)
{
Value * v = allocValue();
auto v = allocValue();
vAttrs.attrs->push_back(Attr(name, v));
return v;
}

View File

@@ -2,6 +2,7 @@
#include "nixexpr.hh"
#include "symbol-table.hh"
#include "gc.hh"
#include <algorithm>
#include <optional>
@@ -31,19 +32,22 @@ struct Attr
by its size and its capacity, the capacity being the number of Attr
elements allocated after this structure, while the size corresponds to
the number of elements already inserted in this structure. */
class Bindings
class Bindings : public Object
{
public:
typedef uint32_t size_t;
private:
size_t size_, capacity_;
// FIXME: eliminate size_. We can just rely on capacity by sorting
// null entries towards the end of the vector.
size_t size_;
Attr attrs[0];
Bindings(size_t capacity) : size_(0), capacity_(capacity) { }
Bindings(size_t capacity) : Object(tBindings, capacity), size_(0) {}
Bindings(const Bindings & bindings) = delete;
public:
size_t size() const { return size_; }
bool empty() const { return !size_; }
@@ -52,7 +56,8 @@ public:
void push_back(const Attr & attr)
{
assert(size_ < capacity_);
assert(size_ < capacity());
gc.assertObject(attr.value);
attrs[size_++] = attr;
}
@@ -90,7 +95,7 @@ public:
void sort();
size_t capacity() { return capacity_; }
size_t capacity() const { return getMisc(); }
/* Returns the attributes in lexicographically sorted order. */
std::vector<const Attr *> lexicographicOrder() const
@@ -105,7 +110,19 @@ public:
return res;
}
friend class EvalState;
size_t words() const
{
return wordsFor(capacity());
}
static size_t wordsFor(size_t capacity)
{
return 2 + 3 * capacity; // FIXME
}
static Ptr<Bindings> allocBindings(size_t capacity);
friend class GC;
};

View File

@@ -28,19 +28,21 @@ MixEvalArgs::MixEvalArgs()
.handler([&](std::string s) { searchPath.push_back(s); });
}
Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
Ptr<Bindings> MixEvalArgs::getAutoArgs(EvalState & state)
{
Bindings * res = state.allocBindings(autoArgs.size());
for (auto & i : autoArgs) {
Value * v = state.allocValue();
if (i.second[0] == 'E')
state.mkThunk_(*v, state.parseExprFromString(string(i.second, 1), absPath(".")));
else
mkString(*v, string(i.second, 1));
res->push_back(Attr(state.symbols.create(i.first), v));
if (!bindings) {
bindings = Bindings::allocBindings(autoArgs.size());
for (auto & i : autoArgs) {
Value * v = state.allocValue();
if (i.second[0] == 'E')
state.mkThunk_(*v, state.parseExprFromString(string(i.second, 1), absPath(".")));
else
mkString(*v, string(i.second, 1));
bindings->push_back(Attr(state.symbols.create(i.first), v));
}
bindings->sort();
}
res->sort();
return res;
return bindings;
}
Path lookupFileArg(EvalState & state, string s)

View File

@@ -1,24 +1,28 @@
#pragma once
#include "args.hh"
#include "eval.hh"
namespace nix {
class Store;
class EvalState;
class Bindings;
template<class T>
struct Ptr;
struct MixEvalArgs : virtual Args
{
MixEvalArgs();
Bindings * getAutoArgs(EvalState & state);
Ptr<Bindings> getAutoArgs(EvalState & state);
Strings searchPath;
private:
std::map<std::string, std::string> autoArgs;
Ptr<Bindings> bindings;
};
Path lookupFileArg(EvalState & state, string s);

View File

@@ -27,21 +27,27 @@ LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v, const
void EvalState::forceValue(Value & v, const Pos & pos)
{
if (v.type == tThunk) {
Env * env = v.thunk.env;
// FIXME: this is necessary because some values (like vList2)
// are created non-atomically.
Ptr<Env> env(v.thunk.env);
Expr * expr = v.thunk.expr;
try {
v.type = tBlackhole;
//checkInterrupt();
expr->eval(*this, *env, v);
} catch (...) {
v.type = tThunk;
v.thunk.env = env;
v.thunk.expr = expr;
v.type = tThunk;
throw;
}
}
else if (v.type == tApp)
else if (v.type == tApp) {
// FIXME: idem.
Ptr<Value> left(v.app.left);
Ptr<Value> right(v.app.right);
callFunction(*v.app.left, *v.app.right, v, noPos);
}
else if (v.type == tBlackhole)
throwEvalError("infinite recursion encountered, at %1%", pos);
}
@@ -78,18 +84,5 @@ inline void EvalState::forceList(Value & v, const Pos & pos)
throwTypeError("value is %1% while a list was expected, at %2%", v, pos);
}
/* Note: Various places expect the allocated memory to be zeroed. */
inline void * allocBytes(size_t n)
{
void * p;
#if HAVE_BOEHMGC
p = GC_MALLOC(n);
#else
p = calloc(n, 1);
#endif
if (!p) throw std::bad_alloc();
return p;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@
#include "symbol-table.hh"
#include "hash.hh"
#include "config.hh"
#include "gc.hh"
#include <map>
#include <optional>
@@ -17,6 +18,7 @@ namespace nix {
class Store;
class EvalState;
struct Derivation; // FIXME: remove
struct StorePath;
enum RepairFlag : bool;
@@ -34,18 +36,56 @@ struct PrimOp
};
struct Env
struct Env : Object
{
Env * up;
unsigned short prevWith:14; // nr of levels up to next `with' environment
enum { Plain = 0, HasWithExpr, HasWithAttrs } type:2;
Value * values[0];
private:
constexpr static size_t maxSize = 1 << 16;
constexpr static size_t maxPrevWith = 1 << 10;
Env(Tag type, size_t size, size_t prevWith)
: Object(type, size | (prevWith << 16))
{
if (size >= maxSize)
throw Error("environment size %d is too big", size);
if (prevWith >= maxPrevWith)
throw Error("too many nesting levels");
for (size_t i = 0; i < size; i++)
values[i] = nullptr;
}
friend class GC;
public:
unsigned short getPrevWith() const
{
return getMisc() >> 16;
}
unsigned short getSize() const
{
return getMisc() & 0xffff;
}
size_t words() const
{
return wordsFor(getSize());
}
static size_t wordsFor(unsigned short size)
{
return 2 + size;
}
};
Value & mkString(Value & v, std::string_view s, const PathSet & context = PathSet());
void copyContext(const Value & v, PathSet & context);
Value & mkString(Value & v, std::string_view s, const PathSet & context);
/* Cache for calls to addToStore(); maps source paths to the store
@@ -60,14 +100,10 @@ typedef std::pair<std::string, std::string> SearchPathElem;
typedef std::list<SearchPathElem> SearchPath;
/* Initialise the Boehm GC, if applicable. */
void initGC();
class EvalState
{
public:
SymbolTable symbols;
SymbolTable & symbols{nix::symbols}; // FIXME: remove
const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue,
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
@@ -84,7 +120,7 @@ public:
mode. */
std::optional<PathSet> allowedPaths;
Value vEmptySet;
Ptr<Bindings> emptyBindings;
const ref<Store> store;
@@ -92,19 +128,11 @@ private:
SrcToStore srcToStore;
/* A cache from path names to parse trees. */
#if HAVE_BOEHMGC
typedef std::map<Path, Expr *, std::less<Path>, traceable_allocator<std::pair<const Path, Expr *> > > FileParseCache;
#else
typedef std::map<Path, Expr *> FileParseCache;
#endif
FileParseCache fileParseCache;
/* A cache from path names to values. */
#if HAVE_BOEHMGC
typedef std::map<Path, Value, std::less<Path>, traceable_allocator<std::pair<const Path, Value> > > FileEvalCache;
#else
typedef std::map<Path, Value> FileEvalCache;
#endif
typedef std::map<Path, Ptr<Value>> FileEvalCache;
FileEvalCache fileEvalCache;
SearchPath searchPath;
@@ -217,7 +245,7 @@ public:
/* The base environment, containing the builtin functions and
values. */
Env & baseEnv;
Ptr<Env> baseEnv;
/* The same, but used during parsing to resolve variables. */
StaticEnv staticBaseEnv; // !!! should be private
@@ -264,13 +292,18 @@ public:
void autoCallFunction(Bindings & args, Value & fun, Value & res);
/* Allocation primitives. */
Value * allocValue();
Env & allocEnv(size_t size);
Ptr<Value> allocValue()
{
nrValues++;
return gc.alloc<Value>(Value::words());
}
Ptr<Env> allocEnv(size_t size, size_t prevWith = 0, Tag type = tEnv);
// Note: the resulting Value is only reachable as long as vAttrs
// is reachable.
Value * allocAttr(Value & vAttrs, const Symbol & name);
Bindings * allocBindings(size_t capacity);
void mkList(Value & v, size_t length);
void mkAttrs(Value & v, size_t capacity);
void mkThunk_(Value & v, Expr * expr);
@@ -314,6 +347,10 @@ private:
friend struct ExprOpConcatLists;
friend struct ExprSelect;
friend void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v);
public:
std::function<void(const StorePath & drvPath, const Derivation & drv)> derivationHook;
};

537
src/libexpr/gc.cc Normal file
View File

@@ -0,0 +1,537 @@
#include "gc.hh"
#include "value.hh"
#include "attr-set.hh"
#include "eval.hh"
#include <chrono>
namespace nix {
GC gc;
GC::GC()
{
nextSize = std::max((size_t) 2, parseSize<size_t>(getEnv("GC_INITIAL_HEAP_SIZE").value_or("131072")) / WORD_SIZE);
// FIXME: placement new
frontPtrSentinel = (Ptr<Object> *) malloc(sizeof(Ptr<Object>));
backPtrSentinel = (Ptr<Object> *) malloc(sizeof(Ptr<Object>));
frontPtrSentinel->prev = nullptr;
frontPtrSentinel->next = backPtrSentinel;
backPtrSentinel->prev = frontPtrSentinel;
backPtrSentinel->next = nullptr;
frontRootSentinel = (Root<Object> *) malloc(sizeof(Root<Object>));
backRootSentinel = (Root<Object> *) malloc(sizeof(Root<Object>));
frontRootSentinel->prev = nullptr;
frontRootSentinel->next = backRootSentinel;
backRootSentinel->prev = frontRootSentinel;
backRootSentinel->next = nullptr;
freeLists[0].minSize = 2;
freeLists[1].minSize = 3;
freeLists[2].minSize = 4;
freeLists[3].minSize = 8;
freeLists[4].minSize = 16;
freeLists[5].minSize = 32;
freeLists[6].minSize = 64;
freeLists[7].minSize = 128;
addArena(nextSize);
}
GC::~GC()
{
debug("%d bytes in arenas, %d bytes allocated, %d bytes reclaimed, in %d ms",
totalSize * WORD_SIZE,
allTimeWordsAllocated * WORD_SIZE,
allTimeWordsFreed * WORD_SIZE,
totalDurationMs);
size_t n = 0;
for (Ptr<Object> * p = frontPtrSentinel->next; p != backPtrSentinel; p = p->next)
n++;
if (n)
warn("%d GC root pointers still exist on exit", n);
n = 0;
for (Root<Object> * p = frontRootSentinel->next; p != backRootSentinel; p = p->next)
n++;
if (n)
warn("%d GC root objects still exist on exit", n);
assert(!frontPtrSentinel->prev);
assert(!backPtrSentinel->next);
assert(!frontRootSentinel->prev);
assert(!backRootSentinel->next);
}
void GC::addArena(size_t arenaSize)
{
debug("allocating arena of %d bytes", arenaSize * WORD_SIZE);
auto arena = Arena(arenaSize);
// Add this arena to a freelist as a single block.
addToFreeList(new (arena.start) Free(arenaSize));
arenas.emplace_back(std::move(arena));
totalSize += arenaSize;
nextSize = arenaSize * 1.5; // FIXME: overflow, clamp
}
void GC::addToFreeList(Free * obj)
{
auto size = obj->words();
for (auto i = freeLists.rbegin(); i != freeLists.rend(); ++i)
if (size >= i->minSize) {
obj->next = i->front;
i->front = obj;
return;
}
abort();
}
void GC::gc()
{
typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
auto before = steady_time_point::clock::now();
size_t marked = 0;
std::stack<Object *> stack;
// FIXME: ensure this gets inlined.
auto push = [&](Object * p) { if (p) { assertObject(p); stack.push(p); } };
auto pushPointers = [&](Object * obj) {
switch (obj->type) {
case tFree:
printError("reached a freed object at %x", obj);
abort();
case tBindings: {
auto obj2 = (Bindings *) obj;
for (auto i = obj2->attrs; i < obj2->attrs + obj2->size_; ++i)
push(i->value);
break;
}
case tValueList: {
auto obj2 = (PtrList<Object> *) obj;
for (auto i = obj2->elems; i < obj2->elems + obj2->size(); ++i)
push(*i);
break;
}
case tEnv: {
auto obj2 = (Env *) obj;
push(obj2->up);
for (auto i = obj2->values; i < obj2->values + obj2->getSize(); ++i)
push(*i);
break;
}
case tWithExprEnv: {
auto obj2 = (Env *) obj;
push(obj2->up);
break;
}
case tWithAttrsEnv: {
auto obj2 = (Env *) obj;
push(obj2->up);
push(obj2->values[0]);
break;
}
case tString:
case tContext:
case tInt:
case tBool:
case tNull:
case tList0:
case tFloat:
case tShortString:
case tStaticString:
break;
case tLongString: {
auto obj2 = (Value *) obj;
push(obj2->string.s);
// See setContext().
if (!(((ptrdiff_t) obj2->string.context) & 1))
push(obj2->string.context);
break;
}
case tPath:
push(((Value *) obj)->path);
break;
case tAttrs:
push(((Value *) obj)->attrs);
break;
case tList1:
push(((Value *) obj)->smallList[0]);
break;
case tList2:
push(((Value *) obj)->smallList[0]);
push(((Value *) obj)->smallList[1]);
break;
case tListN:
push(((Value *) obj)->bigList);
break;
case tThunk:
case tBlackhole:
push(((Value *) obj)->thunk.env);
break;
case tApp:
case tPrimOpApp:
push(((Value *) obj)->app.left);
push(((Value *) obj)->app.right);
break;
case tLambda:
push(((Value *) obj)->lambda.env);
break;
case tPrimOp:
// FIXME: GC primops?
break;
default:
printError("don't know how to traverse object at %x (tag %d)", obj, obj->type);
abort();
}
};
auto processStack = [&]() {
while (!stack.empty()) {
auto obj = stack.top();
stack.pop();
//printError("MARK %x", obj);
if (!obj->isMarked()) {
marked++;
obj->mark();
pushPointers(obj);
}
}
};
for (Root<Object> * p = frontRootSentinel->next; p != backRootSentinel; p = p->next) {
pushPointers(&p->value);
processStack();
}
for (Ptr<Object> * p = frontPtrSentinel->next; p != backPtrSentinel; p = p->next) {
if (!p->value) continue;
stack.push(p->value);
processStack();
}
auto afterMark = steady_time_point::clock::now();
// Reset all the freelists.
for (auto & freeList : freeLists)
freeList.front = nullptr;
// Go through all the arenas and add free objects to the
// appropriate freelists.
size_t totalObjectsFreed = 0;
size_t totalWordsFreed = 0;
size_t totalObjectsKept = 0;
size_t totalWordsKept = 0;
for (auto & arena : arenas) {
auto [objectsFreed, wordsFreed, objectsKept, wordsKept] = freeUnmarked(arena);
totalObjectsFreed += objectsFreed;
totalWordsFreed += wordsFreed;
totalObjectsKept += objectsKept;
totalWordsKept += wordsKept;
}
auto after = steady_time_point::clock::now();
auto markDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(afterMark - before).count();
auto sweepDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(after - afterMark).count();
debug("freed %d dead objects (%d bytes), keeping %d/%d objects (%d bytes), marked in %d ms, swept in %d ms",
totalObjectsFreed, totalWordsFreed * WORD_SIZE,
marked, totalObjectsKept, totalWordsKept * WORD_SIZE,
markDurationMs, sweepDurationMs);
allTimeWordsFreed += totalWordsFreed;
totalDurationMs += markDurationMs + sweepDurationMs;
}
size_t GC::getObjectSize(Object * obj)
{
auto tag = obj->type;
if (tag >= tInt && tag <= tFloat) {
return ((Value *) obj)->words();
} else {
switch (tag) {
case tFree:
return ((Free *) obj)->words();
break;
case tString:
return ((String *) obj)->words();
break;
case tBindings:
return ((Bindings *) obj)->words();
break;
case tValueList:
return ((PtrList<Value> *) obj)->words();
break;
case tEnv:
case tWithExprEnv:
case tWithAttrsEnv:
return ((Env *) obj)->words();
break;
case tContext:
return ((Context *) obj)->getSize() + 1;
break;
default:
printError("GC encountered invalid object with tag %d", tag);
abort();
}
}
}
std::tuple<size_t, size_t, size_t, size_t> GC::freeUnmarked(Arena & arena)
{
size_t objectsFreed = 0;
size_t wordsFreed = 0;
size_t objectsKept = 0;
size_t wordsKept = 0;
auto end = arena.start + arena.size;
auto pos = arena.start;
Free * curFree = nullptr;
auto linkCurFree = [&]() {
if (curFree && curFree->words() > 1)
addToFreeList(curFree);
curFree = nullptr;
};
while (pos < end) {
auto obj = (Object *) pos;
auto objSize = getObjectSize(obj);
// Merge current object into the previous free object.
auto mergeFree = [&]() {
//printError("MERGE %x %x %d", curFree, obj, curFree->size() + objSize);
assert(curFree->words() >= 1);
curFree->setSize(curFree->words() + objSize);
};
if (obj->type == tFree) {
//debug("KEEP FREE %x %d", obj, obj->getMisc());
if (curFree) {
// Merge this object into the previous free
// object.
mergeFree();
} else {
curFree = (Free *) obj;
}
} else {
if (obj->isMarked()) {
// Unmark to prepare for the next GC run.
//debug("KEEP OBJECT %x %d %d", obj, obj->type, objSize);
linkCurFree();
obj->unmark();
objectsKept += 1;
wordsKept += objSize;
} else {
//debug("FREE OBJECT %x %d %d", obj, obj->type, objSize);
#if GC_DEBUG
for (size_t i = 0; i < objSize; ++i)
((Word *) obj)[i] = 0xdeadc0dedeadbeefULL;
#endif
objectsFreed += 1;
wordsFreed += objSize;
if (curFree) {
mergeFree();
} else {
// Convert to a free object.
curFree = (Free *) obj;
curFree->type = tFree;
curFree->setSize(objSize);
}
}
}
pos += objSize;
}
linkCurFree();
assert(pos == end);
return {objectsFreed, wordsFreed, objectsKept, wordsKept};
}
bool GC::isObject(void * p)
{
for (auto & arena : arenas)
if (p >= arena.start && p < arena.start + arena.size)
return true;
return false;
}
std::tuple<size_t, size_t> GC::getObjectClosureSize(Object * p)
{
std::unordered_set<Object *> seen;
// FIXME: cut&paste.
std::stack<Object *> stack;
// FIXME: ensure this gets inlined.
auto push = [&](Object * p) { if (p) { assertObject(p); stack.push(p); } };
auto pushPointers = [&](Object * obj) {
switch (obj->type) {
case tFree:
printError("reached a freed object at %x", obj);
abort();
case tBindings: {
auto obj2 = (Bindings *) obj;
for (auto i = obj2->attrs; i < obj2->attrs + obj2->size_; ++i)
push(i->value);
break;
}
case tValueList: {
auto obj2 = (PtrList<Object> *) obj;
for (auto i = obj2->elems; i < obj2->elems + obj2->size(); ++i)
push(*i);
break;
}
case tEnv: {
auto obj2 = (Env *) obj;
push(obj2->up);
for (auto i = obj2->values; i < obj2->values + obj2->getSize(); ++i)
push(*i);
break;
}
case tWithExprEnv: {
auto obj2 = (Env *) obj;
push(obj2->up);
break;
}
case tWithAttrsEnv: {
auto obj2 = (Env *) obj;
push(obj2->up);
push(obj2->values[0]);
break;
}
case tString:
case tContext:
case tInt:
case tBool:
case tNull:
case tList0:
case tFloat:
case tShortString:
case tStaticString:
break;
case tLongString: {
auto obj2 = (Value *) obj;
push(obj2->string.s);
// See setContext().
if (!(((ptrdiff_t) obj2->string.context) & 1))
push(obj2->string.context);
break;
}
case tPath:
push(((Value *) obj)->path);
break;
case tAttrs:
push(((Value *) obj)->attrs);
break;
case tList1:
push(((Value *) obj)->smallList[0]);
break;
case tList2:
push(((Value *) obj)->smallList[0]);
push(((Value *) obj)->smallList[1]);
break;
case tListN:
push(((Value *) obj)->bigList);
break;
case tThunk:
case tBlackhole:
push(((Value *) obj)->thunk.env);
break;
case tApp:
case tPrimOpApp:
push(((Value *) obj)->app.left);
push(((Value *) obj)->app.right);
break;
case tLambda:
push(((Value *) obj)->lambda.env);
break;
case tPrimOp:
// FIXME: GC primops?
break;
default:
printError("don't know how to traverse object at %x (tag %d)", obj, obj->type);
abort();
}
};
stack.push(p);
size_t totalSize = 0;
while (!stack.empty()) {
auto obj = stack.top();
stack.pop();
if (seen.insert(obj).second) {
pushPointers(obj);
totalSize += getObjectSize(obj);
}
}
return {seen.size(), totalSize};
}
}

482
src/libexpr/gc.hh Normal file
View File

@@ -0,0 +1,482 @@
#pragma once
#include "logging.hh"
#include <stack>
#include <limits>
#include <cassert>
#include <cstring>
//#define GC_DEBUG 1
namespace nix {
typedef unsigned long Word;
enum Tag {
tFree = 3,
// Misc types
tString,
tBindings,
tValueList,
tEnv,
tWithExprEnv,
tWithAttrsEnv,
tContext,
// Value tags
tInt,
tBool,
tShortString,
tStaticString,
tLongString,
tPath,
tNull,
tAttrs,
tList0,
tList1,
tList2,
tListN,
tThunk,
tApp,
tLambda,
tBlackhole,
tPrimOp,
tPrimOpApp,
tExternal,
tFloat
};
constexpr size_t WORD_SIZE = 8;
struct Object
{
friend class GC;
public:
constexpr static size_t miscBytes = 7;
public: // FIXME
Tag type:7;
private:
bool marked:1;
unsigned long misc:56;
void unmark()
{
marked = false;
}
protected:
Object(Tag type, unsigned long misc) : type(type), marked(false), misc(misc) { }
bool isMarked()
{
return marked;
}
void mark()
{
marked = true;
}
void setMisc(unsigned int misc)
{
this->misc = misc;
}
unsigned int getMisc() const
{
return misc;
}
char * getMiscData() const
{
return ((char *) this) + 1;
}
};
template<class T>
struct PtrList : Object
{
T * elems[0];
PtrList(Tag type, size_t size) : Object(type, size)
{
for (size_t i = 0; i < size; i++)
elems[i] = nullptr;
}
size_t size() const { return getMisc(); }
size_t words() const { return wordsFor(size()); }
static size_t wordsFor(size_t size) { return 1 + size; }
};
struct Free : Object
{
Free * next;
Free(size_t size) : Object(tFree, size), next(nullptr) { }
// return size in words
size_t words() const { return getMisc(); }
void setSize(size_t size) { assert(size >= 1); setMisc(size); }
};
template<class T>
struct Ptr;
template<class T>
struct Root;
struct GC
{
private:
Ptr<Object> * frontPtrSentinel;
Ptr<Object> * backPtrSentinel;
Root<Object> * frontRootSentinel;
Root<Object> * backRootSentinel;
template<class T>
friend class Ptr;
template<class T>
friend class Root;
struct Arena
{
size_t size; // in words
Word * start;
Arena(size_t size)
: size(size)
, start(new Word[size])
{
assert(size >= 2);
}
Arena(const Arena & arena) = delete;
Arena(Arena && arena)
{
size = arena.size;
start = arena.start;
arena.start = nullptr;
}
~Arena()
{
delete[] start;
}
};
size_t totalSize = 0;
size_t nextSize;
std::vector<Arena> arenas;
struct FreeList
{
size_t minSize;
Free * front = nullptr;
};
std::array<FreeList, 8> freeLists;
public:
size_t getHeapSize() { return totalSize * WORD_SIZE; }
size_t allTimeWordsAllocated = 0;
size_t allTimeWordsFreed = 0;
uint64_t totalDurationMs = 0;
private:
Object * allocObject(size_t size)
{
assert(size >= 2);
for (int attempt = 0; attempt < 3; attempt++) {
for (size_t i = 0; i < freeLists.size(); ++i) {
auto & freeList = freeLists[i];
if ((size <= freeList.minSize || i == freeLists.size() - 1) && freeList.front) {
//printError("TRY %d %d %d", size, i, freeList.minSize);
Free * * prev = &freeList.front;
while (Free * freeObj = *prev) {
//printError("LOOK %x %d %x", freeObj, freeObj->words(), freeObj->next);
assert(freeObj->words() >= freeList.minSize);
if (freeObj->words() == size) {
// Convert the free object.
*prev = freeObj->next;
return (Object *) freeObj;
} else if (freeObj->words() >= size + 2) {
// Split the free object.
auto newSize = freeObj->words() - size;
freeObj->setSize(newSize);
if (newSize < freeList.minSize) {
/* The free object is now smaller than
the minimum size for this freelist,
so move it to another one. */
//printError("MOVE %x %d -> %d", freeObj, newSize + size, newSize);
*prev = freeObj->next;
addToFreeList(freeObj);
}
return (Object *) (((Word *) freeObj) + newSize);
} else if (freeObj->words() == size + 1) {
// Return the free object and add a padding word.
*prev = freeObj->next;
freeObj->setSize(1);
return (Object *) (((Word *) freeObj) + 1);
} else {
assert(freeObj->words() < size);
prev = &freeObj->next;
}
}
}
}
if (attempt == 0) {
debug("allocation of %d bytes failed, GCing...", size * WORD_SIZE);
gc();
} else if (attempt == 1) {
addArena(std::max(nextSize, size));
}
}
throw Error("allocation of %d bytes failed", size);
}
public:
GC();
~GC();
template<typename T, typename... Args>
Ptr<T> alloc(size_t size, const Args & ... args)
{
auto raw = allocObject(size);
allTimeWordsAllocated += size;
return new (raw) T(args...);
}
void gc();
bool isObject(void * p);
void assertObject(void * p)
{
#if GC_DEBUG
if (!isObject(p)) {
printError("object %p is not an object", p);
abort();
}
#endif
}
/* Return the size in words of object 'obj'. */
static size_t getObjectSize(Object * obj);
/* Return the number and size in words of the objects reachable
from object 'obj'. */
std::tuple<size_t, size_t> getObjectClosureSize(Object * obj);
private:
void addArena(size_t arenaSize);
void addToFreeList(Free * obj);
std::tuple<size_t, size_t, size_t, size_t> freeUnmarked(Arena & arena);
};
extern GC gc;
template<class T>
struct Ptr
{
private:
friend class GC;
Ptr * prev = nullptr, * next = nullptr;
T * value = nullptr;
void link()
{
prev = (Ptr *) gc.frontPtrSentinel;
next = prev->next;
next->prev = this;
prev->next = this;
}
public:
Ptr() {
link();
}
Ptr(T * value) : value(value)
{
link();
}
Ptr(const Ptr & p)
{
auto & p2 = const_cast<Ptr &>(p);
value = p2.value;
next = &p2;
prev = p2.prev;
prev->next = this;
p2.prev = this;
}
Ptr(Ptr && p)
{
link();
value = p.value;
p.value = nullptr;
}
Ptr & operator =(const Ptr & p)
{
value = p.value;
return *this;
}
Ptr & operator =(Ptr && p)
{
value = p.value;
p.value = nullptr;
return *this;
}
Ptr & operator =(T * v)
{
value = v;
return *this;
}
~Ptr()
{
assert(next);
assert(prev);
assert(next->prev == this);
next->prev = prev;
assert(prev->next == this);
prev->next = next;
}
T * operator ->()
{
return value;
}
T * operator ->() const // FIXME
{
return value;
}
operator T * ()
{
return value;
}
operator T & ()
{
assert(value);
return *value;
}
operator bool() const
{
return value != nullptr;
}
};
template<class T>
struct Root
{
Root * prev = nullptr, * next = nullptr;
T value;
template<typename... Args>
Root(const Args & ... args)
: value{args... }
{
prev = (Root *) gc.frontRootSentinel;
next = prev->next;
next->prev = this;
prev->next = this;
}
Root(const Root & p) = delete;
Root(Root && p) = delete;
Root & operator =(const T & v) { value = v; return *this; }
~Root()
{
assert(next);
assert(prev);
assert(next->prev == this);
next->prev = prev;
assert(prev->next == this);
prev->next = next;
}
T * operator ->()
{
return &value;
}
operator T * ()
{
return &value;
}
operator T & ()
{
return value;
}
};
struct String : Object
{
char s[0];
String(size_t len, const char * src)
: Object(tString, len)
{
std::memcpy(s, src, len + 1);
}
size_t words() const { return wordsFor(getMisc()); }
/* Return the number of words needed to store a string of 'len'
characters (where 'len' excludes the terminator). */
static size_t wordsFor(size_t len)
{
return len / WORD_SIZE + 2;
}
static String * alloc(const char * src)
{
auto len = strlen(src);
return gc.alloc<String>(wordsFor(len), len, src);
}
};
}

View File

@@ -115,15 +115,15 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
return outputs;
/* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
const Value * outTI = queryMeta("outputsToInstall");
const auto outTI = queryMeta("outputsToInstall");
if (!outTI) return outputs;
const auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
/* ^ this shows during `nix-env -i` right under the bad derivation */
if (!outTI->isList()) throw errMsg;
Outputs result;
for (auto i = outTI->listElems(); i != outTI->listElems() + outTI->listSize(); ++i) {
if ((*i)->type != tString) throw errMsg;
auto out = outputs.find((*i)->string.s);
if (!(*i)->isString()) throw errMsg;
auto out = outputs.find((*i)->getString());
if (out == outputs.end()) throw errMsg;
result.insert(*out);
}
@@ -178,8 +178,7 @@ bool DrvInfo::checkMeta(Value & v)
if (!checkMeta(*i.value)) return false;
return true;
}
else return v.type == tInt || v.type == tBool || v.type == tString ||
v.type == tFloat;
else return v.type == tInt || v.type == tBool || v.isString() || v.type == tFloat;
}
@@ -194,36 +193,36 @@ Value * DrvInfo::queryMeta(const string & name)
string DrvInfo::queryMetaString(const string & name)
{
Value * v = queryMeta(name);
if (!v || v->type != tString) return "";
return v->string.s;
auto v = queryMeta(name);
if (!v || !v->isString()) return "";
return v->getString();
}
NixInt DrvInfo::queryMetaInt(const string & name, NixInt def)
{
Value * v = queryMeta(name);
auto v = queryMeta(name);
if (!v) return def;
if (v->type == tInt) return v->integer;
if (v->type == tString) {
if (v->isString()) {
/* Backwards compatibility with before we had support for
integer meta fields. */
NixInt n;
if (string2Int(v->string.s, n)) return n;
if (string2Int(v->getString(), n)) return n;
}
return def;
}
NixFloat DrvInfo::queryMetaFloat(const string & name, NixFloat def)
{
Value * v = queryMeta(name);
auto v = queryMeta(name);
if (!v) return def;
if (v->type == tFloat) return v->fpoint;
if (v->type == tString) {
if (v->isString()) {
/* Backwards compatibility with before we had support for
float meta fields. */
NixFloat n;
if (string2Float(v->string.s, n)) return n;
if (string2Float(v->getString(), n)) return n;
}
return def;
}
@@ -231,14 +230,15 @@ NixFloat DrvInfo::queryMetaFloat(const string & name, NixFloat def)
bool DrvInfo::queryMetaBool(const string & name, bool def)
{
Value * v = queryMeta(name);
auto v = queryMeta(name);
if (!v) return def;
if (v->type == tBool) return v->boolean;
if (v->type == tString) {
if (v->isString()) {
/* Backwards compatibility with before we had support for
Boolean meta fields. */
if (strcmp(v->string.s, "true") == 0) return true;
if (strcmp(v->string.s, "false") == 0) return false;
auto s = v->getString();
if (strcmp(s, "true") == 0) return true;
if (strcmp(s, "false") == 0) return false;
}
return def;
}
@@ -247,8 +247,8 @@ bool DrvInfo::queryMetaBool(const string & name, bool def)
void DrvInfo::setMeta(const string & name, Value * v)
{
getMeta();
Bindings * old = meta;
meta = state->allocBindings(1 + (old ? old->size() : 0));
Ptr<Bindings> old = meta;
meta = Bindings::allocBindings(1 + (old ? old->size() : 0));
Symbol sym = state->symbols.create(name);
if (old)
for (auto i : *old)
@@ -260,6 +260,7 @@ void DrvInfo::setMeta(const string & name, Value * v)
/* Cache for already considered attrsets. */
// FIXME: Use Ptr?
typedef set<Bindings *> Done;
@@ -319,24 +320,24 @@ static void getDerivations(EvalState & state, Value & vIn,
DrvInfos & drvs, Done & done,
bool ignoreAssertionFailures)
{
Value v;
Root<Value> v;
state.autoCallFunction(autoArgs, vIn, v);
/* Process the expression. */
if (!getDerivation(state, v, pathPrefix, drvs, done, ignoreAssertionFailures)) ;
else if (v.type == tAttrs) {
else if (v->type == tAttrs) {
/* !!! undocumented hackery to support combining channels in
nix-env.cc. */
bool combineChannels = v.attrs->find(state.symbols.create("_combineChannels")) != v.attrs->end();
bool combineChannels = v->attrs->find(state.symbols.create("_combineChannels")) != v->attrs->end();
/* Consider the attributes in sorted order to get more
deterministic behaviour in nix-env operations (e.g. when
there are names clashes between derivations, the derivation
bound to the attribute with the "lower" name should take
precedence). */
for (auto & i : v.attrs->lexicographicOrder()) {
for (auto & i : v->attrs->lexicographicOrder()) {
debug("evaluating attribute '%1%'", i->name);
if (!std::regex_match(std::string(i->name), attrRegex))
continue;
@@ -356,11 +357,11 @@ static void getDerivations(EvalState & state, Value & vIn,
}
}
else if (v.isList()) {
for (unsigned int n = 0; n < v.listSize(); ++n) {
else if (v->isList()) {
for (unsigned int n = 0; n < v->listSize(); ++n) {
string pathPrefix2 = addToPath(pathPrefix, (format("%1%") % n).str());
if (getDerivation(state, *v.listElems()[n], pathPrefix2, drvs, done, ignoreAssertionFailures))
getDerivations(state, *v.listElems()[n], pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
if (getDerivation(state, *v->listElems()[n], pathPrefix2, drvs, done, ignoreAssertionFailures))
getDerivations(state, *v->listElems()[n], pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
}
}

View File

@@ -26,7 +26,8 @@ private:
bool failed = false; // set if we get an AssertionError
Bindings * attrs = nullptr, * meta = nullptr;
Ptr<Bindings> attrs;
Ptr<Bindings> meta;
Bindings * getMeta();
@@ -69,11 +70,7 @@ public:
};
#if HAVE_BOEHMGC
typedef list<DrvInfo, traceable_allocator<DrvInfo> > DrvInfos;
#else
typedef list<DrvInfo> DrvInfos;
#endif
typedef std::list<DrvInfo> DrvInfos;
/* If value `v' denotes a derivation, return a DrvInfo object

View File

@@ -11,21 +11,22 @@ namespace nix {
// for more information, refer to
// https://github.com/nlohmann/json/blob/master/include/nlohmann/detail/input/json_sax.hpp
class JSONSax : nlohmann::json_sax<json> {
class JSONState {
protected:
unique_ptr<JSONState> parent;
Value * v;
Ptr<Value> v;
public:
virtual unique_ptr<JSONState> resolve(EvalState &)
{
throw std::logic_error("tried to close toplevel json parser state");
};
explicit JSONState(unique_ptr<JSONState>&& p) : parent(std::move(p)), v(nullptr) {};
explicit JSONState(Value* v) : v(v) {};
JSONState(JSONState& p) = delete;
Value& value(EvalState & state)
explicit JSONState(unique_ptr<JSONState> && p) : parent(std::move(p)), v(nullptr) {};
explicit JSONState(Value * v) : v(v) {};
JSONState(JSONState & p) = delete;
Value & value(EvalState & state)
{
if (v == nullptr)
if (!v)
v = state.allocValue();
return *v;
};
@@ -38,7 +39,7 @@ class JSONSax : nlohmann::json_sax<json> {
ValueMap attrs = ValueMap();
virtual unique_ptr<JSONState> resolve(EvalState & state) override
{
Value& v = parent->value(state);
Value & v = parent->value(state);
state.mkAttrs(v, attrs.size());
for (auto & i : attrs)
v.attrs->push_back(Attr(i.first, i.second));
@@ -46,7 +47,7 @@ class JSONSax : nlohmann::json_sax<json> {
}
virtual void add() override { v = nullptr; };
public:
void key(string_t& name, EvalState & state)
void key(string_t & name, EvalState & state)
{
attrs[state.symbols.create(name)] = &value(state);
}
@@ -56,7 +57,7 @@ class JSONSax : nlohmann::json_sax<json> {
ValueVector values = ValueVector();
virtual unique_ptr<JSONState> resolve(EvalState & state) override
{
Value& v = parent->value(state);
Value & v = parent->value(state);
state.mkList(v, values.size());
for (size_t n = 0; n < values.size(); ++n) {
v.listElems()[n] = values[n];
@@ -68,7 +69,7 @@ class JSONSax : nlohmann::json_sax<json> {
v = nullptr;
};
public:
JSONListState(unique_ptr<JSONState>&& p, std::size_t reserve) : JSONState(std::move(p))
JSONListState(unique_ptr<JSONState> && p, std::size_t reserve) : JSONState(std::move(p))
{
values.reserve(reserve);
}
@@ -114,7 +115,7 @@ public:
bool string(string_t& val)
{
return handle_value<void(Value&, const char*)>(mkString, val.c_str());
return handle_value<Value & (Value &, std::string_view)>(mkString, (std::string_view) val);
}
bool start_object(std::size_t len)

View File

@@ -13,11 +13,6 @@ ifneq ($(OS), FreeBSD)
libexpr_LDFLAGS += -ldl
endif
# The dependency on libgc must be propagated (i.e. meaning that
# programs/libraries that use libexpr must explicitly pass -lgc),
# because inline functions in libexpr's header files call libgc.
libexpr_LDFLAGS_PROPAGATED = $(BDW_GC_LIBS)
libexpr_ORDER_AFTER := $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer-tab.hh
$(d)/parser-tab.cc $(d)/parser-tab.hh: $(d)/parser.y

View File

@@ -80,7 +80,7 @@ void ExprString::show(std::ostream & str) const
void ExprPath::show(std::ostream & str) const
{
str << s;
str << v->path->s;
}
void ExprVar::show(std::ostream & str) const

View File

@@ -78,7 +78,7 @@ struct Expr
virtual void show(std::ostream & str) const;
virtual void bindVars(const StaticEnv & env);
virtual void eval(EvalState & state, Env & env, Value & v);
virtual Value * maybeThunk(EvalState & state, Env & env);
virtual Ptr<Value> maybeThunk(EvalState & state, Env & env);
virtual void setName(Symbol & name);
};
@@ -92,28 +92,37 @@ std::ostream & operator << (std::ostream & str, const Expr & e);
struct ExprInt : Expr
{
NixInt n;
Value v;
ExprInt(NixInt n) : n(n) { mkInt(v, n); };
Ptr<Value> v;
ExprInt(NixInt n) : n(n) {
v = gc.alloc<Value>(Value::words());
mkInt(v, n);
};
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env);
Ptr<Value> maybeThunk(EvalState & state, Env & env) override;
};
struct ExprFloat : Expr
{
NixFloat nf;
Value v;
ExprFloat(NixFloat nf) : nf(nf) { mkFloat(v, nf); };
Ptr<Value> v;
ExprFloat(NixFloat nf) : nf(nf) {
v = gc.alloc<Value>(Value::words());
mkFloat(v, nf);
};
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env);
Ptr<Value> maybeThunk(EvalState & state, Env & env) override;
};
struct ExprString : Expr
{
Symbol s;
Value v;
ExprString(const Symbol & s) : s(s) { mkString(v, s); };
Ptr<Value> v;
ExprString(const Symbol & s) : s(s) {
v = gc.alloc<Value>(Value::words());
mkString(v, s);
};
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env);
Ptr<Value> maybeThunk(EvalState & state, Env & env) override;
};
/* Temporary class used during parsing of indented strings. */
@@ -125,11 +134,13 @@ struct ExprIndStr : Expr
struct ExprPath : Expr
{
string s;
Value v;
ExprPath(const string & s) : s(s) { mkPathNoCopy(v, this->s.c_str()); };
Ptr<Value> v;
ExprPath(const string & s) {
v = gc.alloc<Value>(Value::words());
mkPath(v, s);
};
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env);
Ptr<Value> maybeThunk(EvalState & state, Env & env) override;
};
struct ExprVar : Expr
@@ -153,7 +164,7 @@ struct ExprVar : Expr
ExprVar(const Symbol & name) : name(name) { };
ExprVar(const Pos & pos, const Symbol & name) : pos(pos), name(name) { };
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env);
Ptr<Value> maybeThunk(EvalState & state, Env & env) override;
};
struct ExprSelect : Expr

View File

@@ -103,14 +103,13 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
// FIXME
if (state.store->isStorePath(path) && state.store->isValidPath(state.store->parseStorePath(path)) && isDerivation(path)) {
Derivation drv = readDerivation(*state.store, realPath);
Value & w = *state.allocValue();
auto w = state.allocValue();
state.mkAttrs(w, 3 + drv.outputs.size());
Value * v2 = state.allocAttr(w, state.sDrvPath);
auto v2 = state.allocAttr(w, state.sDrvPath);
mkString(*v2, path, {"=" + path});
v2 = state.allocAttr(w, state.sName);
mkString(*v2, drv.env["name"]);
Value * outputsVal =
state.allocAttr(w, state.symbols.create("outputs"));
auto outputsVal = state.allocAttr(w, state.symbols.create("outputs"));
state.mkList(*outputsVal, drv.outputs.size());
unsigned int outputs_index = 0;
@@ -120,8 +119,8 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
outputsVal->listElems()[outputs_index] = state.allocValue();
mkString(*(outputsVal->listElems()[outputs_index++]), o.first);
}
w.attrs->sort();
Value fun;
w->attrs->sort();
auto fun = state.allocValue();
state.evalFile(settings.nixDataDir + "/nix/corepkgs/imported-drv-to-derivation.nix", fun);
state.forceFunction(fun, pos);
mkApp(v, fun, w);
@@ -131,8 +130,8 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
if (args[0]->attrs->empty())
state.evalFile(realPath, v);
else {
Env * env = &state.allocEnv(args[0]->attrs->size());
env->up = &state.baseEnv;
auto env = state.allocEnv(args[0]->attrs->size());
env->up = state.baseEnv;
StaticEnv staticEnv(false, &state.staticBaseEnv);
@@ -240,19 +239,24 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu
switch (args[0]->type) {
case tInt: t = "int"; break;
case tBool: t = "bool"; break;
case tString: t = "string"; break;
case tShortString:
case tStaticString:
case tLongString:
t = "string"; break;
case tPath: t = "path"; break;
case tNull: t = "null"; break;
case tAttrs: t = "set"; break;
case tList1: case tList2: case tListN: t = "list"; break;
case tList0: case tList1: case tList2: case tListN: t = "list"; break;
case tLambda:
case tPrimOp:
case tPrimOpApp:
t = "lambda";
break;
#if 0
case tExternal:
t = args[0]->external->typeOf();
break;
#endif
case tFloat: t = "float"; break;
default: abort();
}
@@ -305,7 +309,7 @@ static void prim_isFloat(EvalState & state, const Pos & pos, Value * * args, Val
static void prim_isString(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceValue(*args[0]);
mkBool(v, args[0]->type == tString);
mkBool(v, args[0]->isString());
}
@@ -331,6 +335,8 @@ struct CompareValues
return v1->fpoint < v2->integer;
if (v1->type == tInt && v2->type == tFloat)
return v1->integer < v2->fpoint;
if (v1->isString() && v2->isString())
return strcmp(v1->getString(), v2->getString()) < 0;
if (v1->type != v2->type)
throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % showType(*v2));
switch (v1->type) {
@@ -338,10 +344,8 @@ struct CompareValues
return v1->integer < v2->integer;
case tFloat:
return v1->fpoint < v2->fpoint;
case tString:
return strcmp(v1->string.s, v2->string.s) < 0;
case tPath:
return strcmp(v1->path, v2->path) < 0;
return strcmp(v1->path->s, v2->path->s) < 0;
default:
throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % showType(*v2));
}
@@ -349,11 +353,7 @@ struct CompareValues
};
#if HAVE_BOEHMGC
typedef list<Value *, gc_allocator<Value *> > ValueList;
#else
typedef list<Value *> ValueList;
#endif
typedef list<Ptr<Value>> ValueList;
static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * args, Value & v)
@@ -383,10 +383,10 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
no new elements are found. */
ValueList res;
// `doneKeys' doesn't need to be a GC root, because its values are
// reachable from res.
// reachable from res. FIXME: dubious.
set<Value *, CompareValues> doneKeys;
while (!workSet.empty()) {
Value * e = *(workSet.begin());
auto e = *(workSet.begin());
workSet.pop_front();
state.forceAttrs(*e, pos);
@@ -401,14 +401,14 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
res.push_back(e);
/* Call the `operator' function with `e' as argument. */
Value call;
Root<Value> call;
mkApp(call, *op->value, *e);
state.forceList(call, pos);
/* Add the values returned by the operator to the work set. */
for (unsigned int n = 0; n < call.listSize(); ++n) {
state.forceValue(*call.listElems()[n]);
workSet.push_back(call.listElems()[n]);
for (unsigned int n = 0; n < call->listSize(); ++n) {
state.forceValue(*call->listElems()[n]);
workSet.push_back(call->listElems()[n]);
}
}
@@ -498,10 +498,10 @@ static void prim_deepSeq(EvalState & state, const Pos & pos, Value * * args, Val
static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceValue(*args[0]);
if (args[0]->type == tString)
printError(format("trace: %1%") % args[0]->string.s);
if (args[0]->isString())
printError("trace: %s", args[0]->getString());
else
printError(format("trace: %1%") % *args[0]);
printError("trace: %s", *args[0]);
state.forceValue(*args[1]);
v = *args[1];
}
@@ -749,6 +749,8 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
auto drvPath = writeDerivation(state.store, drv, drvName, state.repair);
auto drvPathS = state.store->printStorePath(drvPath);
if (state.derivationHook) state.derivationHook(drvPath, drv);
printMsg(lvlChatty, "instantiated '%1%' -> '%2%'", drvName, drvPathS);
/* Optimisation, but required in read-only mode! because in that
@@ -950,15 +952,20 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val
DirEntries entries = readDirectory(state.checkSourcePath(path));
state.mkAttrs(v, entries.size());
auto sRegular = state.symbols.create("regular");
auto sDirectory = state.symbols.create("directory");
auto sSymlink = state.symbols.create("symlink");
auto sUnknown = state.symbols.create("unknown");
for (auto & ent : entries) {
Value * ent_val = state.allocAttr(v, state.symbols.create(ent.name));
auto ent_val = state.allocAttr(v, state.symbols.create(ent.name));
if (ent.type == DT_UNKNOWN)
ent.type = getFileType(path + "/" + ent.name);
mkStringNoCopy(*ent_val,
ent.type == DT_REG ? "regular" :
ent.type == DT_DIR ? "directory" :
ent.type == DT_LNK ? "symlink" :
"unknown");
mkString(*ent_val,
ent.type == DT_REG ? sRegular :
ent.type == DT_DIR ? sDirectory :
ent.type == DT_LNK ? sSymlink :
sUnknown);
}
v.attrs->sort();
@@ -1041,20 +1048,20 @@ static void addPath(EvalState & state, const Pos & pos, const string & name, con
/* Call the filter function. The first argument is the path,
the second is a string indicating the type of the file. */
Value arg1;
auto arg1 = state.allocValue();
mkString(arg1, path);
Value fun2;
Root<Value> fun2;
state.callFunction(*filterFun, arg1, fun2, noPos);
Value arg2;
auto arg2 = state.allocValue();
mkString(arg2,
S_ISREG(st.st_mode) ? "regular" :
S_ISDIR(st.st_mode) ? "directory" :
S_ISLNK(st.st_mode) ? "symlink" :
"unknown" /* not supported, will fail! */);
Value res;
Root<Value> res;
state.callFunction(fun2, arg2, res, noPos);
return state.forceBool(res, pos);
@@ -1146,7 +1153,7 @@ static void prim_attrNames(EvalState & state, const Pos & pos, Value * * args, V
mkString(*(v.listElems()[n++] = state.allocValue()), i.name);
std::sort(v.listElems(), v.listElems() + n,
[](Value * v1, Value * v2) { return strcmp(v1->string.s, v2->string.s) < 0; });
[](Value * v1, Value * v2) { return strcmp(v1->getString(), v2->getString()) < 0; });
}
@@ -1223,10 +1230,9 @@ static void prim_removeAttrs(EvalState & state, const Pos & pos, Value * * args,
/* Get the attribute names to be removed. */
std::set<Symbol> names;
for (unsigned int i = 0; i < args[1]->listSize(); ++i) {
state.forceStringNoCtx(*args[1]->listElems()[i], pos);
names.insert(state.symbols.create(args[1]->listElems()[i]->string.s));
}
for (unsigned int i = 0; i < args[1]->listSize(); ++i)
names.insert(state.symbols.create(
state.forceStringNoCtx(*args[1]->listElems()[i], pos)));
/* Copy all attributes not in that set. Note that we don't need
to sort v.attrs because it's a subset of an already sorted
@@ -1362,8 +1368,8 @@ static void prim_mapAttrs(EvalState & state, const Pos & pos, Value * * args, Va
state.mkAttrs(v, args[1]->attrs->size());
for (auto & i : *args[1]->attrs) {
Value * vName = state.allocValue();
Value * vFun2 = state.allocValue();
auto vName = state.allocValue();
auto vFun2 = state.allocValue();
mkString(*vName, i.name);
mkApp(*vFun2, *args[0], *vName);
mkApp(*state.allocAttr(v, i.name), *vFun2, *i.value);
@@ -1450,7 +1456,7 @@ static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Valu
bool same = true;
for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
Value res;
Root<Value> res;
state.callFunction(*args[0], *args[1]->listElems()[n], res, noPos);
if (state.forceBool(res, pos))
vs[k++] = args[1]->listElems()[n];
@@ -1505,12 +1511,12 @@ static void prim_foldlStrict(EvalState & state, const Pos & pos, Value * * args,
state.forceList(*args[2], pos);
if (args[2]->listSize()) {
Value * vCur = args[1];
Ptr<Value> vCur = args[1];
for (unsigned int n = 0; n < args[2]->listSize(); ++n) {
Value vTmp;
Root<Value> vTmp; // FIXME: correct?
state.callFunction(*args[0], *vCur, vTmp, pos);
vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue();
vCur = n == args[2]->listSize() - 1 ? Ptr(&v) : state.allocValue();
state.callFunction(vTmp, *args[2]->listElems()[n], *vCur, pos);
}
state.forceValue(v);
@@ -1526,7 +1532,7 @@ static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * arg
state.forceFunction(*args[0], pos);
state.forceList(*args[1], pos);
Value vTmp;
Root<Value> vTmp;
for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
state.callFunction(*args[0], *args[1]->listElems()[n], vTmp, pos);
bool res = state.forceBool(vTmp, pos);
@@ -1562,7 +1568,7 @@ static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Val
state.mkList(v, len);
for (unsigned int n = 0; n < (unsigned int) len; ++n) {
Value * arg = state.allocValue();
auto arg = state.allocValue();
mkInt(*arg, n);
mkApp(*(v.listElems()[n] = state.allocValue()), *args[0], *arg);
}
@@ -1591,7 +1597,7 @@ static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value
if (args[0]->type == tPrimOp && args[0]->primOp->fun == prim_lessThan)
return CompareValues()(a, b);
Value vTmp1, vTmp2;
Root<Value> vTmp1, vTmp2; // FIXME
state.callFunction(*args[0], *a, vTmp1, pos);
state.callFunction(vTmp1, *b, vTmp2, pos);
return state.forceBool(vTmp2, pos);
@@ -1611,12 +1617,13 @@ static void prim_partition(EvalState & state, const Pos & pos, Value * * args, V
auto len = args[1]->listSize();
ValueVector right, wrong;
// Note: these Values are reachable via args[0].
std::vector<Value *> right, wrong;
for (unsigned int n = 0; n < len; ++n) {
auto vElem = args[1]->listElems()[n];
state.forceValue(*vElem);
Value res;
Root<Value> res;
state.callFunction(*args[0], *vElem, res, pos);
if (state.forceBool(res, pos))
right.push_back(vElem);
@@ -1626,13 +1633,13 @@ static void prim_partition(EvalState & state, const Pos & pos, Value * * args, V
state.mkAttrs(v, 2);
Value * vRight = state.allocAttr(v, state.sRight);
auto vRight = state.allocAttr(v, state.sRight);
auto rsize = right.size();
state.mkList(*vRight, rsize);
if (rsize)
memcpy(vRight->listElems(), right.data(), sizeof(Value *) * rsize);
Value * vWrong = state.allocAttr(v, state.sWrong);
auto vWrong = state.allocAttr(v, state.sWrong);
auto wsize = wrong.size();
state.mkList(*vWrong, wsize);
if (wsize)
@@ -1650,22 +1657,23 @@ static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, V
state.forceList(*args[1], pos);
auto nrLists = args[1]->listSize();
Value lists[nrLists];
// FIXME: Root<>[] is inefficient
Root<Value> lists[nrLists];
size_t len = 0;
for (unsigned int n = 0; n < nrLists; ++n) {
Value * vElem = args[1]->listElems()[n];
state.callFunction(*args[0], *vElem, lists[n], pos);
state.forceList(lists[n], pos);
len += lists[n].listSize();
len += lists[n]->listSize();
}
state.mkList(v, len);
auto out = v.listElems();
for (unsigned int n = 0, pos = 0; n < nrLists; ++n) {
auto l = lists[n].listSize();
auto l = lists[n]->listSize();
if (l)
memcpy(out + pos, lists[n].listElems(), l * sizeof(Value *));
memcpy(out + pos, lists[n]->listElems(), l * sizeof(Value *));
pos += l;
}
}
@@ -2115,10 +2123,10 @@ RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun)
void EvalState::createBaseEnv()
{
baseEnv.up = 0;
baseEnv->up = 0;
/* Add global constants such as `true' to the base environment. */
Value v;
auto v = allocValue();
/* `builtins' must be first! */
mkAttrs(v, 128);
@@ -2136,7 +2144,7 @@ void EvalState::createBaseEnv()
auto vThrow = addPrimOp("throw", 1, prim_throw);
auto addPurityError = [&](const std::string & name) {
Value * v2 = allocValue();
auto v2 = allocValue();
mkString(*v2, fmt("'%s' is not allowed in pure evaluation mode", name));
mkApp(v, *vThrow, *v2);
addConstant(name, v);
@@ -2167,7 +2175,7 @@ void EvalState::createBaseEnv()
// Miscellaneous
auto vScopedImport = addPrimOp("scopedImport", 2, prim_scopedImport);
Value * v2 = allocValue();
auto v2 = allocValue();
mkAttrs(*v2, 0);
mkApp(v, *vScopedImport, *v2);
forceValue(v);
@@ -2295,7 +2303,7 @@ void EvalState::createBaseEnv()
mkList(v, searchPath.size());
int n = 0;
for (auto & i : searchPath) {
v2 = v.listElems()[n++] = allocValue();
v2 = v->listElems()[n++] = allocValue();
mkAttrs(*v2, 2);
mkString(*allocAttr(*v2, symbols.create("path")), i.second);
mkString(*allocAttr(*v2, symbols.create("prefix")), i.first);
@@ -2309,7 +2317,7 @@ void EvalState::createBaseEnv()
/* Now that we've added all primops, sort the `builtins' set,
because attribute lookups expect it to be sorted. */
baseEnv.values[0]->attrs->sort();
baseEnv->values[0]->attrs->sort();
}

View File

@@ -160,7 +160,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, *iter->pos)) {
if (!isDerivation(i.name)) {
throw EvalError("Tried to add all-outputs context of %s, which is not a derivation, to a string, at %s", i.name, i.pos);
throw EvalError("tried to add all-outputs context of %s, which is not a derivation, to a string, at %s", i.name, *i.pos);
}
context.insert("=" + string(i.name));
}
@@ -170,7 +170,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
if (iter != i.value->attrs->end()) {
state.forceList(*iter->value, *iter->pos);
if (iter->value->listSize() && !isDerivation(i.name)) {
throw EvalError("Tried to add derivation output context of %s, which is not a derivation, to a string, at %s", i.name, i.pos);
throw EvalError("tried to add derivation output context of %s, which is not a derivation, to a string, at %s", i.name, *i.pos);
}
for (unsigned int n = 0; n < iter->value->listSize(); ++n) {
auto name = state.forceStringNoCtx(*iter->value->listElems()[n], *iter->pos);

View File

@@ -19,6 +19,7 @@ private:
const string * s; // pointer into SymbolTable
Symbol(const string * s) : s(s) { };
friend class SymbolTable;
friend class Value;
public:
Symbol() : s(0) { };
@@ -89,4 +90,6 @@ public:
}
};
extern SymbolTable symbols;
}

View File

@@ -26,13 +26,15 @@ void printValueAsJSON(EvalState & state, bool strict,
out.write(v.boolean);
break;
case tString:
copyContext(v, context);
out.write(v.string.s);
case tShortString:
case tStaticString:
case tLongString:
v.getContext(context);
out.write(v.getString());
break;
case tPath:
out.write(state.copyPathToStore(context, v.path));
out.write(state.copyPathToStore(context, v.path->s));
break;
case tNull:
@@ -61,7 +63,7 @@ void printValueAsJSON(EvalState & state, bool strict,
break;
}
case tList1: case tList2: case tListN: {
case tList0: case tList1: case tList2: case tListN: {
auto list(out.list());
for (unsigned int n = 0; n < v.listSize(); ++n) {
auto placeholder(list.placeholder());
@@ -70,9 +72,11 @@ void printValueAsJSON(EvalState & state, bool strict,
break;
}
#if 0
case tExternal:
v.external->printValueAsJSON(state, strict, out, context);
break;
#endif
case tFloat:
out.write(v.fpoint);
@@ -90,11 +94,13 @@ void printValueAsJSON(EvalState & state, bool strict,
printValueAsJSON(state, strict, v, out, context);
}
#if 0
void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
JSONPlaceholder & out, PathSet & context) const
{
throw TypeError(format("cannot convert %1% to JSON") % showType());
}
#endif
}

View File

@@ -68,14 +68,16 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
doc.writeEmptyElement("bool", singletonAttrs("value", v.boolean ? "true" : "false"));
break;
case tString:
case tShortString:
case tStaticString:
case tLongString:
/* !!! show the context? */
copyContext(v, context);
doc.writeEmptyElement("string", singletonAttrs("value", v.string.s));
v.getContext(context);
doc.writeEmptyElement("string", singletonAttrs("value", v.getString()));
break;
case tPath:
doc.writeEmptyElement("path", singletonAttrs("value", v.path));
doc.writeEmptyElement("path", singletonAttrs("value", v.path->s));
break;
case tNull:
@@ -92,15 +94,15 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
a = v.attrs->find(state.sDrvPath);
if (a != v.attrs->end()) {
if (strict) state.forceValue(*a->value);
if (a->value->type == tString)
xmlAttrs["drvPath"] = drvPath = a->value->string.s;
if (a->value->isString())
xmlAttrs["drvPath"] = drvPath = a->value->getString();
}
a = v.attrs->find(state.sOutPath);
if (a != v.attrs->end()) {
if (strict) state.forceValue(*a->value);
if (a->value->type == tString)
xmlAttrs["outPath"] = a->value->string.s;
if (a->value->isString())
xmlAttrs["outPath"] = a->value->getString();
}
XMLOpenElement _(doc, "derivation", xmlAttrs);
@@ -118,7 +120,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break;
case tList1: case tList2: case tListN: {
case tList0: case tList1: case tList2: case tListN: {
XMLOpenElement _(doc, "list");
for (unsigned int n = 0; n < v.listSize(); ++n)
printValueAsXML(state, strict, location, *v.listElems()[n], doc, context, drvsSeen);
@@ -143,9 +145,11 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break;
}
#if 0
case tExternal:
v.external->printValueAsXML(state, strict, location, doc, context, drvsSeen);
break;
#endif
case tFloat:
doc.writeEmptyElement("float", singletonAttrs("value", (format("%1%") % v.fpoint).str()));
@@ -157,11 +161,13 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
}
#if 0
void ExternalValueBase::printValueAsXML(EvalState & state, bool strict,
bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen) const
{
doc.writeEmptyElement("unevaluated");
}
#endif
void printValueAsXML(EvalState & state, bool strict, bool location,

View File

@@ -1,35 +1,12 @@
#pragma once
#include "symbol-table.hh"
#include "gc.hh"
#if HAVE_BOEHMGC
#include <gc/gc_allocator.h>
#endif
#include <cstring>
namespace nix {
typedef enum {
tInt = 1,
tBool,
tString,
tPath,
tNull,
tAttrs,
tList1,
tList2,
tListN,
tThunk,
tApp,
tLambda,
tBlackhole,
tPrimOp,
tPrimOpApp,
tExternal,
tFloat
} ValueType;
class Bindings;
struct Env;
struct Expr;
@@ -45,13 +22,15 @@ class JSONPlaceholder;
typedef int64_t NixInt;
typedef double NixFloat;
#if 0
/* External values must descend from ExternalValueBase, so that
* type-agnostic nix functions (e.g. showType) can be implemented
*/
class ExternalValueBase
{
friend std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
protected:
protected:
/* Print out the value */
virtual std::ostream & print(std::ostream & str) const = 0;
@@ -86,11 +65,29 @@ class ExternalValueBase
};
std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
#endif
struct Value
class Context : Object
{
friend class Value;
friend class GC;
Symbol members[0];
Context(const PathSet & context) : Object(tContext, context.size())
{
size_t n = 0;
for (auto & i : context)
members[n++] = symbols.create(i);
}
size_t getSize() { return getMisc(); }
};
struct Value : Object
{
ValueType type;
union
{
NixInt integer;
@@ -113,20 +110,15 @@ struct Value
with context C is used as a derivation attribute, then the
derivations in C will be added to the inputDrvs of the
derivation, and the other store paths in C will be added to
the inputSrcs of the derivations.
For canonicity, the store paths should be in sorted order. */
the inputSrcs of the derivations. */
struct {
const char * s;
const char * * context; // must be in sorted order
String * s;
Context * context;
} string;
const char * path;
const char * staticString;
String * path;
Bindings * attrs;
struct {
size_t size;
Value * * elems;
} bigList;
PtrList<Value> * bigList;
Value * smallList[2];
struct {
Env * env;
@@ -140,46 +132,99 @@ struct Value
ExprLambda * fun;
} lambda;
PrimOp * primOp;
struct {
Value * left, * right;
} primOpApp;
ExternalValueBase * external;
//ExternalValueBase * external;
NixFloat fpoint;
};
private:
Value() : Object(tNull, 0) {}
friend class GC;
template<typename T> friend class Root;
public:
bool isList() const
{
return type == tList1 || type == tList2 || type == tListN;
return type >= tList0 && type <= tListN;
}
Value * * listElems()
{
return type == tList1 || type == tList2 ? smallList : bigList.elems;
return type == tList0 || type == tList1 || type == tList2 ? smallList : bigList->elems;
}
const Value * const * listElems() const
{
return type == tList1 || type == tList2 ? smallList : bigList.elems;
return type == tList0 || type == tList1 || type == tList2 ? smallList : bigList->elems;
}
size_t listSize() const
{
return type == tList1 ? 1 : type == tList2 ? 2 : bigList.size;
return type == tList0 ? 0 : type == tList1 ? 1 : type == tList2 ? 2 : bigList->size();
}
constexpr static size_t words() { return 3; } // FIXME
void setContext(const PathSet & context)
{
if (context.size() == 0)
string.context = nullptr;
else if (context.size() == 1) {
// If we have a single context, then store it
// directly. This saves allocating a Context object (16
// bytes).
auto symbol = symbols.create(*context.begin());
string.context = (Context *) (((ptrdiff_t) symbol.s) | 1);
} else {
string.context = gc.alloc<Context>(1 + context.size(), context);
}
}
void getContext(PathSet & context)
{
if (type == tLongString && string.context) {
if (((ptrdiff_t) string.context) & 1) {
auto s = (const std::string *) (((ptrdiff_t) string.context) & ~1UL);
context.insert(*s);
} else {
auto size = string.context->getSize();
for (size_t i = 0; i < size; ++i)
context.insert(string.context->members[i]);
}
}
}
bool isString() const
{
return type == tShortString || type == tStaticString || type == tLongString;
}
void setShortString(std::string_view s)
{
// FIXME: can't use strcpy here because gcc flags it as a
// buffer overflow on 'misc'.
auto p = getMiscData();
memcpy(p, s.data(), s.size());
p[s.size()] = 0;
type = tShortString;
}
const char * getString() const
{
if (type == tShortString)
return getMiscData();
else if (type == tStaticString)
return staticString;
else
return string.s->s;
}
};
/* After overwriting an app node, be sure to clear pointers in the
Value to ensure that the target isn't kept alive unnecessarily. */
static inline void clearValue(Value & v)
{
v.app.left = v.app.right = 0;
}
static inline void mkInt(Value & v, NixInt n)
{
clearValue(v);
v.type = tInt;
v.integer = n;
}
@@ -187,7 +232,6 @@ static inline void mkInt(Value & v, NixInt n)
static inline void mkFloat(Value & v, NixFloat n)
{
clearValue(v);
v.type = tFloat;
v.fpoint = n;
}
@@ -195,7 +239,6 @@ static inline void mkFloat(Value & v, NixFloat n)
static inline void mkBool(Value & v, bool b)
{
clearValue(v);
v.type = tBool;
v.boolean = b;
}
@@ -203,7 +246,6 @@ static inline void mkBool(Value & v, bool b)
static inline void mkNull(Value & v)
{
clearValue(v);
v.type = tNull;
}
@@ -224,41 +266,35 @@ static inline void mkPrimOpApp(Value & v, Value & left, Value & right)
}
static inline void mkStringNoCopy(Value & v, const char * s)
static inline Value & mkString(Value & v, std::string_view s)
{
v.type = tString;
v.string.s = s;
v.string.context = 0;
if (s.size() < WORD_SIZE * 2 + Object::miscBytes)
v.setShortString(s);
else {
v.string.s = gc.alloc<String>(String::wordsFor(s.size()), s.size(), s.data());
v.string.context = 0;
v.type = tLongString;
}
return v;
}
static inline void mkString(Value & v, const Symbol & s)
{
mkStringNoCopy(v, ((const string &) s).c_str());
v.staticString = ((const string &) s).c_str();
v.type = tStaticString;
}
void mkString(Value & v, const char * s);
static inline void mkPathNoCopy(Value & v, const char * s)
static inline void mkPath(Value & v, const std::string & s)
{
clearValue(v);
v.path = String::alloc(s.c_str());
v.type = tPath;
v.path = s;
}
void mkPath(Value & v, const char * s);
#if HAVE_BOEHMGC
typedef std::vector<Value *, gc_allocator<Value *> > ValueVector;
typedef std::map<Symbol, Value *, std::less<Symbol>, gc_allocator<std::pair<const Symbol, Value *> > > ValueMap;
#else
typedef std::vector<Value *> ValueVector;
typedef std::map<Symbol, Value *> ValueMap;
#endif
typedef std::vector<Ptr<Value>> ValueVector; // FIXME: make more efficient
typedef std::map<Symbol, Ptr<Value>> ValueMap; // FIXME: use Bindings?
}

View File

@@ -253,9 +253,6 @@ void printVersion(const string & programName)
std::cout << format("%1% (Nix) %2%") % programName % nixVersion << std::endl;
if (verbosity > lvlInfo) {
Strings cfg;
#if HAVE_BOEHMGC
cfg.push_back("gc");
#endif
#if HAVE_SODIUM
cfg.push_back("signed-caches");
#endif

View File

@@ -57,23 +57,7 @@ template<class N> N getIntArg(const string & opt,
{
++i;
if (i == end) throw UsageError(format("'%1%' requires an argument") % opt);
string s = *i;
N multiplier = 1;
if (allowUnit && !s.empty()) {
char u = std::toupper(*s.rbegin());
if (std::isalpha(u)) {
if (u == 'K') multiplier = 1ULL << 10;
else if (u == 'M') multiplier = 1ULL << 20;
else if (u == 'G') multiplier = 1ULL << 30;
else if (u == 'T') multiplier = 1ULL << 40;
else throw UsageError(format("invalid unit specifier '%1%'") % u);
s.resize(s.size() - 1);
}
}
N n;
if (!string2Int(s, n))
throw UsageError(format("'%1%' requires an integer argument") % opt);
return n * multiplier;
return parseSize<N>(*i, allowUnit);
}

View File

@@ -3646,8 +3646,7 @@ void DerivationGoal::registerOutputs()
if (fixedOutput) {
bool recursive; Hash h;
i.second.parseHashInfo(recursive, h);
auto [recursive, h] = i.second.parseHashInfo();
if (!recursive) {
/* The output path should be a regular file without execute permission. */

View File

@@ -9,21 +9,19 @@
namespace nix {
void DerivationOutput::parseHashInfo(bool & recursive, Hash & hash) const
std::pair<bool, Hash> DerivationOutput::parseHashInfo() const
{
recursive = false;
bool recursive = false;
string algo = hashAlgo;
if (string(algo, 0, 2) == "r:") {
if (string(algo, 0, 2) == "r:")
recursive = true;
algo = string(algo, 2);
}
HashType hashType = parseHashType(algo);
HashType hashType = parseHashType(recursive ? string(algo, 2) : algo);
if (hashType == htUnknown)
throw Error("unknown hash algorithm '%s'", algo);
hash = Hash(this->hash, hashType);
return {recursive, Hash(this->hash, hashType)};
}

View File

@@ -22,7 +22,7 @@ struct DerivationOutput
, hashAlgo(std::move(hashAlgo))
, hash(std::move(hash))
{ }
void parseHashInfo(bool & recursive, Hash & hash) const;
std::pair<bool, Hash> parseHashInfo() const;
};
typedef std::map<string, DerivationOutput> DerivationOutputs;

View File

@@ -552,8 +552,7 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
if (out == drv.outputs.end())
throw Error("derivation '%s' does not have an output named 'out'", printStorePath(drvPath));
bool recursive; Hash h;
out->second.parseHashInfo(recursive, h);
auto [recursive, h] = out->second.parseHashInfo();
auto outPath = makeFixedOutputPath(recursive, h, drvName);
StringPairs::const_iterator j = drv.env.find("out");

View File

@@ -384,7 +384,7 @@ bool statusOk(int status);
/* Parse a string into an integer. */
template<class N> bool string2Int(const string & s, N & n)
template<typename N> bool string2Int(const string & s, N & n)
{
if (string(s, 0, 1) == "-" && !std::numeric_limits<N>::is_signed)
return false;
@@ -393,6 +393,27 @@ template<class N> bool string2Int(const string & s, N & n)
return str && str.get() == EOF;
}
template<typename N>
N parseSize(std::string s, bool allowUnit = true)
{
N multiplier = 1;
if (allowUnit && !s.empty()) {
char u = std::toupper(*s.rbegin());
if (std::isalpha(u)) {
if (u == 'K') multiplier = 1ULL << 10;
else if (u == 'M') multiplier = 1ULL << 20;
else if (u == 'G') multiplier = 1ULL << 30;
else if (u == 'T') multiplier = 1ULL << 40;
else throw Error("invalid unit specifier '%1%'", u);
s.resize(s.size() - 1);
}
}
N n;
if (!string2Int(s, n))
throw Error("'%s' is not an integer", s);
return n * multiplier;
}
/* Parse a string into a float. */
template<class N> bool string2Float(const string & s, N & n)
{

View File

@@ -248,7 +248,7 @@ static void _main(int argc, char * * argv)
auto autoArgs = myArgs.getAutoArgs(*state);
if (runEnv) {
auto newArgs = state->allocBindings(autoArgs->size() + 1);
auto newArgs = Bindings::allocBindings(autoArgs->size() + 1);
auto tru = state->allocValue();
mkBool(*tru, true);
newArgs->push_back(Attr(state->symbols.create("inNixShell"), tru));
@@ -305,11 +305,11 @@ static void _main(int argc, char * * argv)
if (attrPaths.empty()) attrPaths = {""};
for (auto e : exprs) {
Value vRoot;
Root<Value> vRoot;
state->eval(e, vRoot);
for (auto & i : attrPaths) {
Value & v(*findAlongAttrPath(*state, i, *autoArgs, vRoot));
auto v = findAlongAttrPath(*state, i, *autoArgs, vRoot);
state->forceValue(v);
getDerivations(*state, v, "", *autoArgs, drvs, false);
}
@@ -351,7 +351,7 @@ static void _main(int argc, char * * argv)
try {
auto expr = state->parseExprFromString("(import <nixpkgs> {}).bashInteractive", absPath("."));
Value v;
Root<Value> v;
state->eval(expr, v);
auto drv = getDerivation(*state, v, false);

View File

@@ -46,7 +46,7 @@ struct InstallSourceInfo
Path nixExprPath; /* for srcNixExprDrvs, srcNixExprs */
Path profile; /* for srcProfile */
string systemFilter; /* for srcNixExprDrvs */
Bindings * autoArgs;
Ptr<Bindings> autoArgs;
};
@@ -130,7 +130,7 @@ static void getAllExprs(EvalState & state,
}
/* Load the expression on demand. */
Value & vFun = state.getBuiltin("import");
Value & vArg(*state.allocValue());
auto vArg = state.allocValue();
mkString(vArg, path2);
if (v.attrs->size() == v.attrs->capacity())
throw Error(format("too many Nix expressions in directory '%1%'") % path);
@@ -175,10 +175,10 @@ static void loadDerivations(EvalState & state, Path nixExprPath,
string systemFilter, Bindings & autoArgs,
const string & pathPrefix, DrvInfos & elems)
{
Value vRoot;
auto vRoot = state.allocValue();
loadSourceExpr(state, nixExprPath, vRoot);
Value & v(*findAlongAttrPath(state, pathPrefix, autoArgs, vRoot));
auto v = findAlongAttrPath(state, pathPrefix, autoArgs, vRoot);
getDerivations(state, v, pathPrefix, autoArgs, elems, true);
@@ -352,13 +352,14 @@ static void queryInstSources(EvalState & state,
(import ./foo.nix)' = `(import ./foo.nix).bar'. */
case srcNixExprs: {
Value vArg;
auto vArg = state.allocValue();
loadSourceExpr(state, instSource.nixExprPath, vArg);
for (auto & i : args) {
Expr * eFun = state.parseExprFromString(i, absPath("."));
Value vFun, vTmp;
auto vFun = state.allocValue();
state.eval(eFun, vFun);
auto vTmp = state.allocValue();
mkApp(vTmp, vFun, vArg);
getDerivations(state, vTmp, "", *instSource.autoArgs, elems, true);
}
@@ -405,10 +406,10 @@ static void queryInstSources(EvalState & state,
}
case srcAttrPath: {
Value vRoot;
Root<Value> vRoot;
loadSourceExpr(state, instSource.nixExprPath, vRoot);
for (auto & i : args) {
Value & v(*findAlongAttrPath(state, i, *instSource.autoArgs, vRoot));
auto v = findAlongAttrPath(state, i, *instSource.autoArgs, vRoot);
getDerivations(state, v, "", *instSource.autoArgs, elems, true);
}
break;
@@ -628,7 +629,7 @@ static void opUpgrade(Globals & globals, Strings opFlags, Strings opArgs)
static void setMetaFlag(EvalState & state, DrvInfo & drv,
const string & name, const string & value)
{
Value * v = state.allocValue();
auto v = state.allocValue();
mkString(*v, value.c_str());
drv.setMeta(name, v);
}
@@ -1112,9 +1113,9 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
if (!v)
printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j);
else {
if (v->type == tString) {
if (v->isString()) {
attrs2["type"] = "string";
attrs2["value"] = v->string.s;
attrs2["value"] = v->getString();
xml.writeEmptyElement("meta", attrs2);
} else if (v->type == tInt) {
attrs2["type"] = "int";
@@ -1132,9 +1133,9 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
attrs2["type"] = "strings";
XMLOpenElement m(xml, "meta", attrs2);
for (unsigned int j = 0; j < v->listSize(); ++j) {
if (v->listElems()[j]->type != tString) continue;
if (!v->listElems()[j]->isString()) continue;
XMLAttrs attrs3;
attrs3["value"] = v->listElems()[j]->string.s;
attrs3["value"] = v->listElems()[j]->getString();
xml.writeEmptyElement("string", attrs3);
}
} else if (v->type == tAttrs) {
@@ -1143,10 +1144,10 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
Bindings & attrs = *v->attrs;
for (auto &i : attrs) {
Attr & a(*attrs.find(i.name));
if(a.value->type != tString) continue;
if (!a.value->isString()) continue;
XMLAttrs attrs3;
attrs3["type"] = i.name;
attrs3["value"] = a.value->string.s;
attrs3["value"] = a.value->getString();
xml.writeEmptyElement("string", attrs3);
}
}

View File

@@ -17,9 +17,9 @@ DrvInfos queryInstalled(EvalState & state, const Path & userEnv)
DrvInfos elems;
Path manifestFile = userEnv + "/manifest.nix";
if (pathExists(manifestFile)) {
Value v;
Root<Value> v;
state.evalFile(manifestFile, v);
Bindings & bindings(*state.allocBindings(0));
auto bindings = Bindings::allocBindings(0);
getDerivations(state, v, "", bindings, elems, false);
}
return elems;
@@ -42,7 +42,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
/* Construct the whole top level derivation. */
StorePathSet references;
Value manifest;
auto manifest = state.allocValue();
state.mkList(manifest, elems.size());
unsigned int n = 0;
for (auto & i : elems) {
@@ -51,8 +51,8 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
as the meta attributes. */
Path drvPath = keepDerivations ? i.queryDrvPath() : "";
Value & v(*state.allocValue());
manifest.listElems()[n++] = &v;
auto v = state.allocValue();
manifest->listElems()[n++] = (Value *) v;
state.mkAttrs(v, 16);
mkString(*state.allocAttr(v, state.sType), "derivation");
@@ -70,7 +70,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
state.mkList(vOutputs, outputs.size());
unsigned int m = 0;
for (auto & j : outputs) {
mkString(*(vOutputs.listElems()[m++] = state.allocValue()), j.first);
mkString(*(vOutputs.listElems()[m++] = (Value *) state.allocValue()), j.first);
Value & vOutputs = *state.allocAttr(v, state.symbols.create(j.first));
state.mkAttrs(vOutputs, 2);
mkString(*state.allocAttr(vOutputs, state.sOutPath), j.second);
@@ -93,7 +93,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
vMeta.attrs->push_back(Attr(state.symbols.create(j), v));
}
vMeta.attrs->sort();
v.attrs->sort();
v->attrs->sort();
if (drvPath != "") references.insert(state.store->parseStorePath(drvPath));
}
@@ -102,29 +102,30 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
the store; we need it for future modifications of the
environment. */
auto manifestFile = state.store->addTextToStore("env-manifest.nix",
fmt("%s", manifest), references);
fmt("%1%", (Value &) manifest), references);
/* Get the environment builder expression. */
Value envBuilder;
auto envBuilder = state.allocValue();
state.evalFile(state.findFile("nix/buildenv.nix"), envBuilder);
/* Construct a Nix expression that calls the user environment
builder with the manifest as argument. */
Value args, topLevel;
auto args = state.allocValue();
Root<Value> topLevel;
state.mkAttrs(args, 3);
mkString(*state.allocAttr(args, state.symbols.create("manifest")),
state.store->printStorePath(manifestFile), {state.store->printStorePath(manifestFile)});
args.attrs->push_back(Attr(state.symbols.create("derivations"), &manifest));
args.attrs->sort();
args->attrs->push_back(Attr(state.symbols.create("derivations"), (Value *) manifest));
args->attrs->sort();
mkApp(topLevel, envBuilder, args);
/* Evaluate it. */
debug("evaluating user environment builder");
state.forceValue(topLevel);
PathSet context;
Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath));
Attr & aDrvPath(*topLevel->attrs->find(state.sDrvPath));
auto topLevelDrv = state.store->parseStorePath(state.coerceToPath(aDrvPath.pos ? *(aDrvPath.pos) : noPos, *(aDrvPath.value), context));
Attr & aOutPath(*topLevel.attrs->find(state.sOutPath));
Attr & aOutPath(*topLevel->attrs->find(state.sOutPath));
Path topLevelOut = state.coerceToPath(aOutPath.pos ? *(aOutPath.pos) : noPos, *(aOutPath.value), context);
/* Realise the resulting store expression. */

View File

@@ -35,18 +35,18 @@ void processExpr(EvalState & state, const Strings & attrPaths,
return;
}
Value vRoot;
Root<Value> vRoot;
state.eval(e, vRoot);
for (auto & i : attrPaths) {
Value & v(*findAlongAttrPath(state, i, autoArgs, vRoot));
auto v = findAlongAttrPath(state, i, autoArgs, vRoot);
state.forceValue(v);
PathSet context;
if (evalOnly) {
Value vRes;
Root<Value> vRes;
if (autoArgs.empty())
vRes = v;
vRes = *v;
else
state.autoCallFunction(autoArgs, v, vRes);
if (output == okXML)
@@ -55,7 +55,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
printValueAsJSON(state, strict, vRes, std::cout, context);
else {
if (strict) state.forceValueDeep(vRes);
std::cout << vRes << std::endl;
std::cout << *vRes << std::endl;
}
} else {
DrvInfos drvs;
@@ -159,7 +159,7 @@ static int _main(int argc, char * * argv)
auto state = std::make_unique<EvalState>(myArgs.searchPath, store);
state->repair = repair;
Bindings & autoArgs = *myArgs.getAutoArgs(*state);
auto autoArgs = myArgs.getAutoArgs(*state);
if (attrPaths.empty()) attrPaths = {""};
@@ -174,7 +174,7 @@ static int _main(int argc, char * * argv)
if (readStdin) {
Expr * e = state->parseStdin();
processExpr(*state, attrPaths, parseOnly, strict, autoArgs,
processExpr(*state, attrPaths, parseOnly, strict, *autoArgs,
evalOnly, outputKind, xmlOutputSourceLocation, e);
} else if (files.empty() && !fromArgs)
files.push_back("./default.nix");
@@ -183,7 +183,7 @@ static int _main(int argc, char * * argv)
Expr * e = fromArgs
? state->parseExprFromString(i, absPath("."))
: state->parseExprFromFile(resolveExprPath(state->checkSourcePath(lookupFileArg(*state, i))));
processExpr(*state, attrPaths, parseOnly, strict, autoArgs,
processExpr(*state, attrPaths, parseOnly, strict, *autoArgs,
evalOnly, outputKind, xmlOutputSourceLocation, e);
}

View File

@@ -31,12 +31,12 @@ string resolveMirrorUri(EvalState & state, string uri)
if (p == string::npos) throw Error("invalid mirror URI");
string mirrorName(s, 0, p);
Value vMirrors;
Root<Value> vMirrors;
state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors);
state.forceAttrs(vMirrors);
auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
if (mirrorList == vMirrors.attrs->end())
auto mirrorList = vMirrors->attrs->find(state.symbols.create(mirrorName));
if (mirrorList == vMirrors->attrs->end())
throw Error(format("unknown mirror name '%1%'") % mirrorName);
state.forceList(*mirrorList->value);
@@ -107,7 +107,7 @@ static int _main(int argc, char * * argv)
auto store = openStore();
auto state = std::make_unique<EvalState>(myArgs.searchPath, store);
Bindings & autoArgs = *myArgs.getAutoArgs(*state);
auto autoArgs = myArgs.getAutoArgs(*state);
/* If -A is given, get the URI from the specified Nix
expression. */
@@ -118,14 +118,14 @@ static int _main(int argc, char * * argv)
uri = args[0];
} else {
Path path = resolveExprPath(lookupFileArg(*state, args.empty() ? "." : args[0]));
Value vRoot;
Root<Value> vRoot;
state->evalFile(path, vRoot);
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot));
auto v = findAlongAttrPath(*state, attrPath, *autoArgs, vRoot);
state->forceAttrs(v);
/* Extract the URI. */
auto attr = v.attrs->find(state->symbols.create("urls"));
if (attr == v.attrs->end())
auto attr = v->attrs->find(state->symbols.create("urls"));
if (attr == v->attrs->end())
throw Error("attribute set does not contain a 'urls' attribute");
state->forceList(*attr->value);
if (attr->value->listSize() < 1)
@@ -133,16 +133,16 @@ static int _main(int argc, char * * argv)
uri = state->forceString(*attr->value->listElems()[0]);
/* Extract the hash mode. */
attr = v.attrs->find(state->symbols.create("outputHashMode"));
if (attr == v.attrs->end())
attr = v->attrs->find(state->symbols.create("outputHashMode"));
if (attr == v->attrs->end())
printInfo("warning: this does not look like a fetchurl call");
else
unpack = state->forceString(*attr->value) == "recursive";
/* Extract the name. */
if (name.empty()) {
attr = v.attrs->find(state->symbols.create("name"));
if (attr != v.attrs->end())
attr = v->attrs->find(state->symbols.create("name"));
if (attr != v->attrs->end())
name = state->forceString(*attr->value);
}
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "args.hh"
#include "gc.hh"
#include "common-eval-args.hh"
#include "path.hh"
@@ -48,7 +49,7 @@ struct Installable
Buildable toBuildable();
virtual Value * toValue(EvalState & state)
virtual Ptr<Value> toValue(EvalState & state)
{
throw Error("argument '%s' cannot be evaluated", what());
}
@@ -56,6 +57,8 @@ struct Installable
struct SourceExprCommand : virtual Args, StoreCommand, MixEvalArgs
{
friend void mainWrapped(int argc, char * * argv);
Path file;
SourceExprCommand();
@@ -64,7 +67,7 @@ struct SourceExprCommand : virtual Args, StoreCommand, MixEvalArgs
are installing. This is either the file specified by --file,
or an attribute set constructed from $NIX_PATH, e.g. { nixpkgs
= import ...; bla = import ...; }. */
Value * getSourceExpr(EvalState & state);
Ptr<Value> getSourceExpr(EvalState & state);
ref<EvalState> getEvalState();
@@ -72,7 +75,7 @@ private:
std::shared_ptr<EvalState> evalState;
Value * vSourceExpr = 0;
Ptr<Value> vSourceExpr;
};
enum RealiseMode { Build, NoBuild, DryRun };

332
src/nix/eval-hydra-jobs.cc Normal file
View File

@@ -0,0 +1,332 @@
#include "command.hh"
#include "eval.hh"
#include "eval-inline.hh"
#include "derivations.hh"
#include "common-args.hh"
#include "json.hh"
#include "get-drvs.hh"
#include "attr-path.hh"
#include <nlohmann/json.hpp>
#include <sys/resource.h>
using namespace nix;
static std::string queryMetaStrings(EvalState & state, DrvInfo & drv, const string & name, const string & subAttribute)
{
Strings res;
std::function<void(Value & v)> rec;
rec = [&](Value & v) {
state.forceValue(v);
if (v.type == tString)
res.push_back(v.getString());
else if (v.isList())
for (unsigned int n = 0; n < v.listSize(); ++n)
rec(*v.listElems()[n]);
else if (v.type == tAttrs) {
auto a = v.attrs->find(state.symbols.create(subAttribute));
if (a != v.attrs->end())
res.push_back(state.forceString(*a->value));
}
};
Value * v = drv.queryMeta(name);
if (v) rec(*v);
return concatStringsSep(", ", res);
}
struct CmdEvalHydraJobs : MixJSON, MixDryRun, InstallableCommand
{
std::optional<Path> gcRootsDir;
size_t nrWorkers = 1;
size_t maxMemorySize = 4ULL * 1024;
CmdEvalHydraJobs()
{
mkFlag()
.longName("gc-roots-dir")
.description("garbage collector roots directory")
.labels({"path"})
.dest(&gcRootsDir);
mkIntFlag(0, "workers", "number of concurrent worker processes", &nrWorkers);
mkIntFlag(0, "max-memory-size", "maximum memory usage per worker process (in MiB)", &maxMemorySize);
}
std::string description() override
{
return "evaluate a Hydra jobset";
}
Examples examples() override
{
return {
Example{
"Evaluate Nixpkgs' release-combined jobset:",
"nix eval-hydra-jobs -f '<nixpkgs/nixos/release-combined.nix>' '' --json"
},
};
}
void worker(AutoCloseFD & to, AutoCloseFD & from)
{
auto state = getEvalState();
// FIXME: should re-open state->store.
if (dryRun) settings.readOnlyMode = true;
/* Prevent access to paths outside of the Nix search path and
to the environment. */
evalSettings.restrictEval = true;
auto vTop = installable->toValue(*state);
auto vRoot = state->allocValue();
state->autoCallFunction(getAutoArgs(*state), vTop, vRoot);
while (true) {
/* Wait for the master to send us a job name. */
writeLine(to.get(), "next");
auto s = readLine(from.get());
if (s == "exit") break;
if (!hasPrefix(s, "do ")) abort();
std::string attrPath(s, 3);
debug("worker process %d at '%s'", getpid(), attrPath);
/* Evaluate it and send info back to the master. */
nlohmann::json reply;
try {
auto v = findAlongAttrPath(*state, attrPath, getAutoArgs(*state), vRoot);
state->forceValue(v);
if (auto drv = getDerivation(*state, v, false)) {
DrvInfo::Outputs outputs = drv->queryOutputs();
if (drv->querySystem() == "unknown")
throw EvalError("derivation must have a 'system' attribute");
auto drvPath = drv->queryDrvPath();
nlohmann::json job;
job["nixName"] = drv->queryName();
job["system"] =drv->querySystem();
job["drvPath"] = drvPath;
job["description"] = drv->queryMetaString("description");
job["license"] = queryMetaStrings(*state, *drv, "license", "shortName");
job["homepage"] = drv->queryMetaString("homepage");
job["maintainers"] = queryMetaStrings(*state, *drv, "maintainers", "email");
job["schedulingPriority"] = drv->queryMetaInt("schedulingPriority", 100);
job["timeout"] = drv->queryMetaInt("timeout", 36000);
job["maxSilent"] = drv->queryMetaInt("maxSilent", 7200);
job["isChannel"] = drv->queryMetaBool("isHydraChannel", false);
/* If this is an aggregate, then get its constituents. */
auto a = v->attrs->get(state->symbols.create("_hydraAggregate"));
if (a && state->forceBool(*a->value, *a->pos)) {
auto a = v->attrs->get(state->symbols.create("constituents"));
if (!a)
throw EvalError("derivation must have a constituents attribute");
PathSet context;
state->coerceToString(*a->pos, *a->value, context, true, false);
PathSet drvs;
for (auto & i : context)
if (i.at(0) == '!') {
size_t index = i.find("!", 1);
drvs.insert(string(i, index + 1));
}
job["constituents"] = concatStringsSep(" ", drvs);
}
/* Register the derivation as a GC root. !!! This
registers roots for jobs that we may have already
done. */
auto localStore = state->store.dynamic_pointer_cast<LocalFSStore>();
if (gcRootsDir && localStore) {
Path root = *gcRootsDir + "/" + std::string(baseNameOf(drvPath));
if (!pathExists(root))
localStore->addPermRoot(localStore->parseStorePath(drvPath), root, false);
}
nlohmann::json out;
for (auto & j : outputs)
out[j.first] = j.second;
job["outputs"] = std::move(out);
reply["job"] = std::move(job);
}
else if (v->type == tAttrs) {
auto attrs = nlohmann::json::array();
StringSet ss;
for (auto & i : v->attrs->lexicographicOrder()) {
std::string name(i->name);
if (name.find('.') != std::string::npos || name.find(' ') != std::string::npos) {
printError("skipping job with illegal name '%s'", name);
continue;
}
attrs.push_back(name);
}
reply["attrs"] = std::move(attrs);
}
} catch (EvalError & e) {
reply["error"] = filterANSIEscapes(e.msg(), true);
}
writeLine(to.get(), reply.dump());
/* If our RSS exceeds the maximum, exit. The master will
start a new process. */
struct rusage r;
getrusage(RUSAGE_SELF, &r);
if ((size_t) r.ru_maxrss > maxMemorySize * 1024) break;
}
writeLine(to.get(), "restart");
}
void run(ref<Store> store) override
{
if (!gcRootsDir) warn("'--gc-roots-dir' not specified");
struct State
{
std::set<std::string> todo{""};
std::set<std::string> active;
nlohmann::json result;
};
std::condition_variable wakeup;
Sync<State> state_;
/* Start a handler thread per worker process. */
auto handler = [this, &state_, &wakeup]()
{
try {
pid_t pid = -1;
AutoCloseFD from, to;
while (true) {
/* Start a new worker process if necessary. */
if (pid == -1) {
Pipe toPipe, fromPipe;
toPipe.create();
fromPipe.create();
pid = startProcess(
[this,
to{std::make_shared<AutoCloseFD>(std::move(fromPipe.writeSide))},
from{std::make_shared<AutoCloseFD>(std::move(toPipe.readSide))}
]()
{
try {
worker(*to, *from);
} catch (Error & e) {
printError("unexpected worker error: %s", e.msg());
_exit(1);
}
},
ProcessOptions { .allowVfork = false });
from = std::move(fromPipe.readSide);
to = std::move(toPipe.writeSide);
debug("created worker process %d", pid);
}
/* Check whether the existing worker process is still there. */
auto s = readLine(from.get());
if (s == "restart") {
pid = -1;
continue;
} else if (s != "next")
throw Error("unexpected worker request: %s", s);
/* Wait for a job name to become available. */
std::string attrPath;
while (true) {
checkInterrupt();
auto state(state_.lock());
if (state->todo.empty() && state->active.empty()) {
writeLine(to.get(), "exit");
return;
}
if (!state->todo.empty()) {
attrPath = *state->todo.begin();
state->todo.erase(state->todo.begin());
state->active.insert(attrPath);
break;
} else
state.wait(wakeup);
}
/* Tell the worker to evaluate it. */
writeLine(to.get(), "do " + attrPath);
/* Wait for the response. */
auto response = nlohmann::json::parse(readLine(from.get()));
/* Handle the response. */
StringSet newAttrs;
if (response.find("job") != response.end()) {
auto state(state_.lock());
if (json)
state->result[attrPath] = response["job"];
else
std::cout << fmt("%d: %d\n", attrPath, (std::string) response["job"]["drvPath"]);
}
if (response.find("attrs") != response.end()) {
for (auto & i : response["attrs"]) {
auto s = (attrPath.empty() ? "" : attrPath + ".") + (std::string) i;
newAttrs.insert(s);
}
}
if (response.find("error") != response.end()) {
auto state(state_.lock());
if (json)
state->result[attrPath]["error"] = response["error"];
else
printError("error in job '%s': %s",
attrPath, (std::string) response["error"]);
}
/* Add newly discovered job names to the queue. */
{
auto state(state_.lock());
state->active.erase(attrPath);
for (auto & s : newAttrs)
state->todo.insert(s);
wakeup.notify_all();
}
}
} catch (Error & e) {
printError("unexpected handler thread error: %s", e.msg());
abort();
}
};
std::vector<std::thread> threads;
for (size_t i = 0; i < nrWorkers; i++)
threads.emplace_back(std::thread(handler));
for (auto & thread : threads)
thread.join();
if (json) std::cout << state_.lock()->result.dump(2) << "\n";
}
};
static auto r1 = registerCommand<CmdEvalHydraJobs>("eval-hydra-jobs");

View File

@@ -22,7 +22,7 @@ SourceExprCommand::SourceExprCommand()
.dest(&file);
}
Value * SourceExprCommand::getSourceExpr(EvalState & state)
Ptr<Value> SourceExprCommand::getSourceExpr(EvalState & state)
{
if (vSourceExpr) return vSourceExpr;
@@ -48,10 +48,10 @@ Value * SourceExprCommand::getSourceExpr(EvalState & state)
auto addEntry = [&](const std::string & name) {
if (name == "") return;
if (!seen.insert(name).second) return;
Value * v1 = state.allocValue();
auto v1 = state.allocValue();
mkPrimOpApp(*v1, state.getBuiltin("findFile"), state.getBuiltin("nixPath"));
Value * v2 = state.allocValue();
mkApp(*v2, *v1, mkString(*state.allocValue(), name));
auto v2 = state.allocValue();
mkApp(*v2, *v1, mkString(state.allocValue(), name));
mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(name)),
state.getBuiltin("import"), *v2);
};
@@ -169,7 +169,7 @@ struct InstallableExpr : InstallableValue
std::string what() override { return text; }
Value * toValue(EvalState & state) override
Ptr<Value> toValue(EvalState & state) override
{
auto v = state.allocValue();
state.eval(state.parseExprFromString(text, absPath(".")), *v);
@@ -187,13 +187,13 @@ struct InstallableAttrPath : InstallableValue
std::string what() override { return attrPath; }
Value * toValue(EvalState & state) override
Ptr<Value> toValue(EvalState & state) override
{
auto source = cmd.getSourceExpr(state);
Bindings & autoArgs = *cmd.getAutoArgs(state);
auto autoArgs = cmd.getAutoArgs(state);
Value * v = findAlongAttrPath(state, attrPath, autoArgs, *source);
auto v = findAlongAttrPath(state, attrPath, *autoArgs, *source);
state.forceValue(*v);
return v;

136
src/nix/list-tarballs.cc Normal file
View File

@@ -0,0 +1,136 @@
#include "command.hh"
#include "eval.hh"
#include "eval-inline.hh"
#include "derivations.hh"
#include "common-args.hh"
#include "json.hh"
using namespace nix;
struct CmdListTarballs : MixJSON, InstallablesCommand
{
std::string description() override
{
return "list the 'fetchurl' calls made by the dependency graph of a package";
}
Examples examples() override
{
return {
Example{
"To get the tarballs required to build GNU Hello and its dependencies:",
"nix list-tarballs nixpkgs.hello"
},
};
}
struct File
{
std::string type;
bool recursive;
Hash hash;
std::string url;
StorePath storePath;
};
void doIt(ref<Store> store, std::function<void(const File &)> callback)
{
settings.readOnlyMode = true;
auto state = getEvalState();
auto autoArgs = getAutoArgs(*state);
StorePathSet done;
state->derivationHook =
[&](const StorePath & drvPath, const Derivation & drv) {
if (drv.outputs.size() != 1) return;
auto & output = *drv.outputs.begin();
if (output.second.hashAlgo.empty() || output.second.hash.empty()) return;
if (!done.insert(output.second.path.clone()).second) return;
auto [recursive, hash] = output.second.parseHashInfo();
if (recursive) return; // FIXME
std::optional<std::string> url;
auto i = drv.env.find("url");
if (i != drv.env.end())
url = i->second;
else {
i = drv.env.find("urls");
if (i == drv.env.end()) return;
auto urls = tokenizeString<std::vector<std::string>>(i->second, " ");
if (urls.empty()) return;
url = urls[0];
}
File file {
.type = drv.builder == "builtin:fetchurl" ? "fetchurl" : "unknown",
.recursive = recursive,
.hash = hash,
.url = *url,
.storePath = output.second.path.clone()
};
callback(file);
};
std::function<void(Value * v)> findDerivations;
findDerivations =
[&](Value * v) {
state->forceValue(*v);
if (v->type == tAttrs) {
if (state->isDerivation(*v)) {
auto aDrvPath = v->attrs->get(state->sDrvPath);
if (!aDrvPath) return;
try {
state->forceValue(*aDrvPath->value, *aDrvPath->pos);
} catch (EvalError & e) {
}
} else {
std::unordered_set<Value *> vs;
for (auto & attr : *v->attrs) {
if (!vs.insert(attr.value).second) continue;
findDerivations(attr.value);
}
}
}
};
for (auto & installable : installables) {
auto v = state->allocValue();
state->autoCallFunction(autoArgs, installable->toValue(*state), v);
findDerivations(v);
}
}
void run(ref<Store> store) override
{
if (json) {
JSONList json(std::cout);
doIt(store,
[&](const File & file) {
auto obj = json.object();
obj.attr("type", file.type);
if (file.recursive)
obj.attr("recursive", true);
obj.attr("hash", file.hash.to_string(SRI));
obj.attr("url", file.url);
obj.attr("storePath", store->printStorePath(file.storePath));
});
} else {
doIt(store,
[&](const File & file) {
std::cout << file.url << "\n";
});
}
}
};
static auto r1 = registerCommand<CmdListTarballs>("list-tarballs");

View File

@@ -132,7 +132,6 @@ void mainWrapped(int argc, char * * argv)
}
initNix();
initGC();
programPath = argv[0];
auto programName = std::string(baseNameOf(programPath));
@@ -178,6 +177,11 @@ void mainWrapped(int argc, char * * argv)
args.command->prepare();
args.command->run();
auto c = dynamic_cast<SourceExprCommand *>(&*args.command);
if (c && c->evalState) {
c->evalState->printStats();
}
}
}

View File

@@ -46,13 +46,13 @@ struct NixRepl
{
string curDir;
EvalState state;
Bindings * autoArgs;
Ptr<Bindings> autoArgs;
Strings loadedFiles;
const static int envSize = 32768;
StaticEnv staticEnv;
Env * env;
Ptr<Env> env;
int displ;
StringSet varNames;
@@ -352,11 +352,11 @@ StringSet NixRepl::completePrefix(string prefix)
string cur2 = string(cur, dot + 1);
Expr * e = parseString(expr);
Value v;
Root<Value> v;
e->eval(state, *env, v);
state.forceAttrs(v);
for (auto & i : *v.attrs) {
for (auto & i : *v->attrs) {
string name = i.name;
if (string(name, 0, cur2.size()) != cur2) continue;
completions.insert(prev + expr + "." + name);
@@ -453,7 +453,7 @@ bool NixRepl::processLine(string line)
}
else if (command == ":a" || command == ":add") {
Value v;
Root<Value> v;
evalString(arg, v);
addAttrsToScope(v);
}
@@ -469,17 +469,17 @@ bool NixRepl::processLine(string line)
}
else if (command == ":e" || command == ":edit") {
Value v;
Root<Value> v;
evalString(arg, v);
Pos pos;
if (v.type == tPath || v.type == tString) {
if (v->type == tPath || v->isString()) {
PathSet context;
auto filename = state.coerceToString(noPos, v, context);
pos.file = state.symbols.create(filename);
} else if (v.type == tLambda) {
pos = v.lambda.fun->pos;
} else if (v->type == tLambda) {
pos = v->lambda.fun->pos;
} else {
// assume it's a derivation
pos = findDerivationFilename(state, v, arg);
@@ -497,12 +497,12 @@ bool NixRepl::processLine(string line)
}
else if (command == ":t") {
Value v;
Root<Value> v;
evalString(arg, v);
std::cout << showType(v) << std::endl;
} else if (command == ":u") {
Value v, f, result;
Root<Value> v, f, result;
evalString(arg, v);
evalString("drv: (import <nixpkgs> {}).runCommand \"shell\" { buildInputs = [ drv ]; } \"\"", f);
state.callFunction(f, v, result, Pos());
@@ -512,7 +512,7 @@ bool NixRepl::processLine(string line)
}
else if (command == ":b" || command == ":i" || command == ":s") {
Value v;
Root<Value> v;
evalString(arg, v);
Path drvPath = getDerivationPath(v);
@@ -534,7 +534,7 @@ bool NixRepl::processLine(string line)
}
else if (command == ":p" || command == ":print") {
Value v;
Root<Value> v;
evalString(arg, v);
printValue(std::cout, v, 1000000000) << std::endl;
}
@@ -560,7 +560,7 @@ bool NixRepl::processLine(string line)
v.thunk.expr = e;
addVarToScope(state.symbols.create(name), v);
} else {
Value v;
Root<Value> v;
evalString(line, v);
printValue(std::cout, v, 1) << std::endl;
}
@@ -574,17 +574,17 @@ void NixRepl::loadFile(const Path & path)
{
loadedFiles.remove(path);
loadedFiles.push_back(path);
Value v, v2;
Root<Value> v, v2;
state.evalFile(lookupFileArg(state, path), v);
state.autoCallFunction(*autoArgs, v, v2);
state.autoCallFunction(autoArgs, v, v2);
addAttrsToScope(v2);
}
void NixRepl::initEnv()
{
env = &state.allocEnv(envSize);
env->up = &state.baseEnv;
env = state.allocEnv(envSize);
env->up = state.baseEnv;
displ = 0;
staticEnv.vars.clear();
@@ -683,9 +683,11 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
str << ESC_CYA << (v.boolean ? "true" : "false") << ESC_END;
break;
case tString:
case tShortString:
case tStaticString:
case tLongString:
str << ESC_YEL;
printStringValue(str, v.string.s);
printStringValue(str, v.getString());
str << ESC_END;
break;
@@ -742,6 +744,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
break;
}
case tList0:
case tList1:
case tList2:
case tListN:

View File

@@ -120,7 +120,7 @@ struct CmdSearch : SourceExprCommand, MixJSON
if (v->type == tLambda && toplevel) {
Value * v2 = state->allocValue();
state->autoCallFunction(*state->allocBindings(1), *v, *v2);
state->autoCallFunction(*Bindings::allocBindings(1), *v, *v2);
v = v2;
state->forceValue(*v);
}
@@ -226,12 +226,12 @@ struct CmdSearch : SourceExprCommand, MixJSON
warn("using cached results; pass '-u' to update the cache");
Value vRoot;
Root<Value> vRoot;
parseJSON(*state, readFile(jsonCacheFileName), vRoot);
fromCache = true;
doExpr(&vRoot, "", true, nullptr);
doExpr(&*vRoot, "", true, nullptr);
}
else {

View File

@@ -144,7 +144,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
auto state = std::make_unique<EvalState>(Strings(), store);
auto v = state->allocValue();
state->eval(state->parseExprFromString(*res.data, "/no-such-path"), *v);
Bindings & bindings(*state->allocBindings(0));
Bindings & bindings(*Bindings::allocBindings(0));
auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v);
return store->parseStorePath(state->forceString(*v2));

View File

@@ -36,6 +36,9 @@ export HAVE_SODIUM="@HAVE_SODIUM@"
export version=@PACKAGE_VERSION@
export system=@system@
# Make it more likely to trigger GC bugs.
export GC_INITIAL_HEAP_SIZE=10
cacheDir=$TEST_ROOT/binary-cache
readLink() {