Commit 19360fa2 authored by Kevin Modzelewski's avatar Kevin Modzelewski

Split arg-to-param matching into a separate function

Could be faster with templates but seems roughly even for now.
parent 5a81a329
...@@ -752,7 +752,7 @@ Value ASTInterpreter::visit_stmt(AST_stmt* node) { ...@@ -752,7 +752,7 @@ Value ASTInterpreter::visit_stmt(AST_stmt* node) {
#endif #endif
if (0) { if (0) {
printf("%20s % 2d ", source_info->getName().c_str(), current_block->idx); printf("%20s % 2d ", source_info->getName().data(), current_block->idx);
print_ast(node); print_ast(node);
printf("\n"); printf("\n");
} }
......
...@@ -87,7 +87,7 @@ InternedStringPool& SourceInfo::getInternedStrings() { ...@@ -87,7 +87,7 @@ InternedStringPool& SourceInfo::getInternedStrings() {
return scoping->getInternedStrings(); return scoping->getInternedStrings();
} }
const std::string SourceInfo::getName() { llvm::StringRef SourceInfo::getName() {
assert(ast); assert(ast);
switch (ast->type) { switch (ast->type) {
case AST_TYPE::ClassDef: case AST_TYPE::ClassDef:
......
...@@ -2595,7 +2595,7 @@ CFG* computeCFG(SourceInfo* source, std::vector<AST_stmt*> body) { ...@@ -2595,7 +2595,7 @@ CFG* computeCFG(SourceInfo* source, std::vector<AST_stmt*> body) {
if (b->predecessors.size() == 0) { if (b->predecessors.size() == 0) {
if (b != rtn->getStartingBlock()) { if (b != rtn->getStartingBlock()) {
rtn->print(); rtn->print();
printf("%s\n", source->getName().c_str()); printf("%s\n", source->getName().data());
} }
ASSERT(b == rtn->getStartingBlock(), "%d", b->idx); ASSERT(b == rtn->getStartingBlock(), "%d", b->idx);
} }
......
...@@ -300,7 +300,7 @@ public: ...@@ -300,7 +300,7 @@ public:
// body and we have to create one. Ideally, we'd be able to avoid the space duplication for non-lambdas. // body and we have to create one. Ideally, we'd be able to avoid the space duplication for non-lambdas.
const std::vector<AST_stmt*> body; const std::vector<AST_stmt*> body;
const std::string getName(); llvm::StringRef getName();
InternedString mangleName(InternedString id); InternedString mangleName(InternedString id);
Box* getDocString(); Box* getDocString();
......
...@@ -2583,7 +2583,7 @@ extern "C" void dumpEx(void* p, int levels) { ...@@ -2583,7 +2583,7 @@ extern "C" void dumpEx(void* p, int levels) {
CLFunction* cl = f->f; CLFunction* cl = f->f;
if (cl->source) { if (cl->source) {
printf("User-defined function '%s'\n", cl->source->getName().c_str()); printf("User-defined function '%s'\n", cl->source->getName().data());
} else { } else {
printf("A builtin function\n"); printf("A builtin function\n");
} }
...@@ -2962,13 +2962,14 @@ static CompiledFunction* pickVersion(CLFunction* f, int num_output_args, Box* oa ...@@ -2962,13 +2962,14 @@ static CompiledFunction* pickVersion(CLFunction* f, int num_output_args, Box* oa
return compileFunction(f, spec, new_effort, NULL); return compileFunction(f, spec, new_effort, NULL);
} }
static std::string getFunctionName(CLFunction* f) { static llvm::StringRef getFunctionName(CLFunction* f) {
if (f->source) if (f->source)
return f->source->getName(); return f->source->getName();
else if (f->versions.size()) { else if (f->versions.size()) {
std::ostringstream oss; return "<builtin function>";
oss << "<function at " << f->versions[0]->code << ">"; // std::ostringstream oss;
return oss.str(); // oss << "<function at " << f->versions[0]->code << ">";
// return oss.str();
} }
return "<unknown function>"; return "<unknown function>";
} }
...@@ -2979,7 +2980,7 @@ enum class KeywordDest { ...@@ -2979,7 +2980,7 @@ enum class KeywordDest {
}; };
static KeywordDest placeKeyword(const ParamNames* param_names, llvm::SmallVector<bool, 8>& params_filled, static KeywordDest placeKeyword(const ParamNames* param_names, llvm::SmallVector<bool, 8>& params_filled,
BoxedString* kw_name, Box* kw_val, Box*& oarg1, Box*& oarg2, Box*& oarg3, Box** oargs, BoxedString* kw_name, Box* kw_val, Box*& oarg1, Box*& oarg2, Box*& oarg3, Box** oargs,
BoxedDict* okwargs, CLFunction* cl) { BoxedDict* okwargs, const char* func_name) {
assert(kw_val); assert(kw_val);
assert(gc::isValidGCObject(kw_val)); assert(gc::isValidGCObject(kw_val));
assert(kw_name); assert(kw_name);
...@@ -2988,8 +2989,8 @@ static KeywordDest placeKeyword(const ParamNames* param_names, llvm::SmallVector ...@@ -2988,8 +2989,8 @@ static KeywordDest placeKeyword(const ParamNames* param_names, llvm::SmallVector
for (int j = 0; j < param_names->args.size(); j++) { for (int j = 0; j < param_names->args.size(); j++) {
if (param_names->args[j] == kw_name->s() && kw_name->size() > 0) { if (param_names->args[j] == kw_name->s() && kw_name->size() > 0) {
if (params_filled[j]) { if (params_filled[j]) {
raiseExcHelper(TypeError, "%.200s() got multiple values for keyword argument '%s'", raiseExcHelper(TypeError, "%.200s() got multiple values for keyword argument '%s'", func_name,
getFunctionName(cl).c_str(), kw_name->c_str()); kw_name->c_str());
} }
getArg(j, oarg1, oarg2, oarg3, oargs) = kw_val; getArg(j, oarg1, oarg2, oarg3, oargs) = kw_val;
...@@ -3002,14 +3003,13 @@ static KeywordDest placeKeyword(const ParamNames* param_names, llvm::SmallVector ...@@ -3002,14 +3003,13 @@ static KeywordDest placeKeyword(const ParamNames* param_names, llvm::SmallVector
if (okwargs) { if (okwargs) {
Box*& v = okwargs->d[kw_name]; Box*& v = okwargs->d[kw_name];
if (v) { if (v) {
raiseExcHelper(TypeError, "%.200s() got multiple values for keyword argument '%s'", raiseExcHelper(TypeError, "%.200s() got multiple values for keyword argument '%s'", func_name,
getFunctionName(cl).c_str(), kw_name->c_str()); kw_name->c_str());
} }
v = kw_val; v = kw_val;
return KeywordDest::KWARGS; return KeywordDest::KWARGS;
} else { } else {
raiseExcHelper(TypeError, "%.200s() got an unexpected keyword argument '%s'", getFunctionName(cl).c_str(), raiseExcHelper(TypeError, "%.200s() got an unexpected keyword argument '%s'", func_name, kw_name->c_str());
kw_name->c_str());
} }
} }
...@@ -3020,15 +3020,17 @@ static Box* _callFuncHelper(BoxedFunctionBase* func, ArgPassSpec argspec, Box* a ...@@ -3020,15 +3020,17 @@ static Box* _callFuncHelper(BoxedFunctionBase* func, ArgPassSpec argspec, Box* a
return callFunc(func, NULL, argspec, arg1, arg2, arg3, args, keyword_names); return callFunc(func, NULL, argspec, arg1, arg2, arg3, args, keyword_names);
} }
static StatCounter slowpath_callfunc("slowpath_callfunc"); typedef std::function<Box*(int, int, RewriterVar*&)> GetDefaultFunc;
static StatCounter slowpath_callfunc_slowpath("slowpath_callfunc_slowpath");
Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpec argspec, Box* arg1, Box* arg2,
Box* arg3, Box** args, const std::vector<BoxedString*>* keyword_names) {
#if STAT_TIMERS
StatTimer::assertActive();
STAT_TIMER(t0, "us_timer_slowpath_callFunc", 0);
#endif
// Passes the output arguments through oarg. Passes the rewrite success by setting rewrite_success.
// Directly modifies rewrite_args args in place, but only if rewrite_success got set.
// oargs needs to be pre-allocated by the caller, since it's assumed that they will want to use alloca.
// TODO Fix this function's signature. should we pass back out through args? the common case is that they
// match anyway.
void rearrangeArguments(ParamReceiveSpec paramspec, const ParamNames* param_names, const char* func_name,
Box** defaults, CallRewriteArgs* rewrite_args, bool& rewrite_success, ArgPassSpec argspec,
Box* arg1, Box* arg2, Box* arg3, Box** args, const std::vector<BoxedString*>* keyword_names,
Box*& oarg1, Box*& oarg2, Box*& oarg3, Box** oargs) {
/* /*
* Procedure: * Procedure:
* - First match up positional arguments; any extra go to varargs. error if too many. * - First match up positional arguments; any extra go to varargs. error if too many.
...@@ -3037,14 +3039,7 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe ...@@ -3037,14 +3039,7 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe
* - error about missing parameters * - error about missing parameters
*/ */
BoxedClosure* closure = func->closure; int num_output_args = paramspec.totalReceived();
CLFunction* f = func->f;
ParamReceiveSpec paramspec = f->paramspec;
slowpath_callfunc.log();
int num_output_args = f->numReceivedArgs();
int num_passed_args = argspec.totalPassed(); int num_passed_args = argspec.totalPassed();
if (num_passed_args >= 1) if (num_passed_args >= 1)
...@@ -3057,126 +3052,78 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe ...@@ -3057,126 +3052,78 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe
assert(gc::isValidGCObject(args[i - 3]) || args[i - 3] == NULL); assert(gc::isValidGCObject(args[i - 3]) || args[i - 3] == NULL);
} }
// TODO Should we guard on the CLFunction or the BoxedFunctionBase? assert((defaults != NULL) == (paramspec.num_defaults != 0));
// A single CLFunction could end up forming multiple BoxedFunctionBases, and we
// could emit assembly that handles any of them. But doing this involves some
// extra indirection, and it's not clear if that's worth it, since it seems like
// the common case will be functions only ever getting a single set of default arguments.
bool guard_clfunc = false;
assert(!guard_clfunc && "I think there are users that expect the boxedfunction to be guarded");
if (rewrite_args) { if (rewrite_args) {
rewrite_success = false; // default case
assert(rewrite_args->args_guarded && "need to guard args here"); assert(rewrite_args->args_guarded && "need to guard args here");
assert(rewrite_args->func_guarded && "this is the callers responsibility");
if (!rewrite_args->func_guarded) {
if (guard_clfunc) {
rewrite_args->obj->addAttrGuard(offsetof(BoxedFunctionBase, f), (intptr_t)f);
} else {
rewrite_args->obj->addGuard((intptr_t)func);
}
rewrite_args->rewriter->addDependenceOn(func->dependent_ics);
}
} }
// Fast path: if it's a simple-enough call, we don't have to do anything special. On a simple // Fast path: if it's a simple-enough call, we don't have to do anything special. On a simple
// django-admin test this covers something like 93% of all calls to callFunc. // django-admin test this covers something like 93% of all calls to callFunc.
if (!f->isGenerator()) { if (argspec.num_keywords == 0 && argspec.has_starargs == paramspec.takes_varargs && !argspec.has_kwargs
if (argspec.num_keywords == 0 && argspec.has_starargs == paramspec.takes_varargs && !argspec.has_kwargs && !paramspec.takes_kwargs && argspec.num_args == paramspec.num_args) {
&& !paramspec.takes_kwargs && argspec.num_args == paramspec.num_args) { assert(num_output_args == num_passed_args);
// If the caller passed starargs, we can only pass those directly to the callee if it's a tuple,
// since otherwise modifications by the callee would be visible to the caller (hence why varargs // If the caller passed starargs, we can only pass those directly to the callee if it's a tuple,
// received by the caller are always tuples). // since otherwise modifications by the callee would be visible to the caller (hence why varargs
// This is why we can't pass kwargs here. // received by the caller are always tuples).
if (argspec.has_starargs) { // This is why we can't pass kwargs here.
Box* given_varargs = getArg(argspec.num_args + argspec.num_keywords, arg1, arg2, arg3, args); if (argspec.has_starargs) {
if (given_varargs->cls == tuple_cls) { Box* given_varargs = getArg(argspec.num_args + argspec.num_keywords, arg1, arg2, arg3, args);
if (rewrite_args) { if (given_varargs->cls == tuple_cls) {
getArg(argspec.num_args + argspec.num_keywords, rewrite_args) if (rewrite_args) {
->addAttrGuard(offsetof(Box, cls), (intptr_t)tuple_cls); getArg(argspec.num_args + argspec.num_keywords, rewrite_args)
} ->addAttrGuard(offsetof(Box, cls), (intptr_t)tuple_cls);
return callCLFunc(f, rewrite_args, argspec.num_args + argspec.has_starargs + argspec.has_kwargs,
closure, NULL, func->globals, arg1, arg2, arg3, args);
} }
} else { rewrite_success = true;
return callCLFunc(f, rewrite_args, argspec.num_args + argspec.has_starargs + argspec.has_kwargs, oarg1 = arg1;
closure, NULL, func->globals, arg1, arg2, arg3, args); oarg2 = arg2;
oarg3 = arg3;
if (num_output_args > 3)
memcpy(oargs, args, sizeof(Box*) * (num_output_args - 3));
return;
} }
} else {
rewrite_success = true;
oarg1 = arg1;
oarg2 = arg2;
oarg3 = arg3;
if (num_output_args > 3)
memcpy(oargs, args, sizeof(Box*) * (num_output_args - 3));
return;
} }
} }
slowpath_callfunc_slowpath.log();
if (argspec.has_starargs || argspec.has_kwargs || argspec.num_keywords || f->isGenerator()) {
// These are the cases that we won't be able to rewrite.
// So instead, just rewrite them to be a call to callFunc, which helps a little bit.
// TODO we should extract the rest of this function from the end of this block,
// put it in a different function, and have the rewrites target that.
// Note(kmod): I tried moving this section to runtimeCallInternal, ie to the place that calls static StatCounter slowpath_rearrangeargs_slowpath("slowpath_rearrangeargs_slowpath");
// callFunc. The thought was that this would let us apply this same optimization to other slowpath_rearrangeargs_slowpath.log();
// internal callables. It ended up hurting perf slightly; my theory is that it's because if
// an internal callable failed, it's better to call the non-internal version since it's lower
// overhead.
// To investigate the cases where we can't rewrite, enable this block. if (argspec.has_starargs || argspec.has_kwargs || argspec.num_keywords) {
// This also will also log the times that we call into callFunc directly rewrite_args = NULL;
// from a rewrite. }
#if 0
char buf[80];
snprintf(buf, sizeof(buf), "zzz_aborted_%d_args_%d_%d_%d_%d_params_%d_%d_%d_%d", f->isGenerator(),
argspec.num_args, argspec.num_keywords, argspec.has_starargs, argspec.has_kwargs, paramspec.num_args,
paramspec.num_defaults, paramspec.takes_varargs, paramspec.takes_kwargs);
uint64_t* counter = Stats::getStatCounter(buf);
Stats::log(counter);
#endif
if (rewrite_args) { if (paramspec.takes_varargs && argspec.num_args > paramspec.num_args + 3) {
Rewriter* rewriter = rewrite_args->rewriter; // We currently only handle up to 3 arguments into the varargs tuple
// rewriter->trap(); rewrite_args = NULL;
RewriterVar* args_array = rewriter->allocate(2); }
if (num_passed_args >= 4) {
RELEASE_ASSERT(rewrite_args->args, "");
args_array->setAttr(0, rewrite_args->args);
}
if (argspec.num_keywords)
args_array->setAttr(8, rewriter->loadConst((intptr_t)keyword_names));
else
args_array->setAttr(8, rewriter->loadConst(0));
RewriterVar::SmallVector arg_vec; // At this point we are not allowed to abort the rewrite any more, since we will start
arg_vec.push_back(rewrite_args->obj); // modifying rewrite_args.
arg_vec.push_back(rewriter->loadConst(argspec.asInt(), Location::forArg(1)));
if (num_passed_args >= 1)
arg_vec.push_back(rewrite_args->arg1);
else
arg_vec.push_back(rewriter->loadConst(0, Location::forArg(2)));
if (num_passed_args >= 2)
arg_vec.push_back(rewrite_args->arg2);
else
arg_vec.push_back(rewriter->loadConst(0, Location::forArg(3)));
if (num_passed_args >= 3)
arg_vec.push_back(rewrite_args->arg3);
else
arg_vec.push_back(rewriter->loadConst(0, Location::forArg(4)));
arg_vec.push_back(args_array);
for (auto v : arg_vec)
assert(v);
RewriterVar* r_rtn = rewriter->call(true, (void*)_callFuncHelper, arg_vec);
rewrite_args->out_success = true; if (rewrite_args)
rewrite_args->out_rtn = r_rtn; rewrite_success = true;
rewrite_args = NULL;
}
}
if (rewrite_args) { if (rewrite_args) {
// We might have trouble if we have more output args than input args, // We might have trouble if we have more output args than input args,
// such as if we need more space to pass defaults. // such as if we need more space to pass defaults.
if (num_output_args > 3 && num_output_args > argspec.totalPassed()) { if (num_output_args > 3 && num_output_args > num_passed_args) {
int arg_bytes_required = (num_output_args - 3) * sizeof(Box*); int arg_bytes_required = (num_output_args - 3) * sizeof(Box*);
RewriterVar* new_args = NULL; RewriterVar* new_args = NULL;
if (rewrite_args->args == NULL) {
// rewrite_args->args could be empty if there are not more than assert((rewrite_args->args == NULL) == (num_passed_args <= 3));
// 3 input args. if (num_passed_args <= 3) {
// we weren't passed args
new_args = rewrite_args->rewriter->allocate(num_output_args - 3); new_args = rewrite_args->rewriter->allocate(num_output_args - 3);
} else { } else {
new_args = rewrite_args->rewriter->allocateAndCopy(rewrite_args->args, num_output_args - 3); new_args = rewrite_args->rewriter->allocateAndCopy(rewrite_args->args, num_output_args - 3);
...@@ -3188,32 +3135,18 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe ...@@ -3188,32 +3135,18 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe
std::vector<Box*, StlCompatAllocator<Box*>> varargs; std::vector<Box*, StlCompatAllocator<Box*>> varargs;
if (argspec.has_starargs) { if (argspec.has_starargs) {
assert(!rewrite_args);
Box* given_varargs = getArg(argspec.num_args + argspec.num_keywords, arg1, arg2, arg3, args); Box* given_varargs = getArg(argspec.num_args + argspec.num_keywords, arg1, arg2, arg3, args);
for (Box* e : given_varargs->pyElements()) { for (Box* e : given_varargs->pyElements()) {
varargs.push_back(e); varargs.push_back(e);
} }
} }
// The "output" args that we will pass to the called function:
Box* oarg1 = NULL, * oarg2 = NULL, * oarg3 = NULL;
Box** oargs = NULL;
if (num_output_args > 3) {
int size = (num_output_args - 3) * sizeof(Box*);
oargs = (Box**)alloca(size);
#ifndef NDEBUG
memset(&oargs[0], 0, size);
#endif
}
//// ////
// First, match up positional parameters to positional/varargs: // First, match up positional parameters to positional/varargs:
int positional_to_positional = std::min(argspec.num_args, paramspec.num_args); int positional_to_positional = std::min(argspec.num_args, paramspec.num_args);
for (int i = 0; i < positional_to_positional; i++) { for (int i = 0; i < positional_to_positional; i++) {
getArg(i, oarg1, oarg2, oarg3, oargs) = getArg(i, arg1, arg2, arg3, args); getArg(i, oarg1, oarg2, oarg3, oargs) = getArg(i, arg1, arg2, arg3, args);
// we already moved the positional args into position
} }
int varargs_to_positional = std::min((int)varargs.size(), paramspec.num_args - positional_to_positional); int varargs_to_positional = std::min((int)varargs.size(), paramspec.num_args - positional_to_positional);
...@@ -3243,8 +3176,7 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe ...@@ -3243,8 +3176,7 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe
} }
} }
for (int i = varargs_to_positional; i < varargs.size(); i++) { for (int i = varargs_to_positional; i < varargs.size(); i++) {
rewrite_args = NULL; assert(!rewrite_args);
REWRITE_ABORTED("");
unused_positional.push_back(varargs[i]); unused_positional.push_back(varargs[i]);
} }
...@@ -3271,8 +3203,8 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe ...@@ -3271,8 +3203,8 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe
= rewrite_args->rewriter->call(false, (void*)BoxedTuple::create3, unused_positional_rvars[0], = rewrite_args->rewriter->call(false, (void*)BoxedTuple::create3, unused_positional_rvars[0],
unused_positional_rvars[1], unused_positional_rvars[2]); unused_positional_rvars[1], unused_positional_rvars[2]);
} else { } else {
varargs_val = NULL; // This is too late to abort the rewrite (we should have checked this earlier)
rewrite_args = NULL; abort();
} }
if (varargs_val) { if (varargs_val) {
...@@ -3290,9 +3222,8 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe ...@@ -3290,9 +3222,8 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe
Box* ovarargs = BoxedTuple::create(unused_positional.size(), &unused_positional[0]); Box* ovarargs = BoxedTuple::create(unused_positional.size(), &unused_positional[0]);
getArg(varargs_idx, oarg1, oarg2, oarg3, oargs) = ovarargs; getArg(varargs_idx, oarg1, oarg2, oarg3, oargs) = ovarargs;
} else if (unused_positional.size()) { } else if (unused_positional.size()) {
raiseExcHelper(TypeError, "%s() takes at most %d argument%s (%d given)", getFunctionName(f).c_str(), raiseExcHelper(TypeError, "%s() takes at most %d argument%s (%d given)", func_name, paramspec.num_args,
paramspec.num_args, (paramspec.num_args == 1 ? "" : "s"), (paramspec.num_args == 1 ? "" : "s"), argspec.num_args + argspec.num_keywords + varargs.size());
argspec.num_args + argspec.num_keywords + varargs.size());
} }
//// ////
...@@ -3318,9 +3249,8 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe ...@@ -3318,9 +3249,8 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe
getArg(kwargs_idx, oarg1, oarg2, oarg3, oargs) = okwargs; getArg(kwargs_idx, oarg1, oarg2, oarg3, oargs) = okwargs;
} }
const ParamNames* param_names = &f->param_names;
if ((!param_names || !param_names->takes_param_names) && argspec.num_keywords && !paramspec.takes_kwargs) { if ((!param_names || !param_names->takes_param_names) && argspec.num_keywords && !paramspec.takes_kwargs) {
raiseExcHelper(TypeError, "%s() doesn't take keyword arguments", getFunctionName(f).c_str()); raiseExcHelper(TypeError, "%s() doesn't take keyword arguments", func_name);
} }
if (argspec.num_keywords) if (argspec.num_keywords)
...@@ -3334,14 +3264,14 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe ...@@ -3334,14 +3264,14 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe
if (!param_names || !param_names->takes_param_names) { if (!param_names || !param_names->takes_param_names) {
assert(okwargs); assert(okwargs);
rewrite_args = NULL; // would need to add it to r_kwargs assert(!rewrite_args); // would need to add it to r_kwargs
okwargs->d[(*keyword_names)[i]] = kw_val; okwargs->d[(*keyword_names)[i]] = kw_val;
continue; continue;
} }
auto dest = placeKeyword(param_names, params_filled, (*keyword_names)[i], kw_val, oarg1, oarg2, oarg3, oargs, auto dest = placeKeyword(param_names, params_filled, (*keyword_names)[i], kw_val, oarg1, oarg2, oarg3, oargs,
okwargs, f); okwargs, func_name);
rewrite_args = NULL; assert(!rewrite_args);
} }
if (argspec.has_kwargs) { if (argspec.has_kwargs) {
...@@ -3362,24 +3292,24 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe ...@@ -3362,24 +3292,24 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe
auto k = coerceUnicodeToStr(p.first); auto k = coerceUnicodeToStr(p.first);
if (k->cls != str_cls) if (k->cls != str_cls)
raiseExcHelper(TypeError, "%s() keywords must be strings", getFunctionName(f).c_str()); raiseExcHelper(TypeError, "%s() keywords must be strings", func_name);
BoxedString* s = static_cast<BoxedString*>(k); BoxedString* s = static_cast<BoxedString*>(k);
if (param_names && param_names->takes_param_names) { if (param_names && param_names->takes_param_names) {
assert(!rewrite_args && "would need to make sure that this didn't need to go into r_kwargs"); assert(!rewrite_args && "would need to make sure that this didn't need to go into r_kwargs");
placeKeyword(param_names, params_filled, s, p.second, oarg1, oarg2, oarg3, oargs, okwargs, f); placeKeyword(param_names, params_filled, s, p.second, oarg1, oarg2, oarg3, oargs, okwargs, func_name);
} else { } else {
assert(!rewrite_args && "would need to make sure that this didn't need to go into r_kwargs"); assert(!rewrite_args && "would need to make sure that this didn't need to go into r_kwargs");
assert(okwargs); assert(okwargs);
Box*& v = okwargs->d[p.first]; Box*& v = okwargs->d[p.first];
if (v) { if (v) {
raiseExcHelper(TypeError, "%s() got multiple values for keyword argument '%s'", raiseExcHelper(TypeError, "%s() got multiple values for keyword argument '%s'", func_name,
getFunctionName(f).c_str(), s->data()); s->data());
} }
v = p.second; v = p.second;
rewrite_args = NULL; assert(!rewrite_args);
} }
} }
} }
...@@ -3390,64 +3320,152 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe ...@@ -3390,64 +3320,152 @@ Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpe
if (params_filled[i]) if (params_filled[i])
continue; continue;
// TODO not right error message // TODO not right error message
raiseExcHelper(TypeError, "%s() did not get a value for positional argument %d", getFunctionName(f).c_str(), i); raiseExcHelper(TypeError, "%s() did not get a value for positional argument %d", func_name, i);
}
for (int arg_idx = paramspec.num_args - paramspec.num_defaults; arg_idx < paramspec.num_args; arg_idx++) {
if (params_filled[arg_idx])
continue;
int default_idx = arg_idx + paramspec.num_defaults - paramspec.num_args;
Box* default_obj = defaults[default_idx];
if (rewrite_args) {
if (arg_idx == 0)
rewrite_args->arg1 = rewrite_args->rewriter->loadConst((intptr_t)default_obj, Location::forArg(0));
else if (arg_idx == 1)
rewrite_args->arg2 = rewrite_args->rewriter->loadConst((intptr_t)default_obj, Location::forArg(1));
else if (arg_idx == 2)
rewrite_args->arg3 = rewrite_args->rewriter->loadConst((intptr_t)default_obj, Location::forArg(2));
else
rewrite_args->args->setAttr((arg_idx - 3) * sizeof(Box*),
rewrite_args->rewriter->loadConst((intptr_t)default_obj));
}
getArg(arg_idx, oarg1, oarg2, oarg3, oargs) = default_obj;
} }
}
RewriterVar* r_defaults_array = NULL; static StatCounter slowpath_callfunc("slowpath_callfunc");
if (guard_clfunc) { Box* callFunc(BoxedFunctionBase* func, CallRewriteArgs* rewrite_args, ArgPassSpec argspec, Box* arg1, Box* arg2,
r_defaults_array = rewrite_args->obj->getAttr(offsetof(BoxedFunctionBase, defaults), Location::any()); Box* arg3, Box** args, const std::vector<BoxedString*>* keyword_names) {
#if STAT_TIMERS
StatTimer::assertActive();
STAT_TIMER(t0, "us_timer_slowpath_callFunc", 0);
#endif
slowpath_callfunc.log();
CLFunction* f = func->f;
ParamReceiveSpec paramspec = f->paramspec;
if (rewrite_args) {
if (!rewrite_args->func_guarded) {
rewrite_args->obj->addGuard((intptr_t)func);
rewrite_args->rewriter->addDependenceOn(func->dependent_ics);
}
} }
for (int i = paramspec.num_args - paramspec.num_defaults; i < paramspec.num_args; i++) { Box* oarg1, *oarg2, *oarg3, **oargs;
if (params_filled[i]) bool rewrite_success = false;
continue;
int num_output_args = paramspec.totalReceived();
int num_passed_args = argspec.totalPassed();
if (num_output_args > 3) {
int size = (num_output_args - 3) * sizeof(Box*);
oargs = (Box**)alloca(size);
#ifndef NDEBUG
memset(&oargs[0], 0, size);
#endif
} else {
// It won't get looked at, but the compiler wants this:
oargs = NULL;
}
int default_idx = i + paramspec.num_defaults - paramspec.num_args; rearrangeArguments(paramspec, &f->param_names, getFunctionName(f).data(),
Box* default_obj = func->defaults->elts[default_idx]; paramspec.num_defaults ? func->defaults->elts : NULL, rewrite_args, rewrite_success, argspec,
arg1, arg2, arg3, args, keyword_names, oarg1, oarg2, oarg3, oargs);
#if 0
for (int i = 0; i < num_output_args; i++) {
auto arg = getArg(i, oarg1, oarg2, oarg3, oargs);
RELEASE_ASSERT(!arg || gc::isValidGCObject(arg), "%p", arg);
}
#endif
if (rewrite_args && !rewrite_success) {
// These are the cases that we weren't able to rewrite.
// So instead, just rewrite them to be a call to callFunc, which helps a little bit.
// TODO we should extract the rest of this function from the end of this block,
// put it in a different function, and have the rewrites target that.
// Note(kmod): I tried moving this section to runtimeCallInternal, ie to the place that calls
// callFunc. The thought was that this would let us apply this same optimization to other
// internal callables. It ended up hurting perf slightly; my theory is that it's because if
// an internal callable failed, it's better to call the non-internal version since it's lower
// overhead.
// To investigate the cases where we can't rewrite, enable this block.
// This also will also log the times that we call into callFunc directly
// from a rewrite.
#if 0
char buf[80];
snprintf(buf, sizeof(buf), "zzz_aborted_%d_args_%d_%d_%d_%d_params_%d_%d_%d_%d", f->isGenerator(),
argspec.num_args, argspec.num_keywords, argspec.has_starargs, argspec.has_kwargs, paramspec.num_args,
paramspec.num_defaults, paramspec.takes_varargs, paramspec.takes_kwargs);
uint64_t* counter = Stats::getStatCounter(buf);
Stats::log(counter);
#endif
if (rewrite_args) { if (rewrite_args) {
int offset = offsetof(std::remove_pointer<decltype(BoxedFunctionBase::defaults)>::type, elts) Rewriter* rewriter = rewrite_args->rewriter;
+ sizeof(Box*) * default_idx; // rewriter->trap();
if (guard_clfunc) { RewriterVar* args_array = rewriter->allocate(2);
// If we just guarded on the CLFunction, then we have to emit assembly if (num_passed_args >= 4) {
// to fetch the values from the defaults array: RELEASE_ASSERT(rewrite_args->args, "");
if (i < 3) { args_array->setAttr(0, rewrite_args->args);
RewriterVar* r_default = r_defaults_array->getAttr(offset, Location::forArg(i));
if (i == 0)
rewrite_args->arg1 = r_default;
if (i == 1)
rewrite_args->arg2 = r_default;
if (i == 2)
rewrite_args->arg3 = r_default;
} else {
RewriterVar* r_default = r_defaults_array->getAttr(offset, Location::any());
rewrite_args->args->setAttr((i - 3) * sizeof(Box*), r_default);
}
} else {
// If we guarded on the BoxedFunctionBase, which has a constant set of defaults,
// we can embed the default arguments directly into the instructions.
if (i < 3) {
RewriterVar* r_default = rewrite_args->rewriter->loadConst((intptr_t)default_obj, Location::any());
if (i == 0)
rewrite_args->arg1 = r_default;
if (i == 1)
rewrite_args->arg2 = r_default;
if (i == 2)
rewrite_args->arg3 = r_default;
} else {
RewriterVar* r_default = rewrite_args->rewriter->loadConst((intptr_t)default_obj, Location::any());
rewrite_args->args->setAttr((i - 3) * sizeof(Box*), r_default);
}
} }
} if (argspec.num_keywords)
args_array->setAttr(8, rewriter->loadConst((intptr_t)keyword_names));
else
args_array->setAttr(8, rewriter->loadConst(0));
getArg(i, oarg1, oarg2, oarg3, oargs) = default_obj; RewriterVar::SmallVector arg_vec;
arg_vec.push_back(rewrite_args->obj);
arg_vec.push_back(rewriter->loadConst(argspec.asInt(), Location::forArg(1)));
if (num_passed_args >= 1)
arg_vec.push_back(rewrite_args->arg1);
else
arg_vec.push_back(rewriter->loadConst(0, Location::forArg(2)));
if (num_passed_args >= 2)
arg_vec.push_back(rewrite_args->arg2);
else
arg_vec.push_back(rewriter->loadConst(0, Location::forArg(3)));
if (num_passed_args >= 3)
arg_vec.push_back(rewrite_args->arg3);
else
arg_vec.push_back(rewriter->loadConst(0, Location::forArg(4)));
arg_vec.push_back(args_array);
for (auto v : arg_vec)
assert(v);
RewriterVar* r_rtn = rewriter->call(true, (void*)_callFuncHelper, arg_vec);
rewrite_args->out_success = true;
rewrite_args->out_rtn = r_rtn;
rewrite_args = NULL;
}
} }
BoxedClosure* closure = func->closure;
// special handling for generators: // special handling for generators:
// the call to function containing a yield should just create a new generator object. // the call to function containing a yield should just create a new generator object.
Box* res; Box* res;
if (f->isGenerator()) { if (f->isGenerator()) {
// TODO: we might not have a lot to gain by rewriting into createGenerator, but we could at least
// rewrite up to the call to it:
res = createGenerator(func, oarg1, oarg2, oarg3, oargs); res = createGenerator(func, oarg1, oarg2, oarg3, oargs);
} else { } else {
res = callCLFunc(f, rewrite_args, num_output_args, closure, NULL, func->globals, oarg1, oarg2, oarg3, oargs); res = callCLFunc(f, rewrite_args, num_output_args, closure, NULL, func->globals, oarg1, oarg2, oarg3, oargs);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment