Merge pull request #5501 from edolstra/optimize-calls

Optimize primop calls
This commit is contained in:
Eelco Dolstra 2021-11-05 12:57:19 +01:00 committed by GitHub
commit a1c1b0e553
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 319 additions and 192 deletions

View file

@ -583,14 +583,20 @@ Value * EvalState::addConstant(const string & name, Value & v)
{ {
Value * v2 = allocValue(); Value * v2 = allocValue();
*v2 = v; *v2 = v;
staticBaseEnv.vars[symbols.create(name)] = baseEnvDispl; addConstant(name, v2);
baseEnv.values[baseEnvDispl++] = v2;
string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name;
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v2));
return v2; return v2;
} }
void EvalState::addConstant(const string & name, Value * v)
{
staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name;
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
}
Value * EvalState::addPrimOp(const string & name, Value * EvalState::addPrimOp(const string & name,
size_t arity, PrimOpFun primOp) size_t arity, PrimOpFun primOp)
{ {
@ -609,7 +615,7 @@ Value * EvalState::addPrimOp(const string & name,
Value * v = allocValue(); Value * v = allocValue();
v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym }); v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym });
staticBaseEnv.vars[symbols.create(name)] = baseEnvDispl; staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v; baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(sym, v)); baseEnv.values[0]->attrs->push_back(Attr(sym, v));
return v; return v;
@ -635,7 +641,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
Value * v = allocValue(); Value * v = allocValue();
v->mkPrimOp(new PrimOp(std::move(primOp))); v->mkPrimOp(new PrimOp(std::move(primOp)));
staticBaseEnv.vars[envName] = baseEnvDispl; staticBaseEnv.vars.emplace_back(envName, baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v; baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v)); baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v));
return v; return v;
@ -785,7 +791,7 @@ void mkPath(Value & v, const char * s)
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
{ {
for (size_t l = var.level; l; --l, env = env->up) ; for (auto l = var.level; l; --l, env = env->up) ;
if (!var.fromWith) return env->values[var.displ]; if (!var.fromWith) return env->values[var.displ];
@ -1058,7 +1064,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
/* The recursive attributes are evaluated in the new /* The recursive attributes are evaluated in the new
environment, while the inherited attributes are evaluated environment, while the inherited attributes are evaluated
in the original environment. */ in the original environment. */
size_t displ = 0; Displacement displ = 0;
for (auto & i : attrs) { for (auto & i : attrs) {
Value * vAttr; Value * vAttr;
if (hasOverrides && !i.second.inherited) { if (hasOverrides && !i.second.inherited) {
@ -1134,7 +1140,7 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
/* The recursive attributes are evaluated in the new environment, /* The recursive attributes are evaluated in the new environment,
while the inherited attributes are evaluated in the original while the inherited attributes are evaluated in the original
environment. */ environment. */
size_t displ = 0; Displacement displ = 0;
for (auto & i : attrs->attrs) for (auto & i : attrs->attrs)
env2.values[displ++] = i.second.e->maybeThunk(state, i.second.inherited ? env : env2); env2.values[displ++] = i.second.e->maybeThunk(state, i.second.inherited ? env : env2);
@ -1251,144 +1257,182 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
} }
void ExprApp::eval(EvalState & state, Env & env, Value & v) void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos)
{
/* FIXME: vFun prevents GCC from doing tail call optimisation. */
Value vFun;
e1->eval(state, env, vFun);
state.callFunction(vFun, *(e2->maybeThunk(state, env)), v, pos);
}
void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos)
{
/* Figure out the number of arguments still needed. */
size_t argsDone = 0;
Value * primOp = &fun;
while (primOp->isPrimOpApp()) {
argsDone++;
primOp = primOp->primOpApp.left;
}
assert(primOp->isPrimOp());
auto arity = primOp->primOp->arity;
auto argsLeft = arity - argsDone;
if (argsLeft == 1) {
/* We have all the arguments, so call the primop. */
/* Put all the arguments in an array. */
Value * vArgs[arity];
auto n = arity - 1;
vArgs[n--] = &arg;
for (Value * arg = &fun; arg->isPrimOpApp(); arg = arg->primOpApp.left)
vArgs[n--] = arg->primOpApp.right;
/* And call the primop. */
nrPrimOpCalls++;
if (countCalls) primOpCalls[primOp->primOp->name]++;
primOp->primOp->fun(*this, pos, vArgs, v);
} else {
Value * fun2 = allocValue();
*fun2 = fun;
v.mkPrimOpApp(fun2, &arg);
}
}
void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & pos)
{ {
auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr; auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr;
forceValue(fun, pos); forceValue(fun, pos);
if (fun.isPrimOp() || fun.isPrimOpApp()) { Value vCur(fun);
callPrimOp(fun, arg, v, pos);
return;
}
if (fun.type() == nAttrs) { auto makeAppChain = [&]()
auto found = fun.attrs->find(sFunctor); {
if (found != fun.attrs->end()) { vRes = vCur;
/* fun may be allocated on the stack of the calling function, for (size_t i = 0; i < nrArgs; ++i) {
* but for functors we may keep a reference, so heap-allocate auto fun2 = allocValue();
* a copy and use that instead. *fun2 = vRes;
*/ vRes.mkPrimOpApp(fun2, args[i]);
auto & fun2 = *allocValue(); }
fun2 = fun; };
/* !!! Should we use the attr pos here? */
Value v2;
callFunction(*found->value, fun2, v2, pos);
return callFunction(v2, arg, v, pos);
}
}
if (!fun.isLambda()) while (nrArgs > 0) {
throwTypeError(pos, "attempt to call something which is not a function but %1%", fun);
ExprLambda & lambda(*fun.lambda.fun); if (vCur.isLambda()) {
auto size = ExprLambda & lambda(*vCur.lambda.fun);
(lambda.arg.empty() ? 0 : 1) +
(lambda.hasFormals() ? lambda.formals->formals.size() : 0);
Env & env2(allocEnv(size));
env2.up = fun.lambda.env;
size_t displ = 0; auto size =
(lambda.arg.empty() ? 0 : 1) +
(lambda.hasFormals() ? lambda.formals->formals.size() : 0);
Env & env2(allocEnv(size));
env2.up = vCur.lambda.env;
if (!lambda.hasFormals()) Displacement displ = 0;
env2.values[displ++] = &arg;
else { if (!lambda.hasFormals())
forceAttrs(arg, pos); env2.values[displ++] = args[0];
if (!lambda.arg.empty()) else {
env2.values[displ++] = &arg; forceAttrs(*args[0], pos);
/* For each formal argument, get the actual argument. If if (!lambda.arg.empty())
there is no matching actual argument but the formal env2.values[displ++] = args[0];
argument has a default, use the default. */
size_t attrsUsed = 0; /* For each formal argument, get the actual argument. If
for (auto & i : lambda.formals->formals) { there is no matching actual argument but the formal
Bindings::iterator j = arg.attrs->find(i.name); argument has a default, use the default. */
if (j == arg.attrs->end()) { size_t attrsUsed = 0;
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'", for (auto & i : lambda.formals->formals) {
lambda, i.name); auto j = args[0]->attrs->get(i.name);
env2.values[displ++] = i.def->maybeThunk(*this, env2); if (!j) {
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
lambda, i.name);
env2.values[displ++] = i.def->maybeThunk(*this, env2);
} else {
attrsUsed++;
env2.values[displ++] = j->value;
}
}
/* Check that each actual argument is listed as a formal
argument (unless the attribute match specifies a `...'). */
if (!lambda.formals->ellipsis && attrsUsed != args[0]->attrs->size()) {
/* Nope, so show the first unexpected argument to the
user. */
for (auto & i : *args[0]->attrs)
if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end())
throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name);
abort(); // can't happen
}
}
nrFunctionCalls++;
if (countCalls) incrFunctionCall(&lambda);
/* Evaluate the body. */
try {
lambda.body->eval(*this, env2, vCur);
} catch (Error & e) {
if (loggerSettings.showTrace.get()) {
addErrorTrace(e, lambda.pos, "while evaluating %s",
(lambda.name.set()
? "'" + (string) lambda.name + "'"
: "anonymous lambda"));
addErrorTrace(e, pos, "from call site%s", "");
}
throw;
}
nrArgs--;
args += 1;
}
else if (vCur.isPrimOp()) {
size_t argsLeft = vCur.primOp->arity;
if (nrArgs < argsLeft) {
/* We don't have enough arguments, so create a tPrimOpApp chain. */
makeAppChain();
return;
} else { } else {
attrsUsed++; /* We have all the arguments, so call the primop. */
env2.values[displ++] = j->value; nrPrimOpCalls++;
if (countCalls) primOpCalls[vCur.primOp->name]++;
vCur.primOp->fun(*this, pos, args, vCur);
nrArgs -= argsLeft;
args += argsLeft;
} }
} }
/* Check that each actual argument is listed as a formal else if (vCur.isPrimOpApp()) {
argument (unless the attribute match specifies a `...'). */ /* Figure out the number of arguments still needed. */
if (!lambda.formals->ellipsis && attrsUsed != arg.attrs->size()) { size_t argsDone = 0;
/* Nope, so show the first unexpected argument to the Value * primOp = &vCur;
user. */ while (primOp->isPrimOpApp()) {
for (auto & i : *arg.attrs) argsDone++;
if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end()) primOp = primOp->primOpApp.left;
throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name); }
abort(); // can't happen assert(primOp->isPrimOp());
auto arity = primOp->primOp->arity;
auto argsLeft = arity - argsDone;
if (nrArgs < argsLeft) {
/* We still don't have enough arguments, so extend the tPrimOpApp chain. */
makeAppChain();
return;
} else {
/* We have all the arguments, so call the primop with
the previous and new arguments. */
Value * vArgs[arity];
auto n = argsDone;
for (Value * arg = &vCur; arg->isPrimOpApp(); arg = arg->primOpApp.left)
vArgs[--n] = arg->primOpApp.right;
for (size_t i = 0; i < argsLeft; ++i)
vArgs[argsDone + i] = args[i];
nrPrimOpCalls++;
if (countCalls) primOpCalls[primOp->primOp->name]++;
primOp->primOp->fun(*this, pos, vArgs, vCur);
nrArgs -= argsLeft;
args += argsLeft;
}
} }
else if (vCur.type() == nAttrs) {
if (auto functor = vCur.attrs->get(sFunctor)) {
/* 'vCur" may be allocated on the stack of the calling
function, but for functors we may keep a reference,
so heap-allocate a copy and use that instead. */
Value * args2[] = {allocValue()};
*args2[0] = vCur;
/* !!! Should we use the attr pos here? */
callFunction(*functor->value, 1, args2, vCur, pos);
}
}
else
throwTypeError(pos, "attempt to call something which is not a function but %1%", vCur);
} }
nrFunctionCalls++; vRes = vCur;
if (countCalls) incrFunctionCall(&lambda); }
/* Evaluate the body. This is conditional on showTrace, because
catching exceptions makes this function not tail-recursive. */ void ExprCall::eval(EvalState & state, Env & env, Value & v)
if (loggerSettings.showTrace.get()) {
try { Value vFun;
lambda.body->eval(*this, env2, v); fun->eval(state, env, vFun);
} catch (Error & e) {
addErrorTrace(e, lambda.pos, "while evaluating %s", Value * vArgs[args.size()];
(lambda.name.set() for (size_t i = 0; i < args.size(); ++i)
? "'" + (string) lambda.name + "'" vArgs[i] = args[i]->maybeThunk(state, env);
: "anonymous lambda"));
addErrorTrace(e, pos, "from call site%s", ""); state.callFunction(vFun, args.size(), vArgs, v, pos);
throw;
}
else
fun.lambda.fun->body->eval(*this, env2, v);
} }

