libexpr: make thunk state shareable
this is a strong prerequisite for making values themselves copyable without duplicating evaluation side effects. with this we can treat `Value` the way we treated `Value *` to date and drop indirections. Change-Id: I08f30d12697614a3ae7149615f6f1da83b13f52b
This commit is contained in:
+27
-16
@@ -13,7 +13,7 @@ namespace nix {
|
||||
inline Value::Value(app_t, EvalMemory & mem, Value & lhs, Value & rhs)
|
||||
{
|
||||
auto app = static_cast<Value::App *>(mem.allocBytes(sizeof(Value::App) + sizeof(Value *)));
|
||||
app->_left = &lhs;
|
||||
app->_left = reinterpret_cast<uintptr_t>(&lhs);
|
||||
app->_n = 1;
|
||||
app->_args[0] = &rhs;
|
||||
raw = tag(tApp, app);
|
||||
@@ -22,7 +22,7 @@ inline Value::Value(app_t, EvalMemory & mem, Value & lhs, Value & rhs)
|
||||
inline Value::Value(app_t, EvalMemory & mem, Value & lhs, std::span<Value *> args)
|
||||
{
|
||||
auto app = static_cast<Value::App *>(mem.allocBytes(sizeof(Value::App) + args.size_bytes()));
|
||||
app->_left = &lhs;
|
||||
app->_left = reinterpret_cast<uintptr_t>(&lhs);
|
||||
app->_n = args.size();
|
||||
memcpy(app->_args, args.data(), args.size_bytes());
|
||||
raw = tag(tApp, app);
|
||||
@@ -31,7 +31,7 @@ inline Value::Value(app_t, EvalMemory & mem, Value & lhs, std::span<Value *> arg
|
||||
inline Value::Value(thunk_t, EvalMemory & mem, Env & env, Expr & expr)
|
||||
{
|
||||
auto thunk = mem.allocType<Thunk>();
|
||||
*thunk = {.env = &env, .expr = &expr};
|
||||
*thunk = {._env = &env, .expr = &expr};
|
||||
raw = tag(tThunk, thunk);
|
||||
}
|
||||
|
||||
@@ -113,22 +113,33 @@ Env & EvalMemory::allocEnv(size_t size)
|
||||
void EvalState::forceValue(Value & v, const PosIdx pos)
|
||||
{
|
||||
if (v.isThunk()) {
|
||||
const auto backup = v;
|
||||
Env * env = v.thunk().env;
|
||||
Expr & expr = *v.thunk().expr;
|
||||
v = Value{NewValueAs::blackhole};
|
||||
try {
|
||||
expr.eval(*this, *env, v);
|
||||
} catch (...) {
|
||||
v = backup;
|
||||
tryFixupBlackHolePos(v, pos);
|
||||
throw;
|
||||
auto & thunk = v.thunk();
|
||||
if (thunk.resolved()) {
|
||||
v = thunk.result();
|
||||
} else {
|
||||
const auto backup = v;
|
||||
Env * env = v.thunk().env();
|
||||
Expr & expr = *v.thunk().expr;
|
||||
v = Value{NewValueAs::blackhole};
|
||||
try {
|
||||
expr.eval(*this, *env, v);
|
||||
backup.thunk().resolve(v);
|
||||
} catch (...) {
|
||||
v = backup;
|
||||
tryFixupBlackHolePos(v, pos);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
} else if (v.isApp()) {
|
||||
auto & app = v.app();
|
||||
auto target = app.target();
|
||||
if (!target->isPrimOp() || target->primOp()->arity <= app.totalArgs()) {
|
||||
callFunction(*v.app().left(), v.app().args(), v, pos);
|
||||
if (app.resolved()) {
|
||||
v = app.result();
|
||||
} else {
|
||||
auto target = app.target();
|
||||
if (!target->isPrimOp() || target->primOp()->arity <= app.totalArgs()) {
|
||||
callFunction(*v.app().left(), v.app().args(), v, pos);
|
||||
app.resolve(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -2154,8 +2154,14 @@ void EvalState::forceValueDeep(Value & v)
|
||||
try {
|
||||
// If the value is a thunk, we're evaling. Otherwise no trace necessary.
|
||||
auto dts = ctx.debug && i.value->isThunk()
|
||||
? makeDebugTraceStacker(*this, *i.value->thunk().expr, *i.value->thunk().env, ctx.positions[i.pos],
|
||||
"while evaluating the attribute '%1%'", ctx.symbols[i.name])
|
||||
? makeDebugTraceStacker(
|
||||
*this,
|
||||
*i.value->thunk().expr,
|
||||
*i.value->thunk().env(),
|
||||
ctx.positions[i.pos],
|
||||
"while evaluating the attribute '%1%'",
|
||||
ctx.symbols[i.name]
|
||||
)
|
||||
: nullptr;
|
||||
|
||||
recurse(*i.value);
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace nix {
|
||||
ExprBlackHole eBlackHole;
|
||||
|
||||
static Env nullEnv;
|
||||
Value::Thunk Value::blackHole{&nullEnv, &eBlackHole};
|
||||
Value::Thunk Value::blackHole{{&nullEnv}, &eBlackHole};
|
||||
|
||||
// FIXME: remove, because *symbols* are abstract and do not have a single
|
||||
// textual representation; see printIdentifier()
|
||||
|
||||
+110
-54
@@ -245,7 +245,7 @@ struct NewValueAs
|
||||
struct Value
|
||||
{
|
||||
private:
|
||||
uintptr_t raw;
|
||||
mutable uintptr_t raw;
|
||||
|
||||
public:
|
||||
static constexpr size_t TAG_BITS = 3;
|
||||
@@ -613,7 +613,7 @@ public:
|
||||
}
|
||||
inline bool isPrimOpApp() const
|
||||
{
|
||||
return internalType() == tApp && app().target()->isPrimOp();
|
||||
return internalType() == tApp && !app().resolved() && app().target()->isPrimOp();
|
||||
}
|
||||
|
||||
struct alignas(TAG_ALIGN) List
|
||||
@@ -665,13 +665,31 @@ public:
|
||||
|
||||
struct alignas(TAG_ALIGN) App
|
||||
{
|
||||
Value * _left;
|
||||
uintptr_t _left;
|
||||
size_t _n;
|
||||
Value * _args[0];
|
||||
|
||||
bool resolved() const
|
||||
{
|
||||
return _n == 0;
|
||||
}
|
||||
|
||||
void resolve(Value v)
|
||||
{
|
||||
_left = v.raw;
|
||||
_n = 0;
|
||||
}
|
||||
|
||||
Value * left() const
|
||||
{
|
||||
return _left;
|
||||
return reinterpret_cast<Value *>(_left);
|
||||
}
|
||||
|
||||
Value result() const
|
||||
{
|
||||
Value v;
|
||||
v.raw = _left;
|
||||
return v;
|
||||
}
|
||||
|
||||
Value * target() const
|
||||
@@ -727,11 +745,6 @@ public:
|
||||
Env * env;
|
||||
ExprLambda * fun;
|
||||
};
|
||||
struct alignas(TAG_ALIGN) Thunk
|
||||
{
|
||||
Env * env;
|
||||
Expr * expr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the normal type of a Value. This only returns nThunk if
|
||||
@@ -740,43 +753,7 @@ public:
|
||||
* @param invalidIsThunk Instead of aborting an an invalid (probably
|
||||
* 0, so uninitialized) internal type, return `nThunk`.
|
||||
*/
|
||||
inline ValueType type(bool invalidIsThunk = false) const
|
||||
{
|
||||
switch (internalType()) {
|
||||
case tInt:
|
||||
return nInt;
|
||||
case tBool:
|
||||
return nBool;
|
||||
case tString:
|
||||
return untag<const String *>()->isPath() ? nPath : nString;
|
||||
case tAttrs:
|
||||
return nAttrs;
|
||||
case tList:
|
||||
return nList;
|
||||
case tAuxiliary:
|
||||
switch (untag<const Acb *>()->type) {
|
||||
case Acb::tExternal:
|
||||
return nExternal;
|
||||
case Acb::tFloat:
|
||||
return nFloat;
|
||||
case Acb::tNull:
|
||||
return nNull;
|
||||
case Acb::tPrimOp:
|
||||
case Acb::tLambda:
|
||||
return nFunction;
|
||||
case Acb::tInt:
|
||||
return nInt;
|
||||
}
|
||||
case tThunk:
|
||||
return nThunk;
|
||||
case tApp:
|
||||
return app().target()->isPrimOp() ? nFunction : nThunk;
|
||||
}
|
||||
if (invalidIsThunk)
|
||||
return nThunk;
|
||||
else
|
||||
abort();
|
||||
}
|
||||
inline ValueType type(bool invalidIsThunk = false) const;
|
||||
|
||||
inline void mkInt(NixInt::Inner n)
|
||||
{
|
||||
@@ -928,21 +905,16 @@ public:
|
||||
return untag<Bindings *>();
|
||||
}
|
||||
|
||||
const auto & thunk() const
|
||||
Thunk & thunk() const
|
||||
{
|
||||
return *untag<const Thunk *>();
|
||||
return *untag<Thunk *>();
|
||||
}
|
||||
|
||||
App & app()
|
||||
App & app() const
|
||||
{
|
||||
return *untag<App *>();
|
||||
}
|
||||
|
||||
const App & app() const
|
||||
{
|
||||
return *untag<const App *>();
|
||||
}
|
||||
|
||||
const auto & lambda() const
|
||||
{
|
||||
return *untag<const Lambda *>();
|
||||
@@ -972,6 +944,90 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
struct alignas(Value::TAG_ALIGN) Value::Thunk
|
||||
{
|
||||
union {
|
||||
Env * _env;
|
||||
Value _result;
|
||||
};
|
||||
Expr * expr;
|
||||
|
||||
bool resolved() const
|
||||
{
|
||||
return expr == nullptr;
|
||||
}
|
||||
|
||||
void resolve(Value v)
|
||||
{
|
||||
_result = v;
|
||||
expr = nullptr;
|
||||
}
|
||||
|
||||
Env * env() const
|
||||
{
|
||||
return _env;
|
||||
}
|
||||
|
||||
Value result() const
|
||||
{
|
||||
return _result;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the normal type of a Value. This only returns nThunk if
|
||||
* the Value hasn't been forceValue'd
|
||||
*
|
||||
* @param invalidIsThunk Instead of aborting an an invalid (probably
|
||||
* 0, so uninitialized) internal type, return `nThunk`.
|
||||
*/
|
||||
inline ValueType Value::type(bool invalidIsThunk) const
|
||||
{
|
||||
again:
|
||||
switch (internalType()) {
|
||||
case tInt:
|
||||
return nInt;
|
||||
case tBool:
|
||||
return nBool;
|
||||
case tString:
|
||||
return untag<const String *>()->isPath() ? nPath : nString;
|
||||
case tAttrs:
|
||||
return nAttrs;
|
||||
case tList:
|
||||
return nList;
|
||||
case tAuxiliary:
|
||||
switch (untag<const Acb *>()->type) {
|
||||
case Acb::tExternal:
|
||||
return nExternal;
|
||||
case Acb::tFloat:
|
||||
return nFloat;
|
||||
case Acb::tNull:
|
||||
return nNull;
|
||||
case Acb::tPrimOp:
|
||||
case Acb::tLambda:
|
||||
return nFunction;
|
||||
case Acb::tInt:
|
||||
return nInt;
|
||||
}
|
||||
case tThunk:
|
||||
if (thunk().resolved()) {
|
||||
raw = thunk().result().raw;
|
||||
goto again;
|
||||
}
|
||||
return nThunk;
|
||||
case tApp:
|
||||
if (app().resolved()) {
|
||||
raw = app().result().raw;
|
||||
goto again;
|
||||
}
|
||||
return app().target()->isPrimOp() ? nFunction : nThunk;
|
||||
}
|
||||
if (invalidIsThunk)
|
||||
return nThunk;
|
||||
else
|
||||
abort();
|
||||
}
|
||||
|
||||
using ValueVector = GcVector<Value *>;
|
||||
|
||||
using PrimOp = Value::PrimOp;
|
||||
|
||||
Reference in New Issue
Block a user