Skip to content

Commit a1c1b0e

Browse files
authored
Merge pull request NixOS#5501 from edolstra/optimize-calls
Optimize primop calls
2 parents 7d6017b + 4092533 commit a1c1b0e

File tree

9 files changed

+320
-193
lines changed

9 files changed

+320
-193
lines changed

src/libexpr/eval.cc

Lines changed: 170 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -583,14 +583,20 @@ Value * EvalState::addConstant(const string & name, Value & v)
583583
{
584584
Value * v2 = allocValue();
585585
*v2 = v;
586-
staticBaseEnv.vars[symbols.create(name)] = baseEnvDispl;
587-
baseEnv.values[baseEnvDispl++] = v2;
588-
string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name;
589-
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v2));
586+
addConstant(name, v2);
590587
return v2;
591588
}
592589

593590

591+
void EvalState::addConstant(const string & name, Value * v)
592+
{
593+
staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl);
594+
baseEnv.values[baseEnvDispl++] = v;
595+
string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name;
596+
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
597+
}
598+
599+
594600
Value * EvalState::addPrimOp(const string & name,
595601
size_t arity, PrimOpFun primOp)
596602
{
@@ -609,7 +615,7 @@ Value * EvalState::addPrimOp(const string & name,
609615

610616
Value * v = allocValue();
611617
v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym });
612-
staticBaseEnv.vars[symbols.create(name)] = baseEnvDispl;
618+
staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl);
613619
baseEnv.values[baseEnvDispl++] = v;
614620
baseEnv.values[0]->attrs->push_back(Attr(sym, v));
615621
return v;
@@ -635,7 +641,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
635641

636642
Value * v = allocValue();
637643
v->mkPrimOp(new PrimOp(std::move(primOp)));
638-
staticBaseEnv.vars[envName] = baseEnvDispl;
644+
staticBaseEnv.vars.emplace_back(envName, baseEnvDispl);
639645
baseEnv.values[baseEnvDispl++] = v;
640646
baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v));
641647
return v;
@@ -785,7 +791,7 @@ void mkPath(Value & v, const char * s)
785791

786792
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
787793
{
788-
for (size_t l = var.level; l; --l, env = env->up) ;
794+
for (auto l = var.level; l; --l, env = env->up) ;
789795

790796
if (!var.fromWith) return env->values[var.displ];
791797

@@ -1058,7 +1064,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
10581064
/* The recursive attributes are evaluated in the new
10591065
environment, while the inherited attributes are evaluated
10601066
in the original environment. */
1061-
size_t displ = 0;
1067+
Displacement displ = 0;
10621068
for (auto & i : attrs) {
10631069
Value * vAttr;
10641070
if (hasOverrides && !i.second.inherited) {
@@ -1134,7 +1140,7 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
11341140
/* The recursive attributes are evaluated in the new environment,
11351141
while the inherited attributes are evaluated in the original
11361142
environment. */
1137-
size_t displ = 0;
1143+
Displacement displ = 0;
11381144
for (auto & i : attrs->attrs)
11391145
env2.values[displ++] = i.second.e->maybeThunk(state, i.second.inherited ? env : env2);
11401146

@@ -1251,144 +1257,182 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
12511257
}
12521258

12531259

1254-
void ExprApp::eval(EvalState & state, Env & env, Value & v)
1260+
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos)
12551261
{
1256-
/* FIXME: vFun prevents GCC from doing tail call optimisation. */
1257-
Value vFun;
1258-
e1->eval(state, env, vFun);
1259-
state.callFunction(vFun, *(e2->maybeThunk(state, env)), v, pos);
1260-
}
1262+
auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr;
12611263

1264+
forceValue(fun, pos);
12621265

1263-
void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos)
1264-
{
1265-
/* Figure out the number of arguments still needed. */
1266-
size_t argsDone = 0;
1267-
Value * primOp = &fun;
1268-
while (primOp->isPrimOpApp()) {
1269-
argsDone++;
1270-
primOp = primOp->primOpApp.left;
1271-
}
1272-
assert(primOp->isPrimOp());
1273-
auto arity = primOp->primOp->arity;
1274-
auto argsLeft = arity - argsDone;
1275-
1276-
if (argsLeft == 1) {
1277-
/* We have all the arguments, so call the primop. */
1278-
1279-
/* Put all the arguments in an array. */
1280-
Value * vArgs[arity];
1281-
auto n = arity - 1;
1282-
vArgs[n--] = &arg;
1283-
for (Value * arg = &fun; arg->isPrimOpApp(); arg = arg->primOpApp.left)
1284-
vArgs[n--] = arg->primOpApp.right;
1285-
1286-
/* And call the primop. */
1287-
nrPrimOpCalls++;
1288-
if (countCalls) primOpCalls[primOp->primOp->name]++;
1289-
primOp->primOp->fun(*this, pos, vArgs, v);
1290-
} else {
1291-
Value * fun2 = allocValue();
1292-
*fun2 = fun;
1293-
v.mkPrimOpApp(fun2, &arg);
1294-
}
1295-
}
1266+
Value vCur(fun);
12961267