View file

@ -277,6 +277,8 @@ private:
Value * addConstant(const string & name, Value & v); Value * addConstant(const string & name, Value & v);
void addConstant(const string & name, Value * v);
Value * addPrimOp(const string & name, Value * addPrimOp(const string & name,
size_t arity, PrimOpFun primOp); size_t arity, PrimOpFun primOp);
@ -316,8 +318,14 @@ public:
bool isFunctor(Value & fun); bool isFunctor(Value & fun);
void callFunction(Value & fun, Value & arg, Value & v, const Pos & pos); // FIXME: use std::span
void callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos); void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos);
void callFunction(Value & fun, Value & arg, Value & vRes, const Pos & pos)
{
Value * args[] = {&arg};
callFunction(fun, 1, args, vRes, pos);
}
/* Automatically call a function for which each argument has a /* Automatically call a function for which each argument has a
default value or has a binding in the `args' map. */ default value or has a binding in the `args' map. */

View file

@ -64,6 +64,7 @@ static void adjustLoc(YYLTYPE * loc, const char * s, size_t len)
} }
// FIXME: optimize
static Expr * unescapeStr(SymbolTable & symbols, const char * s, size_t length) static Expr * unescapeStr(SymbolTable & symbols, const char * s, size_t length)
{ {
string t; string t;

View file

@ -143,6 +143,16 @@ void ExprLambda::show(std::ostream & str) const
str << ": " << *body << ")"; str << ": " << *body << ")";
} }
void ExprCall::show(std::ostream & str) const
{
str << '(' << *fun;
for (auto e : args) {
str << ' ';
str << *e;
}
str << ')';
}
void ExprLet::show(std::ostream & str) const void ExprLet::show(std::ostream & str) const
{ {
str << "(let "; str << "(let ";
@ -263,13 +273,13 @@ void ExprVar::bindVars(const StaticEnv & env)
/* Check whether the variable appears in the environment. If so, /* Check whether the variable appears in the environment. If so,
set its level and displacement. */ set its level and displacement. */
const StaticEnv * curEnv; const StaticEnv * curEnv;
unsigned int level; Level level;
int withLevel = -1; int withLevel = -1;
for (curEnv = &env, level = 0; curEnv; curEnv = curEnv->up, level++) { for (curEnv = &env, level = 0; curEnv; curEnv = curEnv->up, level++) {
if (curEnv->isWith) { if (curEnv->isWith) {
if (withLevel == -1) withLevel = level; if (withLevel == -1) withLevel = level;
} else { } else {
StaticEnv::Vars::const_iterator i = curEnv->vars.find(name); auto i = curEnv->find(name);
if (i != curEnv->vars.end()) { if (i != curEnv->vars.end()) {
fromWith = false; fromWith = false;
this->level = level; this->level = level;
@ -311,14 +321,16 @@ void ExprOpHasAttr::bindVars(const StaticEnv & env)
void ExprAttrs::bindVars(const StaticEnv & env) void ExprAttrs::bindVars(const StaticEnv & env)
{ {
const StaticEnv * dynamicEnv = &env; const StaticEnv * dynamicEnv = &env;
StaticEnv newEnv(false, &env); StaticEnv newEnv(false, &env, recursive ? attrs.size() : 0);
if (recursive) { if (recursive) {
dynamicEnv = &newEnv; dynamicEnv = &newEnv;
unsigned int displ = 0; Displacement displ = 0;
for (auto & i : attrs) for (auto & i : attrs)
newEnv.vars[i.first] = i.second.displ = displ++; newEnv.vars.emplace_back(i.first, i.second.displ = displ++);
// No need to sort newEnv since attrs is in sorted order.
for (auto & i : attrs) for (auto & i : attrs)
i.second.e->bindVars(i.second.inherited ? env : newEnv); i.second.e->bindVars(i.second.inherited ? env : newEnv);
@ -342,15 +354,20 @@ void ExprList::bindVars(const StaticEnv & env)
void ExprLambda::bindVars(const StaticEnv & env) void ExprLambda::bindVars(const StaticEnv & env)
{ {
StaticEnv newEnv(false, &env); StaticEnv newEnv(
false, &env,
(hasFormals() ? formals->formals.size() : 0) +
(arg.empty() ? 0 : 1));
unsigned int displ = 0; Displacement displ = 0;
if (!arg.empty()) newEnv.vars[arg] = displ++; if (!arg.empty()) newEnv.vars.emplace_back(arg, displ++);
if (hasFormals()) { if (hasFormals()) {
for (auto & i : formals->formals) for (auto & i : formals->formals)
newEnv.vars[i.name] = displ++; newEnv.vars.emplace_back(i.name, displ++);
newEnv.sort();
for (auto & i : formals->formals) for (auto & i : formals->formals)
if (i.def) i.def->bindVars(newEnv); if (i.def) i.def->bindVars(newEnv);
@ -359,13 +376,22 @@ void ExprLambda::bindVars(const StaticEnv & env)
body->bindVars(newEnv); body->bindVars(newEnv);
} }
void ExprCall::bindVars(const StaticEnv & env)
{
fun->bindVars(env);
for (auto e : args)
e->bindVars(env);
}
void ExprLet::bindVars(const StaticEnv & env) void ExprLet::bindVars(const StaticEnv & env)
{ {
StaticEnv newEnv(false, &env); StaticEnv newEnv(false, &env, attrs->attrs.size());
unsigned int displ = 0; Displacement displ = 0;
for (auto & i : attrs->attrs) for (auto & i : attrs->attrs)
newEnv.vars[i.first] = i.second.displ = displ++; newEnv.vars.emplace_back(i.first, i.second.displ = displ++);
// No need to sort newEnv since attrs->attrs is in sorted order.
for (auto & i : attrs->attrs) for (auto & i : attrs->attrs)
i.second.e->bindVars(i.second.inherited ? env : newEnv); i.second.e->bindVars(i.second.inherited ? env : newEnv);
@ -379,7 +405,7 @@ void ExprWith::bindVars(const StaticEnv & env)
level so that `lookupVar' can look up variables in the previous level so that `lookupVar' can look up variables in the previous
`with' if this one doesn't contain the desired attribute. */ `with' if this one doesn't contain the desired attribute. */
const StaticEnv * curEnv; const StaticEnv * curEnv;
unsigned int level; Level level;
prevWith = 0; prevWith = 0;
for (curEnv = &env, level = 1; curEnv; curEnv = curEnv->up, level++) for (curEnv = &env, level = 1; curEnv; curEnv = curEnv->up, level++)
if (curEnv->isWith) { if (curEnv->isWith) {
@ -452,5 +478,4 @@ size_t SymbolTable::totalSize() const
return n; return n;
} }
} }

View file

@ -4,8 +4,6 @@
#include "symbol-table.hh" #include "symbol-table.hh"
#include "error.hh" #include "error.hh"
#include <map>
namespace nix { namespace nix {
@ -135,6 +133,9 @@ struct ExprPath : Expr
Value * maybeThunk(EvalState & state, Env & env); Value * maybeThunk(EvalState & state, Env & env);
}; };
typedef uint32_t Level;
typedef uint32_t Displacement;
struct ExprVar : Expr struct ExprVar : Expr
{ {
Pos pos; Pos pos;
@ -150,8 +151,8 @@ struct ExprVar : Expr
value is obtained by getting the attribute named `name' from value is obtained by getting the attribute named `name' from
the set stored in the environment that is `level' levels up the set stored in the environment that is `level' levels up
from the current one.*/ from the current one.*/
unsigned int level; Level level;
unsigned int displ; Displacement displ;
ExprVar(const Symbol & name) : name(name) { }; ExprVar(const Symbol & name) : name(name) { };
ExprVar(const Pos & pos, const Symbol & name) : pos(pos), name(name) { }; ExprVar(const Pos & pos, const Symbol & name) : pos(pos), name(name) { };
@ -185,7 +186,7 @@ struct ExprAttrs : Expr
bool inherited; bool inherited;
Expr * e; Expr * e;
Pos pos; Pos pos;
unsigned int displ; // displacement Displacement displ; // displacement
AttrDef(Expr * e, const Pos & pos, bool inherited=false) AttrDef(Expr * e, const Pos & pos, bool inherited=false)
: inherited(inherited), e(e), pos(pos) { }; : inherited(inherited), e(e), pos(pos) { };
AttrDef() { }; AttrDef() { };
@ -250,6 +251,17 @@ struct ExprLambda : Expr
COMMON_METHODS COMMON_METHODS
}; };
struct ExprCall : Expr
{
Expr * fun;
std::vector<Expr *> args;
Pos pos;
ExprCall(const Pos & pos, Expr * fun, std::vector<Expr *> && args)
: fun(fun), args(args), pos(pos)
{ }
COMMON_METHODS
};
struct ExprLet : Expr struct ExprLet : Expr
{ {
ExprAttrs * attrs; ExprAttrs * attrs;
@ -308,7 +320,6 @@ struct ExprOpNot : Expr
void eval(EvalState & state, Env & env, Value & v); \ void eval(EvalState & state, Env & env, Value & v); \
}; };
MakeBinOp(ExprApp, "")
MakeBinOp(ExprOpEq, "==") MakeBinOp(ExprOpEq, "==")
MakeBinOp(ExprOpNEq, "!=") MakeBinOp(ExprOpNEq, "!=")
MakeBinOp(ExprOpAnd, "&&") MakeBinOp(ExprOpAnd, "&&")
@ -342,9 +353,28 @@ struct StaticEnv
{ {
bool isWith; bool isWith;
const StaticEnv * up; const StaticEnv * up;
typedef std::map<Symbol, unsigned int> Vars;
// Note: these must be in sorted order.
typedef std::vector<std::pair<Symbol, Displacement>> Vars;
Vars vars; Vars vars;
StaticEnv(bool isWith, const StaticEnv * up) : isWith(isWith), up(up) { };
StaticEnv(bool isWith, const StaticEnv * up, size_t expectedSize = 0) : isWith(isWith), up(up) {
vars.reserve(expectedSize);
};
void sort()
{
std::sort(vars.begin(), vars.end(),
[](const Vars::value_type & a, const Vars::value_type & b) { return a.first < b.first; });
}
Vars::const_iterator find(const Symbol & name) const
{
Vars::value_type key(name, 0);
auto i = std::lower_bound(vars.begin(), vars.end(), key);
if (i != vars.end() && i->first == name) return i;
return vars.end();
}
}; };

