Skip to content

Commit f4b9906

Browse files
committed
add a secondary slowpath for callattr
1 parent 10734f5 commit f4b9906

File tree

9 files changed

+170
-67
lines changed

9 files changed

+170
-67
lines changed

src/asm_writing/assembler.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ int Assembler::getModeFromOffset(int offset) const {
155155
}
156156

157157
void Assembler::mov(Immediate val, Register dest, bool force_64bit_load) {
158-
force_64bit_load = force_64bit_load || !val.fitsInto32Bit();
158+
force_64bit_load = force_64bit_load || !val.fitsInto32BitSigned();
159159

160160
int rex = force_64bit_load ? REX_W : 0;
161161
int dest_idx = dest.regnum;

src/asm_writing/rewriter.cpp

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ void Rewriter::emitGuardJump(bool useJne) {
345345
}
346346
}
347347

348-
if (allArgsInPlace) {
348+
if (allArgsInPlace && !should_use_second_guard_destination) {
349349
if (useJne) {
350350
assembler->jne(assembler::JumpDestination::fromStart(rewrite->getSlotSize()));
351351
} else {
@@ -358,6 +358,9 @@ void Rewriter::emitGuardJump(bool useJne) {
358358
assembler->je(assembler::JumpDestination::fromStart((1 << 31) - 2));
359359
}
360360

361+
if (should_use_second_guard_destination) {
362+
assert(marked_inside_ic);
363+
}
361364
guard_infos.emplace_back(assembler->curInstPointer(), VarLocations(args));
362365
}
363366

@@ -366,15 +369,39 @@ void Rewriter::emitGuardJump(bool useJne) {
366369
}
367370
}
368371

369-
void Rewriter::guardCalls() {
372+
void Rewriter::guardCalls(int continue_offset) {
370373
for (GuardInfo& guard_info : guard_infos) {
374+
assembler->comment("path for guard fail");
375+
371376
uint8_t* dest_addr = assembler->curInstPointer();
372377
int offset = dest_addr - guard_info.guard_jmp_addr;
373378
*((int*)(guard_info.guard_jmp_addr - 4)) = offset;
374379

375380
guard_info.var_locations.arrangeAsArgs(this);
376381

377-
assembler->jmp(assembler::JumpDestination::fromStart(rewrite->getSlotSize()));
382+
if (!should_use_second_guard_destination) {
383+
assembler->jmp(assembler::JumpDestination::fromStart(rewrite->getSlotSize()));
384+
} else {
385+
assembler->comment("calling second slowpath");
386+
387+
// TODO Inefficient way to write a call
388+
assembler::Register r = allocReg(assembler::R11);
389+
assembler->mov(assembler::Immediate(second_slowpath), r);
390+
assembler->callq(r);
391+
392+
assembler->comment("mark inside ic");
393+
394+
uintptr_t counter_addr = (uintptr_t)(&picked_slot->num_inside);
395+
if (isLargeConstant(counter_addr)) {
396+
assembler::Register reg = assembler::R11;
397+
assembler->mov(assembler::Immediate(counter_addr), reg);
398+
assembler->decl(assembler::Indirect(reg, 0));
399+
} else {
400+
assembler->decl(assembler::Immediate(counter_addr));
401+
}
402+
403+
assembler->jmp(assembler::JumpDestination::fromStart(continue_offset));
404+
}
378405
}
379406
}
380407

@@ -1134,6 +1161,10 @@ void Rewriter::commit() {
11341161

11351162
// Now, start emitting assembly; check if we're dong guarding after each.
11361163
for (int i = 0; i < actions.size(); i++) {
1164+
if (second_slowpath && i == action_where_second_slowpath_starts) {
1165+
this->useSecondGuardDestination();
1166+
}
1167+
11371168
actions[i].action();
11381169

11391170
if (failed) {
@@ -1310,7 +1341,7 @@ bool Rewriter::finishAssembly(int continue_offset) {
13101341

13111342
assembler->jmp(assembler::JumpDestination::fromStart(continue_offset));
13121343

1313-
guardCalls();
1344+
guardCalls(continue_offset);
13141345

13151346
assembler->fillWithNops();
13161347

@@ -1763,6 +1794,9 @@ Rewriter::Rewriter(std::unique_ptr<ICSlotRewrite> rewrite, int num_args, const L
17631794
const_loader(this),
17641795
return_location(this->rewrite->returnRegister()),
17651796
failed(false),
1797+
second_slowpath(NULL),
1798+
action_where_second_slowpath_starts(-1),
1799+
should_use_second_guard_destination(false),
17661800
added_changing_action(false),
17671801
marked_inside_ic(false) {
17681802
initPhaseCollecting();
@@ -1775,7 +1809,6 @@ Rewriter::Rewriter(std::unique_ptr<ICSlotRewrite> rewrite, int num_args, const L
17751809
addLocationToVar(var, l);
17761810

17771811
var->is_arg = true;
1778-
var->arg_loc = l;
17791812

17801813
args.push_back(var);
17811814
}
@@ -2138,24 +2171,26 @@ void VarLocations::arrangeAsArgs(Rewriter* rewriter) {
21382171

21392172
for (int i = 0; i < vars.size(); i++) {
21402173
Location targetLoc = Location::forArg(i);
2141-
Location startLoc = Location::any();
2142-
assert(this->vars[i].second.size());
2174+
Location startLoc = Location::none();
21432175
for (Location l : this->vars[i].second) {
21442176
if (l == targetLoc) {
21452177
startLoc = l;
21462178
break;
21472179
} else if (l.type == Location::Register) {
21482180
startLoc = l;
21492181
} else {
2150-
if (startLoc == Location::any()) {
2182+
if (startLoc == Location::none()) {
21512183
startLoc = l;
21522184
}
21532185
}
21542186
}
2155-
assert(startLoc != Location::any());
21562187
locs.push_back(startLoc);
2157-
if (startLoc.type == Location::Register) {
2158-
argInReg[startLoc.asRegister().regnum] = i;
2188+
if (startLoc != Location::none()) {
2189+
if (startLoc.type == Location::Register) {
2190+
argInReg[startLoc.asRegister().regnum] = i;
2191+
}
2192+
} else {
2193+
assert(this->vars[i].first->is_constant);
21592194
}
21602195
}
21612196

@@ -2170,6 +2205,9 @@ void VarLocations::arrangeAsArgs(Rewriter* rewriter) {
21702205
};
21712206

21722207
for (int i = vars.size() - 1; i >= 0; i--) {
2208+
if (vars[i].first->is_constant)
2209+
continue;
2210+
21732211
Location curLoc = locs[i];
21742212
Location targetLoc = Location::forArg(i);
21752213
if (curLoc == targetLoc) continue;
@@ -2182,7 +2220,7 @@ void VarLocations::arrangeAsArgs(Rewriter* rewriter) {
21822220
assembler::Register r = getFreeReg();
21832221
assert(curLoc.type == Location::Scratch);
21842222
assembler->mov(assembler::Indirect(assembler::RSP, rewriter->rewrite->getScratchRspOffset() + curLoc.scratch_offset), r);
2185-
assembler->mov(r, assembler::Indirect(assembler::RSP, curLoc.stack_offset));
2223+
assembler->mov(r, assembler::Indirect(assembler::RSP, targetLoc.stack_offset));
21862224
locs[i] = targetLoc;
21872225
}
21882226
} else {
@@ -2193,17 +2231,32 @@ void VarLocations::arrangeAsArgs(Rewriter* rewriter) {
21932231
argInReg[targetLoc.asRegister().regnum] = -1;
21942232
locs[argInReg[r.regnum]] = r;
21952233
}
2234+
21962235
if (curLoc.type == Location::Register) {
21972236
assembler->mov(curLoc.asRegister(), targetLoc.asRegister());
21982237
argInReg[curLoc.asRegister().regnum] = -1;
21992238
} else {
22002239
assert(curLoc.type == Location::Scratch);
22012240
assembler->mov(assembler::Indirect(assembler::RSP, rewriter->rewrite->getScratchRspOffset() + curLoc.scratch_offset), targetLoc.asRegister());
22022241
}
2242+
22032243
locs[i] = targetLoc;
22042244
argInReg[targetLoc.asRegister().regnum] = i;
22052245
}
22062246
}
2247+
2248+
for (int i = vars.size() - 1; i >= 0; i--) {
2249+
if (vars[i].first->is_constant) {
2250+
Location targetLoc = Location::forArg(i);
2251+
if (targetLoc.type == Location::Register) {
2252+
assembler->mov(assembler::Immediate(vars[i].first->constant_value), targetLoc.asRegister());
2253+
} else {
2254+
// TODO inefficient
2255+
assembler->mov(assembler::Immediate(vars[i].first->constant_value), assembler::R11);
2256+
assembler->mov(assembler::R11, assembler::Indirect(assembler::RSP, targetLoc.stack_offset));
2257+
}
2258+
}
2259+
}
22072260
}
22082261

22092262
}

src/asm_writing/rewriter.h

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,6 @@ class RewriterVar {
267267
bool is_constant;
268268

269269
uint64_t constant_value;
270-
Location arg_loc;
271270
std::pair<int /*offset*/, int /*size*/> scratch_allocation;
272271

273272
llvm::SmallSet<std::tuple<int, uint64_t, bool>, 4> attr_guards; // used to detect duplicate guards
@@ -437,10 +436,42 @@ class Rewriter : public ICSlotRewrite::CommitHook {
437436
LocMap<RewriterVar*> vars_by_location;
438437
llvm::SmallVector<RewriterVar*, 8> args;
439438
llvm::SmallVector<RewriterVar*, 8> live_outs;
439+
llvm::SmallVector<RewriterVar*, 8> second_slowpath_args;
440440

441441
llvm::SmallVector<GuardInfo, 8> guard_infos;
442442
void emitGuardJump(bool useJne);
443-
void guardCalls();
443+
void guardCalls(int continue_offset);
444+
445+
void useSecondGuardDestination() {
446+
assertPhaseEmitting();
447+
448+
for (RewriterVar* v : args) {
449+
v->is_arg = false;
450+
}
451+
args = std::move(second_slowpath_args);
452+
for (int i = 0; i < args.size(); i++) {
453+
RewriterVar* v = args[i];
454+
v->is_arg = true;
455+
}
456+
457+
should_use_second_guard_destination = true;
458+
}
459+
460+
public:
461+
void addSecondSlowpath(void* new_slowpath, const llvm::SmallVector<RewriterVar*, 8>& args) {
462+
assertPhaseCollecting();
463+
added_changing_action = false;
464+
this->second_slowpath = new_slowpath;
465+
action_where_second_slowpath_starts = actions.size();
466+
second_slowpath_args = args;
467+
}
468+
bool hasAddedChangingAction() { return added_changing_action; }
469+
470+
protected:
471+
472+
void* second_slowpath;
473+
int action_where_second_slowpath_starts;
474+
bool should_use_second_guard_destination;
444475

445476
Rewriter(std::unique_ptr<ICSlotRewrite> rewrite, int num_args, LiveOutSet live_outs);
446477

@@ -458,7 +489,7 @@ class Rewriter : public ICSlotRewrite::CommitHook {
458489
failed = true;
459490
return;
460491
}
461-
for (RewriterVar* arg : args) {
492+
for (RewriterVar* arg : (second_slowpath ? second_slowpath_args : args)) {
462493
arg->uses.push_back(actions.size());
463494
}
464495
assert(!added_changing_action);

src/asm_writing/types.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ struct Immediate {
163163
uint32_t val_32bit = (uint32_t)val;
164164
return val_32bit == val;
165165
}
166+
167+
bool fitsInto32BitSigned() const {
168+
int64_t val = (int64_t) this->val;
169+
return val >= std::numeric_limits<int32_t>::min() && val <= std::numeric_limits<int32_t>::max();
170+
}
166171
};
167172

168173
struct JumpDestination {

src/runtime/objmodel.cpp

Lines changed: 19 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,11 +1357,6 @@ Box* dataDescriptorInstanceSpecialCases(GetattrRewriteArgs* rewrite_args, BoxedS
13571357
getTypeName(getset_descr));
13581358
}
13591359

1360-
// Abort because right now we can't call twice in a rewrite
1361-
if (for_call) {
1362-
rewrite_args = NULL;
1363-
}
1364-
13651360
if (rewrite_args) {
13661361
// hmm, maybe we should write assembly which can look up the function address and call any function
13671362
r_descr->addAttrGuard(offsetof(BoxedGetsetDescriptor, get), (intptr_t)getset_descr->get);
@@ -1669,21 +1664,6 @@ Box* getattrInternalGeneric(Box* obj, BoxedString* attr, GetattrRewriteArgs* rew
16691664

16701665
// Call __get__(descr, obj, obj->cls)
16711666
if (_set_) {
1672-
// Have to abort because we're about to call now, but there will be before more
1673-
// guards between this call and the next...
1674-
if (for_call) {
1675-
#if STAT_CALLATTR_DESCR_ABORTS
1676-
if (rewrite_args) {
1677-
std::string attr_name = "num_callattr_descr_abort";
1678-
Stats::log(Stats::getStatCounter(attr_name));
1679-
logByCurrentPythonLine(attr_name);
1680-
}
1681-
#endif
1682-
1683-
rewrite_args = NULL;
1684-
REWRITE_ABORTED("");
1685-
}
1686-
16871667
Box* res;
16881668
if (rewrite_args) {
16891669
CallRewriteArgs crewrite_args(rewrite_args->rewriter, r_get, rewrite_args->destination);
@@ -1809,20 +1789,6 @@ Box* getattrInternalGeneric(Box* obj, BoxedString* attr, GetattrRewriteArgs* rew
18091789
// We looked up __get__ above. If we found it, call it and return
18101790
// the result.
18111791
if (descr_get) {
1812-
// this could happen for the callattr path...
1813-
if (for_call) {
1814-
#if STAT_CALLATTR_DESCR_ABORTS
1815-
if (rewrite_args) {
1816-
std::string attr_name = "num_callattr_descr_abort";
1817-
Stats::log(Stats::getStatCounter(attr_name));
1818-
logByCurrentPythonLine(attr_name);
1819-
}
1820-
#endif
1821-
1822-
rewrite_args = NULL;
1823-
REWRITE_ABORTED("");
1824-
}
1825-
18261792
Box* res;
18271793
if (rewrite_args) {
18281794
assert(_get_);
@@ -2757,6 +2723,25 @@ Box* callattrInternal(Box* obj, BoxedString* attr, LookupScope scope, CallRewrit
27572723
rewrite_args->obj = r_val;
27582724
}
27592725

2726+
if (rewrite_args && rewrite_args->rewriter->hasAddedChangingAction()) {
2727+
// It's possible that `getattrInternalEx` did some mutation operation, like a runtimeCall
2728+
// (Could do that if it's a descriptor). Since we would have to make guards for the runtimeCall below,
2729+
// we would have to abort if we don't add this new slowpath.
2730+
RewriterVar* r_null = rewrite_args->rewriter->loadConst(0);
2731+
llvm::SmallVector<RewriterVar*, 8> args;
2732+
args.push_back(r_val);
2733+
args.push_back(r_null);
2734+
args.push_back(rewrite_args->rewriter->loadConst(argspec.asInt()));
2735+
// Pass r_null because we need to pass *something* as an arg
2736+
// TODO let the rewriter accept NULL as an argument to mean "doesn't matter what you pass"
2737+
args.push_back(rewrite_args->arg1 == NULL ? r_null : rewrite_args->arg1);
2738+
args.push_back(rewrite_args->arg2 == NULL ? r_null : rewrite_args->arg2);
2739+
args.push_back(rewrite_args->arg3 == NULL ? r_null : rewrite_args->arg3);
2740+
args.push_back(rewrite_args->args == NULL ? r_null : rewrite_args->args);
2741+
args.push_back(rewrite_args->rewriter->loadConst((intptr_t)(void*)keyword_names));
2742+
rewrite_args->rewriter->addSecondSlowpath((void*)(runtimeCallInternal<CXX>), args);
2743+
}
2744+
27602745
Box* rtn = runtimeCallInternal<S>(val, rewrite_args, argspec, arg1, arg2, arg3, args, keyword_names);
27612746
assert(rtn || (S == CAPI && PyErr_Occurred()));
27622747
return rtn;

test/tests/callattr_guards.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
def f(a, b):
2+
print 'f is called', a, b
3+
4+
def g(a, b, c=3):
5+
print 'g is called', a, b, c
6+
7+
counter = 0
8+
9+
class Desc(object):
10+
def __get__(self, obj, type):
11+
global counter
12+
counter += 1
13+
if counter <= 800:
14+
return f
15+
else:
16+
return g
17+
18+
class C(object):
19+
d = Desc()
20+
21+
def do():
22+
c = C()
23+
c.d('a', 'b')
24+
25+
for i in xrange(1000):
26+
do()

test/tests/callattr_ics.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# run_args: -n
2+
# statcheck: noninit_count('slowpath_callattr') <= 200
3+
4+
def f(a, b):
5+
print 'f is called', a, b
6+
7+
class Desc(object):
8+
def __get__(self, obj, type):
9+
return f
10+
11+
class C(object):
12+
d = Desc()
13+
14+
def do():
15+
c = C()
16+
c.d('a', 'b')
17+
18+
for i in xrange(1000):
19+
do()

0 commit comments

Comments
 (0)