1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-25 14:51:16 +02:00

encode black holes as tApp values

checking for isBlackhole in the forceValue hot path is rather more
expensive than necessary, and with a little bit of trickery we can move
such handling into the isApp case. small performance benefit, but under
some circumstances we've seen 2% improvement as well.

〉 nix eval --raw --impure --expr 'with import <nixpkgs/nixos> {}; system'

before:

  Time (mean ± σ):      4.429 s ±  0.002 s    [User: 3.929 s, System: 0.500 s]
  Range (min … max):    4.427 s …  4.433 s    10 runs

after:

  Time (mean ± σ):      4.396 s ±  0.002 s    [User: 3.894 s, System: 0.501 s]
  Range (min … max):    4.393 s …  4.399 s    10 runs
This commit is contained in:
pennae 2023-12-10 08:24:45 +01:00
parent 0218e4e6c3
commit 78353deb02
7 changed files with 93 additions and 32 deletions

View file

@ -104,11 +104,16 @@ void EvalState::forceValue(Value & v, Callable getPos)
} }
} }
else if (v.isApp()) { else if (v.isApp()) {
PosIdx pos = getPos(); try {
callFunction(*v.app.left, *v.app.right, v, pos); callFunction(*v.app.left, *v.app.right, v, noPos);
} catch (InfiniteRecursionError & e) {
// only one black hole can *throw* in any given eval stack so we need not
// check whether the position is set already.
if (v.isBlackhole())
e.err.errPos = positions[getPos()];
throw;
}
} }
else if (v.isBlackhole())
error("infinite recursion encountered").atPos(getPos()).template debugThrow<EvalError>();
} }

View file

@ -162,7 +162,17 @@ void Value::print(const SymbolTable &symbols, std::ostream &str,
break; break;
case tThunk: case tThunk:
case tApp: case tApp:
if (!isBlackhole()) {
str << "<CODE>"; str << "<CODE>";
} else {
// Although we know for sure that it's going to be an infinite recursion
// when this value is accessed _in the current context_, it's likely
// that the user will misinterpret a simpler «infinite recursion» output
// as a definitive statement about the value, while in fact it may be
// a valid value after `builtins.trace` and perhaps some other steps
// have completed.
str << "«potential infinite recursion»";
}
break; break;
case tLambda: case tLambda:
str << "<LAMBDA>"; str << "<LAMBDA>";
@ -179,15 +189,6 @@ void Value::print(const SymbolTable &symbols, std::ostream &str,
case tFloat: case tFloat:
str << fpoint; str << fpoint;
break; break;
case tBlackhole:
// Although we know for sure that it's going to be an infinite recursion
// when this value is accessed _in the current context_, it's likely
// that the user will misinterpret a simpler «infinite recursion» output
// as a definitive statement about the value, while in fact it may be
// a valid value after `builtins.trace` and perhaps some other steps
// have completed.
str << "«potential infinite recursion»";
break;
default: default:
printError("Nix evaluator internal error: Value::print(): invalid value type %1%", internalType); printError("Nix evaluator internal error: Value::print(): invalid value type %1%", internalType);
abort(); abort();
@ -256,8 +257,7 @@ std::string showType(const Value & v)
return fmt("the partially applied built-in function '%s'", std::string(getPrimOp(v)->primOp->name)); return fmt("the partially applied built-in function '%s'", std::string(getPrimOp(v)->primOp->name));
case tExternal: return v.external->showType(); case tExternal: return v.external->showType();
case tThunk: return "a thunk"; case tThunk: return "a thunk";
case tApp: return "a function application"; case tApp: return v.isBlackhole() ? "a black hole" : "a function application";
case tBlackhole: return "a black hole";
default: default:
return std::string(showType(v.type())); return std::string(showType(v.type()));
} }
@ -1621,15 +1621,17 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
return; return;
} else { } else {
/* We have all the arguments, so call the primop. */ /* We have all the arguments, so call the primop. */
auto name = vCur.primOp->name; auto * fn = vCur.primOp;
nrPrimOpCalls++; nrPrimOpCalls++;
if (countCalls) primOpCalls[name]++; // This will count black holes, but that's ok, because unrecoverable errors are rare.
if (countCalls) primOpCalls[fn->name]++;
try { try {
vCur.primOp->fun(*this, vCur.determinePos(noPos), args, vCur); fn->fun(*this, vCur.determinePos(noPos), args, vCur);
} catch (Error & e) { } catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", name); if (!fn->hideInDiagnostics)
addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name);
throw; throw;
} }
@ -1666,18 +1668,20 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
for (size_t i = 0; i < argsLeft; ++i) for (size_t i = 0; i < argsLeft; ++i)
vArgs[argsDone + i] = args[i]; vArgs[argsDone + i] = args[i];
auto name = primOp->primOp->name; auto fn = primOp->primOp;
nrPrimOpCalls++; nrPrimOpCalls++;
if (countCalls) primOpCalls[name]++; // This will count black holes, but that's ok, because unrecoverable errors are rare.
if (countCalls) primOpCalls[fn->name]++;
try { try {
// TODO: // TODO:
// 1. Unify this and above code. Heavily redundant. // 1. Unify this and above code. Heavily redundant.
// 2. Create a fake env (arg1, arg2, etc.) and a fake expr (arg1: arg2: etc: builtins.name arg1 arg2 etc) // 2. Create a fake env (arg1, arg2, etc.) and a fake expr (arg1: arg2: etc: builtins.name arg1 arg2 etc)
// so the debugger allows to inspect the wrong parameters passed to the builtin. // so the debugger allows to inspect the wrong parameters passed to the builtin.
primOp->primOp->fun(*this, vCur.determinePos(noPos), vArgs, vCur); fn->fun(*this, vCur.determinePos(noPos), vArgs, vCur);
} catch (Error & e) { } catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", name); if (!fn->hideInDiagnostics)
addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name);
throw; throw;
} }

View file

@ -77,6 +77,14 @@ struct PrimOp
*/ */
std::optional<ExperimentalFeature> experimentalFeature; std::optional<ExperimentalFeature> experimentalFeature;
/**
* Whether to hide this primop in diagnostics.
*
* Used to hide the fact that black holes are primop applications from
* stack traces.
*/
bool hideInDiagnostics;
/** /**
* Validity check to be performed by functions that introduce primops, * Validity check to be performed by functions that introduce primops,
* such as RegisterPrimOp() and Value::mkPrimOp(). * such as RegisterPrimOp() and Value::mkPrimOp().

View file

@ -21,6 +21,13 @@ MakeError(TypeError, EvalError);
MakeError(UndefinedVarError, Error); MakeError(UndefinedVarError, Error);
MakeError(MissingArgumentError, EvalError); MakeError(MissingArgumentError, EvalError);
class InfiniteRecursionError : public EvalError
{
friend class EvalState;
public:
using EvalError::EvalError;
};
/** /**
* Position objects. * Position objects.
*/ */

View file

@ -4263,6 +4263,29 @@ static RegisterPrimOp primop_splitVersion({
}); });
static void prim_blackHoleFn(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.error("infinite recursion encountered")
.debugThrow<InfiniteRecursionError>();
}
static PrimOp primop_blackHole = {
.name = "«blackHole»",
.args = {},
.fun = prim_blackHoleFn,
.hideInDiagnostics = true,
};
static Value makeBlackHole()
{
Value v;
v.mkPrimOp(&primop_blackHole);
return v;
}
Value prim_blackHole = makeBlackHole();
/************************************************************* /*************************************************************
* Primop registration * Primop registration
*************************************************************/ *************************************************************/

View file

@ -51,4 +51,10 @@ void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Valu
*/ */
void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v); void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v);
/**
* Placeholder value for black holes, used to represent black holes as
* applications of this value to the evaluated thunks.
*/
extern Value prim_blackHole;
} }

View file

@ -32,7 +32,6 @@ typedef enum {
tThunk, tThunk,
tApp, tApp,
tLambda, tLambda,
tBlackhole,
tPrimOp, tPrimOp,
tPrimOpApp, tPrimOpApp,
tExternal, tExternal,
@ -151,7 +150,7 @@ public:
// type() == nThunk // type() == nThunk
inline bool isThunk() const { return internalType == tThunk; }; inline bool isThunk() const { return internalType == tThunk; };
inline bool isApp() const { return internalType == tApp; }; inline bool isApp() const { return internalType == tApp; };
inline bool isBlackhole() const { return internalType == tBlackhole; }; inline bool isBlackhole() const;
// type() == nFunction // type() == nFunction
inline bool isLambda() const { return internalType == tLambda; }; inline bool isLambda() const { return internalType == tLambda; };
@ -248,7 +247,7 @@ public:
case tLambda: case tPrimOp: case tPrimOpApp: return nFunction; case tLambda: case tPrimOp: case tPrimOpApp: return nFunction;
case tExternal: return nExternal; case tExternal: return nExternal;
case tFloat: return nFloat; case tFloat: return nFloat;
case tThunk: case tApp: case tBlackhole: return nThunk; case tThunk: case tApp: return nThunk;
} }
if (invalidIsThunk) if (invalidIsThunk)
return nThunk; return nThunk;
@ -356,11 +355,7 @@ public:
lambda.fun = f; lambda.fun = f;
} }
inline void mkBlackhole() inline void mkBlackhole();
{
internalType = tBlackhole;
// Value will be overridden anyways
}
void mkPrimOp(PrimOp * p); void mkPrimOp(PrimOp * p);
@ -447,6 +442,19 @@ public:
}; };
extern Value prim_blackHole;
inline bool Value::isBlackhole() const
{
return internalType == tApp && app.left == &prim_blackHole;
}
inline void Value::mkBlackhole()
{
mkApp(&prim_blackHole, &prim_blackHole);
}
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
typedef std::vector<Value *, traceable_allocator<Value *>> ValueVector; typedef std::vector<Value *, traceable_allocator<Value *>> ValueVector;
typedef std::map<Symbol, Value *, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, Value *>>> ValueMap; typedef std::map<Symbol, Value *, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, Value *>>> ValueMap;