View file

@ -41,6 +41,12 @@ namespace nix {
{ }; { };
}; };
// Helper to prevent an expensive dynamic_cast call in expr_app.
struct App
{
Expr * e;
bool isCall;
};
} }
#define YY_DECL int yylex \ #define YY_DECL int yylex \
@ -126,14 +132,14 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
auto j2 = jAttrs->attrs.find(ad.first); auto j2 = jAttrs->attrs.find(ad.first);
if (j2 != jAttrs->attrs.end()) // Attr already defined in iAttrs, error. if (j2 != jAttrs->attrs.end()) // Attr already defined in iAttrs, error.
dupAttr(ad.first, j2->second.pos, ad.second.pos); dupAttr(ad.first, j2->second.pos, ad.second.pos);
jAttrs->attrs[ad.first] = ad.second; jAttrs->attrs.emplace(ad.first, ad.second);
} }
} else { } else {
dupAttr(attrPath, pos, j->second.pos); dupAttr(attrPath, pos, j->second.pos);
} }
} else { } else {
// This attr path is not defined. Let's create it. // This attr path is not defined. Let's create it.
attrs->attrs[i->symbol] = ExprAttrs::AttrDef(e, pos); attrs->attrs.emplace(i->symbol, ExprAttrs::AttrDef(e, pos));
e->setName(i->symbol); e->setName(i->symbol);
} }
} else { } else {
@ -280,10 +286,12 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
char * uri; char * uri;
std::vector<nix::AttrName> * attrNames; std::vector<nix::AttrName> * attrNames;
std::vector<nix::Expr *> * string_parts; std::vector<nix::Expr *> * string_parts;
nix::App app; // bool == whether this is an ExprCall
} }
%type <e> start expr expr_function expr_if expr_op %type <e> start expr expr_function expr_if expr_op
%type <e> expr_app expr_select expr_simple %type <e> expr_select expr_simple
%type <app> expr_app
%type <list> expr_list %type <list> expr_list
%type <attrs> binds %type <attrs> binds
%type <formals> formals %type <formals> formals
@ -353,13 +361,13 @@ expr_if
expr_op expr_op
: '!' expr_op %prec NOT { $$ = new ExprOpNot($2); } : '!' expr_op %prec NOT { $$ = new ExprOpNot($2); }
| '-' expr_op %prec NEGATE { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__sub")), new ExprInt(0)), $2); } | '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {new ExprInt(0), $2}); }
| expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); } | expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); }
| expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); } | expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); }
| expr_op '<' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__lessThan")), $1), $3); } | expr_op '<' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); }
| expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__lessThan")), $3), $1)); } | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); }
| expr_op '>' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__lessThan")), $3), $1); } | expr_op '>' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); }
| expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__lessThan")), $1), $3)); } | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); }
| expr_op AND expr_op { $$ = new ExprOpAnd(CUR_POS, $1, $3); } | expr_op AND expr_op { $$ = new ExprOpAnd(CUR_POS, $1, $3); }
| expr_op OR expr_op { $$ = new ExprOpOr(CUR_POS, $1, $3); } | expr_op OR expr_op { $$ = new ExprOpOr(CUR_POS, $1, $3); }
| expr_op IMPL expr_op { $$ = new ExprOpImpl(CUR_POS, $1, $3); } | expr_op IMPL expr_op { $$ = new ExprOpImpl(CUR_POS, $1, $3); }
@ -367,17 +375,24 @@ expr_op
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); } | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
| expr_op '+' expr_op | expr_op '+' expr_op
{ $$ = new ExprConcatStrings(CUR_POS, false, new vector<Expr *>({$1, $3})); } { $$ = new ExprConcatStrings(CUR_POS, false, new vector<Expr *>({$1, $3})); }
| expr_op '-' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__sub")), $1), $3); } | expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
| expr_op '*' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__mul")), $1), $3); } | expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
| expr_op '/' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__div")), $1), $3); } | expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); }
| expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(CUR_POS, $1, $3); } | expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(CUR_POS, $1, $3); }
| expr_app | expr_app { $$ = $1.e; }
; ;
expr_app expr_app
: expr_app expr_select : expr_app expr_select {
{ $$ = new ExprApp(CUR_POS, $1, $2); } if ($1.isCall) {
| expr_select { $$ = $1; } ((ExprCall *) $1.e)->args.push_back($2);
$$ = $1;
} else {
$$.e = new ExprCall(CUR_POS, $1.e, {$2});
$$.isCall = true;
}
}
| expr_select { $$.e = $1; $$.isCall = false; }
; ;
expr_select expr_select
@ -388,7 +403,7 @@ expr_select
| /* Backwards compatibility: because Nixpkgs has a rarely used | /* Backwards compatibility: because Nixpkgs has a rarely used
function named or, allow stuff like map or [...]. */ function named or, allow stuff like map or [...]. */
expr_simple OR_KW expr_simple OR_KW
{ $$ = new ExprApp(CUR_POS, $1, new ExprVar(CUR_POS, data->symbols.create("or"))); } { $$ = new ExprCall(CUR_POS, $1, {new ExprVar(CUR_POS, data->symbols.create("or"))}); }
| expr_simple { $$ = $1; } | expr_simple { $$ = $1; }
; ;
@ -412,10 +427,10 @@ expr_simple
} }
| SPATH { | SPATH {
string path($1 + 1, strlen($1) - 2); string path($1 + 1, strlen($1) - 2);
$$ = new ExprApp(CUR_POS, $$ = new ExprCall(CUR_POS,
new ExprApp(new ExprVar(data->symbols.create("__findFile")), new ExprVar(data->symbols.create("__findFile")),
new ExprVar(data->symbols.create("__nixPath"))), {new ExprVar(data->symbols.create("__nixPath")),
new ExprString(data->symbols.create(path))); new ExprString(data->symbols.create(path))});
} }
| URI { | URI {
static bool noURLLiterals = settings.isExperimentalFeatureEnabled(Xp::NoUrlLiterals); static bool noURLLiterals = settings.isExperimentalFeatureEnabled(Xp::NoUrlLiterals);
@ -483,7 +498,7 @@ binds
if ($$->attrs.find(i.symbol) != $$->attrs.end()) if ($$->attrs.find(i.symbol) != $$->attrs.end())
dupAttr(i.symbol, makeCurPos(@3, data), $$->attrs[i.symbol].pos); dupAttr(i.symbol, makeCurPos(@3, data), $$->attrs[i.symbol].pos);
Pos pos = makeCurPos(@3, data); Pos pos = makeCurPos(@3, data);
$$->attrs[i.symbol] = ExprAttrs::AttrDef(new ExprVar(CUR_POS, i.symbol), pos, true); $$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprVar(CUR_POS, i.symbol), pos, true));
} }
} }
| binds INHERIT '(' expr ')' attrs ';' | binds INHERIT '(' expr ')' attrs ';'
@ -492,7 +507,7 @@ binds
for (auto & i : *$6) { for (auto & i : *$6) {
if ($$->attrs.find(i.symbol) != $$->attrs.end()) if ($$->attrs.find(i.symbol) != $$->attrs.end())
dupAttr(i.symbol, makeCurPos(@6, data), $$->attrs[i.symbol].pos); dupAttr(i.symbol, makeCurPos(@6, data), $$->attrs[i.symbol].pos);
$$->attrs[i.symbol] = ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data)); $$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data)));
} }
} }
| { $$ = new ExprAttrs(makeCurPos(@0, data)); } | { $$ = new ExprAttrs(makeCurPos(@0, data)); }