1297-
void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & pos)
1298-
{
1299-
auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr;
1268+
auto makeAppChain = [&]()
1269+
{
1270+
vRes = vCur;
1271+
for (size_t i = 0; i < nrArgs; ++i) {
1272+
auto fun2 = allocValue();
1273+
*fun2 = vRes;
1274+
vRes.mkPrimOpApp(fun2, args[i]);
1275+
}
1276+
};
13001277

1301-
forceValue(fun, pos);
1278+
while (nrArgs > 0) {
13021279

1303-
if (fun.isPrimOp() || fun.isPrimOpApp()) {
1304-
callPrimOp(fun, arg, v, pos);
1305-
return;
1306-
}
1280+
if (vCur.isLambda()) {
13071281

1308-
if (fun.type() == nAttrs) {
1309-
auto found = fun.attrs->find(sFunctor);
1310-
if (found != fun.attrs->end()) {
1311-
/* fun may be allocated on the stack of the calling function,
1312-
* but for functors we may keep a reference, so heap-allocate
1313-
* a copy and use that instead.
1314-
*/
1315-
auto & fun2 = *allocValue();
1316-
fun2 = fun;
1317-
/* !!! Should we use the attr pos here? */
1318-
Value v2;
1319-
callFunction(*found->value, fun2, v2, pos);
1320-
return callFunction(v2, arg, v, pos);
1321-
}
1322-
}
1282+
ExprLambda & lambda(*vCur.lambda.fun);
13231283

1324-
if (!fun.isLambda())
1325-
throwTypeError(pos, "attempt to call something which is not a function but %1%", fun);
1284+
auto size =
1285+
(lambda.arg.empty() ? 0 : 1) +
1286+
(lambda.hasFormals() ? lambda.formals->formals.size() : 0);
1287+
Env & env2(allocEnv(size));
1288+
env2.up = vCur.lambda.env;
13261289

1327-
ExprLambda & lambda(*fun.lambda.fun);
1290+
Displacement displ = 0;
13281291

1329-
auto size =
1330-
(lambda.arg.empty() ? 0 : 1) +
1331-
(lambda.hasFormals() ? lambda.formals->formals.size() : 0);
1332-
Env & env2(allocEnv(size));
1333-
env2.up = fun.lambda.env;
1292+
if (!lambda.hasFormals())
1293+
env2.values[displ++] = args[0];
13341294

1335-
size_t displ = 0;
1295+
else {
1296+
forceAttrs(*args[0], pos);
13361297

1337-
if (!lambda.hasFormals())
1338-
env2.values[displ++] = &arg;
1298+
if (!lambda.arg.empty())
1299+
env2.values[displ++] = args[0];
13391300

1340-
else {
1341-
forceAttrs(arg, pos);
1342-
1343-
if (!lambda.arg.empty())
1344-
env2.values[displ++] = &arg;
1345-
1346-
/* For each formal argument, get the actual argument. If
1347-
there is no matching actual argument but the formal
1348-
argument has a default, use the default. */
1349-
size_t attrsUsed = 0;
1350-
for (auto & i : lambda.formals->formals) {
1351-
Bindings::iterator j = arg.attrs->find(i.name);
1352-
if (j == arg.attrs->end()) {
1353-
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
1354-
lambda, i.name);
1355-
env2.values[displ++] = i.def->maybeThunk(*this, env2);
1301+
/* For each formal argument, get the actual argument. If
1302+
there is no matching actual argument but the formal
1303+
argument has a default, use the default. */
1304+
size_t attrsUsed = 0;
1305+
for (auto & i : lambda.formals->formals) {
1306+
auto j = args[0]->attrs->get(i.name);
1307+
if (!j) {
1308+
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
1309+
lambda, i.name);
1310+
env2.values[displ++] = i.def->maybeThunk(*this, env2);
1311+
} else {
1312+
attrsUsed++;
1313+
env2.values[displ++] = j->value;
1314+
}
1315+
}
1316+
1317+
/* Check that each actual argument is listed as a formal
1318+
argument (unless the attribute match specifies a `...'). */
1319+
if (!lambda.formals->ellipsis && attrsUsed != args[0]->attrs->size()) {
1320+
/* Nope, so show the first unexpected argument to the
1321+
user. */
1322+
for (auto & i : *args[0]->attrs)
1323+
if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end())
1324+
throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name);
1325+
abort(); // can't happen
1326+
}
1327+
}
1328+
1329+
nrFunctionCalls++;
1330+
if (countCalls) incrFunctionCall(&lambda);
1331+
1332+
/* Evaluate the body. */
1333+
try {
1334+
lambda.body->eval(*this, env2, vCur);
1335+
} catch (Error & e) {
1336+
if (loggerSettings.showTrace.get()) {
1337+
addErrorTrace(e, lambda.pos, "while evaluating %s",
1338+
(lambda.name.set()
1339+
? "'" + (string) lambda.name + "'"
1340+
: "anonymous lambda"));
1341+
addErrorTrace(e, pos, "from call site%s", "");
1342+
}
1343+
throw;
1344+
}
1345+
1346+
nrArgs--;
1347+
args += 1;
1348+
}
1349+
1350+
else if (vCur.isPrimOp()) {
1351+
1352+
size_t argsLeft = vCur.primOp->arity;
1353+
1354+
if (nrArgs < argsLeft) {
1355+
/* We don't have enough arguments, so create a tPrimOpApp chain. */
1356+
makeAppChain();
1357+
return;
1358+
} else {
1359+
/* We have all the arguments, so call the primop. */
1360+
nrPrimOpCalls++;
1361+
if (countCalls) primOpCalls[vCur.primOp->name]++;
1362+
vCur.primOp->fun(*this, pos, args, vCur);
1363+
1364+
nrArgs -= argsLeft;
1365+
args += argsLeft;
1366+
}
1367+
}
1368+
1369+
else if (vCur.isPrimOpApp()) {
1370+
/* Figure out the number of arguments still needed. */
1371+
size_t argsDone = 0;
1372+
Value * primOp = &vCur;
1373+
while (primOp->isPrimOpApp()) {
1374+
argsDone++;
1375+
primOp = primOp->primOpApp.left;
1376+
}
1377+
assert(primOp->isPrimOp());
1378+
auto arity = primOp->primOp->arity;
1379+
auto argsLeft = arity - argsDone;
1380+
1381+
if (nrArgs < argsLeft) {
1382+
/* We still don't have enough arguments, so extend the tPrimOpApp chain. */
1383+
makeAppChain();
1384+
return;
13561385
} else {
1357-
attrsUsed++;
1358-
env2.values[displ++] = j->value;
1386+
/* We have all the arguments, so call the primop with
1387+
the previous and new arguments. */
1388+
1389+
Value * vArgs[arity];
1390+
auto n = argsDone;
1391+
for (Value * arg = &vCur; arg->isPrimOpApp(); arg = arg->primOpApp.left)
1392+
vArgs[--n] = arg->primOpApp.right;
1393+
1394+
for (size_t i = 0; i < argsLeft; ++i)
1395+
vArgs[argsDone + i] = args[i];
1396+
1397+
nrPrimOpCalls++;
1398+
if (countCalls) primOpCalls[primOp->primOp->name]++;
1399+
primOp->primOp->fun(*this, pos, vArgs, vCur);
1400+
1401+
nrArgs -= argsLeft;
1402+
args += argsLeft;
13591403
}
13601404
}
13611405

1362-
/* Check that each actual argument is listed as a formal
1363-
argument (unless the attribute match specifies a `...'). */
1364-
if (!lambda.formals->ellipsis && attrsUsed != arg.attrs->size()) {
1365-
/* Nope, so show the first unexpected argument to the
1366-
user. */
1367-
for (auto & i : *arg.attrs)
1368-
if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end())
1369-
throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name);
1370-
abort(); // can't happen
1406+
else if (vCur.type() == nAttrs) {
1407+
if (auto functor = vCur.attrs->get(sFunctor)) {
1408+
/* 'vCur" may be allocated on the stack of the calling
1409+
function, but for functors we may keep a reference,
1410+
so heap-allocate a copy and use that instead. */
1411+
Value * args2[] = {allocValue()};
1412+
*args2[0] = vCur;
1413+
/* !!! Should we use the attr pos here? */
1414+
callFunction(*functor->value, 1, args2, vCur, pos);
1415+
}
13711416
}
1417+
1418+
else
1419+
throwTypeError(pos, "attempt to call something which is not a function but %1%", vCur);
13721420
}
13731421