View file

@ -184,14 +184,17 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS
Env * env = &state.allocEnv(vScope->attrs->size()); Env * env = &state.allocEnv(vScope->attrs->size());
env->up = &state.baseEnv; env->up = &state.baseEnv;
StaticEnv staticEnv(false, &state.staticBaseEnv); StaticEnv staticEnv(false, &state.staticBaseEnv, vScope->attrs->size());
unsigned int displ = 0; unsigned int displ = 0;
for (auto & attr : *vScope->attrs) { for (auto & attr : *vScope->attrs) {
staticEnv.vars[attr.name] = displ; staticEnv.vars.emplace_back(attr.name, displ);
env->values[displ++] = attr.value; env->values[displ++] = attr.value;
} }
// No need to call staticEnv.sort(), because
// args[0]->attrs is already sorted.
printTalkative("evaluating file '%1%'", realPath); printTalkative("evaluating file '%1%'", realPath);
Expr * e = state.parseExprFromFile(resolveExprPath(realPath), staticEnv); Expr * e = state.parseExprFromFile(resolveExprPath(realPath), staticEnv);
@ -1880,9 +1883,6 @@ static void addPath(
Value arg1; Value arg1;
mkString(arg1, path); mkString(arg1, path);
Value fun2;
state.callFunction(*filterFun, arg1, fun2, noPos);
Value arg2; Value arg2;
mkString(arg2, mkString(arg2,
S_ISREG(st.st_mode) ? "regular" : S_ISREG(st.st_mode) ? "regular" :
@ -1890,8 +1890,9 @@ static void addPath(
S_ISLNK(st.st_mode) ? "symlink" : S_ISLNK(st.st_mode) ? "symlink" :
"unknown" /* not supported, will fail! */); "unknown" /* not supported, will fail! */);
Value * args []{&arg1, &arg2};
Value res; Value res;
state.callFunction(fun2, arg2, res, noPos); state.callFunction(*filterFun, 2, args, res, pos);
return state.forceBool(res, pos); return state.forceBool(res, pos);
}) : defaultPathFilter; }) : defaultPathFilter;
@ -2692,10 +2693,9 @@ static void prim_foldlStrict(EvalState & state, const Pos & pos, Value * * args,
Value * vCur = args[1]; Value * vCur = args[1];
for (unsigned int n = 0; n < args[2]->listSize(); ++n) { for (unsigned int n = 0; n < args[2]->listSize(); ++n) {
Value vTmp; Value * vs []{vCur, args[2]->listElems()[n]};
state.callFunction(*args[0], *vCur, vTmp, pos);
vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue(); vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue();
state.callFunction(vTmp, *args[2]->listElems()[n], *vCur, pos); state.callFunction(*args[0], 2, vs, *vCur, pos);
} }
state.forceValue(v, pos); state.forceValue(v, pos);
} else { } else {
@ -2816,17 +2816,16 @@ static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value
v.listElems()[n] = args[1]->listElems()[n]; v.listElems()[n] = args[1]->listElems()[n];
} }
auto comparator = [&](Value * a, Value * b) { auto comparator = [&](Value * a, Value * b) {
/* Optimization: if the comparator is lessThan, bypass /* Optimization: if the comparator is lessThan, bypass
callFunction. */ callFunction. */
if (args[0]->isPrimOp() && args[0]->primOp->fun == prim_lessThan) if (args[0]->isPrimOp() && args[0]->primOp->fun == prim_lessThan)
return CompareValues()(a, b); return CompareValues()(a, b);
Value vTmp1, vTmp2; Value * vs[] = {a, b};
state.callFunction(*args[0], *a, vTmp1, pos); Value vBool;
state.callFunction(vTmp1, *b, vTmp2, pos); state.callFunction(*args[0], 2, vs, vBool, pos);
return state.forceBool(vTmp2, pos); return state.forceBool(vBool, pos);
}; };
/* FIXME: std::sort can segfault if the comparator is not a strict /* FIXME: std::sort can segfault if the comparator is not a strict
@ -3727,14 +3726,20 @@ void EvalState::createBaseEnv()
/* Add a wrapper around the derivation primop that computes the /* Add a wrapper around the derivation primop that computes the
`drvPath' and `outPath' attributes lazily. */ `drvPath' and `outPath' attributes lazily. */
sDerivationNix = symbols.create("//builtin/derivation.nix"); sDerivationNix = symbols.create("//builtin/derivation.nix");
eval(parse( auto vDerivation = allocValue();
#include "primops/derivation.nix.gen.hh" addConstant("derivation", vDerivation);
, foFile, sDerivationNix, "/", staticBaseEnv), v);
addConstant("derivation", v);
/* Now that we've added all primops, sort the `builtins' set, /* Now that we've added all primops, sort the `builtins' set,
because attribute lookups expect it to be sorted. */ because attribute lookups expect it to be sorted. */
baseEnv.values[0]->attrs->sort(); baseEnv.values[0]->attrs->sort();
staticBaseEnv.sort();
/* Note: we have to initialize the 'derivation' constant *after*
building baseEnv/staticBaseEnv because it uses 'builtins'. */
eval(parse(
#include "primops/derivation.nix.gen.hh"
, foFile, sDerivationNix, "/", staticBaseEnv), *vDerivation);
} }

View file

@ -644,7 +644,8 @@ void NixRepl::addVarToScope(const Symbol & name, Value & v)
{ {
if (displ >= envSize) if (displ >= envSize)
throw Error("environment full; cannot add more variables"); throw Error("environment full; cannot add more variables");
staticEnv.vars[name] = displ; staticEnv.vars.emplace_back(name, displ);
staticEnv.sort();
env->values[displ++] = &v; env->values[displ++] = &v;
varNames.insert((string) name); varNames.insert((string) name);
} }

View file

@ -60,8 +60,6 @@ function-trace exited (string):1:1 at
expect_trace '(x: x) 1 2' " expect_trace '(x: x) 1 2' "
function-trace entered (string):1:1 at function-trace entered (string):1:1 at
function-trace exited (string):1:1 at function-trace exited (string):1:1 at
function-trace entered (string):1:1 at
function-trace exited (string):1:1 at
" "
# Not a function # Not a function