1374-
nrFunctionCalls++;
1375-
if (countCalls) incrFunctionCall(&lambda);
1422+
vRes = vCur;
1423+
}
13761424

1377-
/* Evaluate the body. This is conditional on showTrace, because
1378-
catching exceptions makes this function not tail-recursive. */
1379-
if (loggerSettings.showTrace.get())
1380-
try {
1381-
lambda.body->eval(*this, env2, v);
1382-
} catch (Error & e) {
1383-
addErrorTrace(e, lambda.pos, "while evaluating %s",
1384-
(lambda.name.set()
1385-
? "'" + (string) lambda.name + "'"
1386-
: "anonymous lambda"));
1387-
addErrorTrace(e, pos, "from call site%s", "");
1388-
throw;
1389-
}
1390-
else
1391-
fun.lambda.fun->body->eval(*this, env2, v);
1425+
1426+
void ExprCall::eval(EvalState & state, Env & env, Value & v)
1427+
{
1428+
Value vFun;
1429+
fun->eval(state, env, vFun);
1430+
1431+
Value * vArgs[args.size()];
1432+
for (size_t i = 0; i < args.size(); ++i)
1433+
vArgs[i] = args[i]->maybeThunk(state, env);
1434+
1435+
state.callFunction(vFun, args.size(), vArgs, v, pos);
13921436
}
13931437

13941438

src/libexpr/eval.hh

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,8 @@ private:
277277

278278
Value * addConstant(const string & name, Value & v);
279279

280+
void addConstant(const string & name, Value * v);
281+
280282
Value * addPrimOp(const string & name,
281283
size_t arity, PrimOpFun primOp);
282284

@@ -316,8 +318,14 @@ public:
316318

317319
bool isFunctor(Value & fun);
318320

319-
void callFunction(Value & fun, Value & arg, Value & v, const Pos & pos);
320-
void callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos);
321+
// FIXME: use std::span
322+
void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos);
323+
324+
void callFunction(Value & fun, Value & arg, Value & vRes, const Pos & pos)
325+
{
326+
Value * args[] = {&arg};
327+
callFunction(fun, 1, args, vRes, pos);
328+
}
321329

322330
/* Automatically call a function for which each argument has a
323331
default value or has a binding in the `args' map. */

0 commit comments

Comments
 (0)