Commit b594728e authored by Travis Hance's avatar Travis Hance

Support for __get__ and __set__ for descriptors

parent 7be0644b
......@@ -157,7 +157,7 @@ void RewriterVarUsage::addGuardNotEq(uint64_t val) {
assembler->je(assembler::JumpDestination::fromStart(rewriter->rewrite->getSlotSize()));
}
void RewriterVarUsage::addAttrGuard(int offset, uint64_t val) {
void RewriterVarUsage::addAttrGuard(int offset, uint64_t val, bool negate) {
assertValid();
Rewriter* rewriter = var->rewriter;
......@@ -167,13 +167,16 @@ void RewriterVarUsage::addAttrGuard(int offset, uint64_t val) {
assembler::Register this_reg = var->getInReg();
if (val < (-1L << 31) || val >= (1L << 31) - 1) {
assembler::Register reg = rewriter->allocReg(Location::any());
assembler::Register reg = rewriter->allocReg(Location::any(), /* otherThan */ this_reg);
assert(reg != this_reg);
assembler->mov(assembler::Immediate(val), reg);
assembler->cmp(assembler::Indirect(this_reg, offset), reg);
} else {
assembler->cmp(assembler::Indirect(this_reg, offset), assembler::Immediate(val));
}
if (negate)
assembler->je(assembler::JumpDestination::fromStart(rewriter->rewrite->getSlotSize()));
else
assembler->jne(assembler::JumpDestination::fromStart(rewriter->rewrite->getSlotSize()));
}
......@@ -772,8 +775,8 @@ RewriterVarUsage Rewriter::allocateAndCopy(RewriterVarUsage array_ptr, int n) {
std::pair<RewriterVarUsage, int> allocation = _allocate(n);
int offset = allocation.second;
assembler::Register tmp = allocReg(Location::any());
assembler::Register src_ptr = array_ptr.var->getInReg();
assembler::Register tmp = allocReg(Location::any(), /* otherThan */ src_ptr);
assert(tmp != src_ptr); // TODO how to ensure this?
for (int i = 0; i < n; i++) {
......@@ -872,14 +875,14 @@ void Rewriter::spillRegister(assembler::XMMRegister reg) {
removeLocationFromVar(var, reg);
}
assembler::Register Rewriter::allocReg(Location dest) {
assembler::Register Rewriter::allocReg(Location dest, Location otherThan) {
if (dest.type == Location::AnyReg) {
for (assembler::Register reg : allocatable_regs) {
if (vars_by_location.count(reg) == 0)
if (Location(reg) != otherThan && vars_by_location.count(reg) == 0)
return reg;
}
// TODO maybe should do some sort of round-robin or LRU eviction strategy?
return allocReg(assembler::R15);
return allocReg(otherThan == assembler::R15 ? assembler::R14 : assembler::R15);
} else if (dest.type == Location::Register) {
assembler::Register reg(dest.regnum);
......
......@@ -159,7 +159,7 @@ public:
void addGuard(uint64_t val);
void addGuardNotEq(uint64_t val);
void addAttrGuard(int offset, uint64_t val);
void addAttrGuard(int offset, uint64_t val, bool negate = false);
RewriterVarUsage getAttr(int offset, KillFlag kill, Location loc = Location::any());
void setAttr(int offset, RewriterVarUsage other);
RewriterVarUsage cmp(AST_TYPE::AST_TYPE cmp_type, RewriterVarUsage other, Location loc = Location::any());
......@@ -229,7 +229,8 @@ private:
void kill(RewriterVar* var);
// Allocates a register. dest must be of type Register or AnyReg
assembler::Register allocReg(Location dest);
// If otherThan is a register, guaranteed to not use that register.
assembler::Register allocReg(Location dest, Location otherThan = Location::any());
// Allocates an 8-byte region in the scratch space
Location allocScratch();
assembler::Indirect indirectFor(Location l);
......
......@@ -270,7 +270,7 @@ public:
llvm::Value* rtn;
if (do_patchpoint) {
PatchpointSetupInfo* pp
= patchpoints::createGenericPatchpoint(emitter.currentFunction(), info.getTypeRecorder(), true, 160);
= patchpoints::createGenericPatchpoint(emitter.currentFunction(), info.getTypeRecorder(), true, 256);
std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(var->getValue());
......
......@@ -120,23 +120,23 @@ PatchpointSetupInfo* createGenericPatchpoint(CompiledFunction* parent_cf, TypeRe
}
PatchpointSetupInfo* createGetattrPatchpoint(CompiledFunction* parent_cf, TypeRecorder* type_recorder) {
return PatchpointSetupInfo::initialize(true, 1, 196, parent_cf, Getattr, type_recorder);
return PatchpointSetupInfo::initialize(true, 1, 512, parent_cf, Getattr, type_recorder);
}
PatchpointSetupInfo* createGetitemPatchpoint(CompiledFunction* parent_cf, TypeRecorder* type_recorder) {
return PatchpointSetupInfo::initialize(true, 1, 128, parent_cf, Getitem, type_recorder);
return PatchpointSetupInfo::initialize(true, 1, 512, parent_cf, Getitem, type_recorder);
}
PatchpointSetupInfo* createSetitemPatchpoint(CompiledFunction* parent_cf, TypeRecorder* type_recorder) {
return PatchpointSetupInfo::initialize(true, 1, 144, parent_cf, Setitem, type_recorder);
return PatchpointSetupInfo::initialize(true, 1, 256, parent_cf, Setitem, type_recorder);
}
PatchpointSetupInfo* createDelitemPatchpoint(CompiledFunction* parent_cf, TypeRecorder* type_recorder) {
return PatchpointSetupInfo::initialize(false, 1, 144, parent_cf, Delitem, type_recorder);
return PatchpointSetupInfo::initialize(false, 1, 256, parent_cf, Delitem, type_recorder);
}
PatchpointSetupInfo* createSetattrPatchpoint(CompiledFunction* parent_cf, TypeRecorder* type_recorder) {
return PatchpointSetupInfo::initialize(false, 2, 128, parent_cf, Setattr, type_recorder);
return PatchpointSetupInfo::initialize(false, 2, 512, parent_cf, Setattr, type_recorder);
}
PatchpointSetupInfo* createDelattrPatchpoint(CompiledFunction* parent_cf, TypeRecorder* type_recorder) {
......@@ -147,7 +147,7 @@ PatchpointSetupInfo* createCallsitePatchpoint(CompiledFunction* parent_cf, TypeR
// TODO These are very large, but could probably be made much smaller with IC optimizations
// - using rewriter2 for better code
// - not emitting duplicate guards
return PatchpointSetupInfo::initialize(true, 3, 480 + 48 * num_args, parent_cf, Callsite, type_recorder);
return PatchpointSetupInfo::initialize(true, 3, 640 + 48 * num_args, parent_cf, Callsite, type_recorder);
}
PatchpointSetupInfo* createGetGlobalPatchpoint(CompiledFunction* parent_cf, TypeRecorder* type_recorder) {
......@@ -155,7 +155,7 @@ PatchpointSetupInfo* createGetGlobalPatchpoint(CompiledFunction* parent_cf, Type
}
PatchpointSetupInfo* createBinexpPatchpoint(CompiledFunction* parent_cf, TypeRecorder* type_recorder) {
return PatchpointSetupInfo::initialize(true, 4, 320, parent_cf, Binexp, type_recorder);
return PatchpointSetupInfo::initialize(true, 4, 512, parent_cf, Binexp, type_recorder);
}
PatchpointSetupInfo* createNonzeroPatchpoint(CompiledFunction* parent_cf, TypeRecorder* type_recorder) {
......
......@@ -55,7 +55,7 @@ extern "C" Box* dir(Box* obj) {
}
// If __dict__ is present use its keys and add the reset below
Box* obj_dict = getattr_internal(obj, "__dict__", false, true, nullptr);
Box* obj_dict = getattrInternal(obj, "__dict__", nullptr);
if (obj_dict && obj_dict->cls == dict_cls) {
result = new BoxedList();
for (auto& kv : static_cast<BoxedDict*>(obj_dict)->d) {
......@@ -331,7 +331,7 @@ Box* getattrFunc(Box* obj, Box* _str, Box* default_value) {
}
BoxedString* str = static_cast<BoxedString*>(_str);
Box* rtn = getattr_internal(obj, str->s, true, true, NULL);
Box* rtn = getattrInternal(obj, str->s, NULL);
if (!rtn) {
if (default_value)
......@@ -350,7 +350,7 @@ Box* hasattr(Box* obj, Box* _str) {
}
BoxedString* str = static_cast<BoxedString*>(_str);
Box* attr = getattr_internal(obj, str->s, true, true, NULL);
Box* attr = getattrInternal(obj, str->s, NULL);
Box* rtn = attr ? True : False;
return rtn;
......
......@@ -672,41 +672,6 @@ void Box::setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite
attrs->attr_list->attrs[numattrs] = val;
}
static Box* _handleClsAttr(Box* obj, Box* attr) {
if (attr->cls == function_cls || attr->cls == method_cls) {
Box* rtn = boxInstanceMethod(obj, attr);
return rtn;
}
if (attr->cls == member_cls) {
BoxedMemberDescriptor* member_desc = static_cast<BoxedMemberDescriptor*>(attr);
switch (member_desc->type) {
case BoxedMemberDescriptor::OBJECT: {
assert(member_desc->offset % sizeof(Box*) == 0);
Box* rtn = reinterpret_cast<Box**>(obj)[member_desc->offset / sizeof(Box*)];
// be careful about returning NULLs; I'm not sure what the correct behavior is here:
RELEASE_ASSERT(rtn, "");
return rtn;
}
case BoxedMemberDescriptor::BYTE: {
int8_t rtn = reinterpret_cast<int8_t*>(obj)[member_desc->offset];
return boxInt(rtn);
}
case BoxedMemberDescriptor::BOOL: {
bool rtn = reinterpret_cast<bool*>(obj)[member_desc->offset];
return boxBool(rtn);
}
case BoxedMemberDescriptor::INT: {
int rtn = reinterpret_cast<int*>(obj)[member_desc->offset/sizeof(int)];
return boxInt(rtn);
}
default:
RELEASE_ASSERT(0, "%d", member_desc->type);
}
abort();
}
return attr;
}
Box* typeLookup(BoxedClass* cls, const std::string& attr, GetattrRewriteArgs* rewrite_args) {
Box* val;
......@@ -737,43 +702,150 @@ Box* typeLookup(BoxedClass* cls, const std::string& attr, GetattrRewriteArgs* re
}
}
Box* getclsattr_internal(Box* obj, const std::string& attr, GetattrRewriteArgs* rewrite_args) {
Box* val;
bool isNondataDescriptorInstanceSpecialCase(Box* descr) {
return descr->cls == function_cls;
}
Box* nondataDescriptorInstanceSpecialCases(GetattrRewriteArgs* rewrite_args, Box* obj, Box* descr,
RewriterVarUsage& r_descr, bool for_call, bool* should_bind_out) {
// Special case: non-data descriptor: function
if (descr->cls == function_cls) {
if (!for_call) {
if (rewrite_args) {
RewriterVarUsage rcls = rewrite_args->obj.getAttr(BOX_CLS_OFFSET, RewriterVarUsage::NoKill);
// can't guard after because we make this call... the call is trivial enough
// that we can probably work around it if it's important, but otherwise, if
// this triggers, just abort rewriting, I guess
assert(!rewrite_args->more_guards_after);
rewrite_args->rewriter->setDoneGuarding();
rewrite_args->out_rtn = rewrite_args->rewriter->call(false, (void*)boxInstanceMethod,
std::move(rewrite_args->obj), std::move(r_descr));
rewrite_args->out_success = true;
}
return boxInstanceMethod(obj, descr);
} else {
if (rewrite_args) {
if (!rewrite_args->more_guards_after)
rewrite_args->rewriter->setDoneGuarding();
rewrite_args->obj.setDoneUsing();
rewrite_args->out_rtn = std::move(r_descr);
rewrite_args->out_success = true;
}
*should_bind_out = true;
return descr;
}
}
GetattrRewriteArgs sub_rewrite_args(rewrite_args->rewriter, std::move(rcls), Location::forArg(1),
rewrite_args->more_guards_after);
return NULL;
}
val = typeLookup(obj->cls, attr, &sub_rewrite_args);
Box* descriptorClsSpecialCases(GetattrRewriteArgs* rewrite_args, BoxedClass* cls, Box* descr, RewriterVarUsage& r_descr,
bool for_call, bool* should_bind_out) {
// Special case: functions
if (descr->cls == function_cls) {
if (rewrite_args)
r_descr.addAttrGuard(BOX_CLS_OFFSET, (uint64_t)descr->cls);
if (!sub_rewrite_args.out_success) {
rewrite_args = NULL;
if (!for_call) {
if (rewrite_args) {
assert(!rewrite_args->more_guards_after);
rewrite_args->rewriter->setDoneGuarding();
rewrite_args->obj.setDoneUsing();
// return an unbound instancemethod
rewrite_args->out_rtn
= rewrite_args->rewriter->call(false, (void*)boxUnboundInstanceMethod, std::move(r_descr));
rewrite_args->out_success = true;
}
return boxUnboundInstanceMethod(descr);
} else {
if (val) {
rewrite_args->out_rtn = std::move(sub_rewrite_args.out_rtn);
if (rewrite_args) {
if (!rewrite_args->more_guards_after)
rewrite_args->rewriter->setDoneGuarding();
rewrite_args->obj.setDoneUsing();
rewrite_args->out_success = true;
rewrite_args->out_rtn = std::move(r_descr);
}
// leave should_bind_out set to false
return descr;
}
} else {
val = typeLookup(obj->cls, attr, NULL);
}
if (val == NULL) {
// Special case: member descriptor
if (descr->cls == member_cls) {
if (rewrite_args)
r_descr.addAttrGuard(BOX_CLS_OFFSET, (uint64_t)descr->cls);
if (rewrite_args) {
assert(!rewrite_args->more_guards_after);
rewrite_args->rewriter->setDoneGuarding();
rewrite_args->obj.setDoneUsing();
// Actually just return val (it's a descriptor but only
// has special behaviour for instance lookups - see below)
rewrite_args->out_rtn = std::move(r_descr);
rewrite_args->out_success = true;
return val;
}
return descr;
}
return NULL;
}
Box* dataDescriptorInstanceSpecialCases(GetattrRewriteArgs* rewrite_args, Box* obj, Box* descr,
RewriterVarUsage& r_descr, bool for_call, bool* should_bind_out) {
// Special case: data descriptor: member descriptor
if (descr->cls == member_cls) {
BoxedMemberDescriptor* member_desc = static_cast<BoxedMemberDescriptor*>(descr);
// TODO should also have logic to raise a type error if type of obj is wrong
if (rewrite_args) {
// Ok this is a lie, _handleClsAttr can call back into python because it does GC collection.
// I guess it should disable GC or something...
RewriterVarUsage rrtn = rewrite_args->rewriter->call(false, (void*)_handleClsAttr, std::move(rewrite_args->obj),
std::move(rewrite_args->out_rtn));
rewrite_args->out_rtn = std::move(rrtn);
if (!rewrite_args->more_guards_after)
rewrite_args->rewriter->setDoneGuarding();
r_descr.setDoneUsing();
// TODO figure out guards
// do i need to have the rewriter write code to lookup the
// offset or do the guards protect that?
rewrite_args->out_rtn = rewrite_args->obj.getAttr(member_desc->offset, RewriterVarUsage::KillFlag::Kill,
rewrite_args->destination);
rewrite_args->out_success = true;
}
return _handleClsAttr(obj, val);
switch (member_desc->type) {
case BoxedMemberDescriptor::OBJECT: {
assert(member_desc->offset % sizeof(Box*) == 0);
Box* rtn = reinterpret_cast<Box**>(obj)[member_desc->offset / sizeof(Box*)];
RELEASE_ASSERT(rtn, "");
return rtn;
}
case BoxedMemberDescriptor::BYTE: {
// TODO rewriter stuff for these other cases as well
rewrite_args = NULL;
int8_t rtn = reinterpret_cast<int8_t*>(obj)[member_desc->offset];
return boxInt(rtn);
}
case BoxedMemberDescriptor::BOOL: {
rewrite_args = NULL;
bool rtn = reinterpret_cast<bool*>(obj)[member_desc->offset];
return boxBool(rtn);
}
case BoxedMemberDescriptor::INT: {
rewrite_args = NULL;
int rtn = reinterpret_cast<int*>(obj)[member_desc->offset / sizeof(int)];
return boxInt(rtn);
}
default:
RELEASE_ASSERT(0, "%d", member_desc->type);
}
}
return NULL;
}
inline Box* getclsattr_internal(Box* obj, const std::string& attr, GetattrRewriteArgs* rewrite_args) {
return getattrInternalGeneral(obj, attr, rewrite_args,
/* cls_only */ true,
/* for_call */ false, NULL);
}
extern "C" Box* getclsattr(Box* obj, const char* attr) {
......@@ -822,16 +894,61 @@ static Box* (*runtimeCall2)(Box*, ArgPassSpec, Box*, Box*) = (Box * (*)(Box*, Ar
static Box* (*runtimeCall3)(Box*, ArgPassSpec, Box*, Box*, Box*)
= (Box * (*)(Box*, ArgPassSpec, Box*, Box*, Box*))runtimeCall;
Box* getattr_internal(Box* obj, const std::string& attr, bool check_cls, bool allow_custom,
GetattrRewriteArgs* rewrite_args) {
if (allow_custom) {
Box* getattrInternalGeneral(Box* obj, const std::string& attr, GetattrRewriteArgs* rewrite_args, bool cls_only,
bool for_call, bool* should_bind_out) {
if (for_call) {
*should_bind_out = false;
}
if (obj->cls == closure_cls) {
Box* val = NULL;
if (rewrite_args) {
GetattrRewriteArgs hrewrite_args(rewrite_args->rewriter, rewrite_args->obj.addUse(),
rewrite_args->destination, true);
val = obj->getattr(attr, &hrewrite_args);
if (!hrewrite_args.out_success) {
rewrite_args = NULL;
} else if (val) {
if (!rewrite_args->more_guards_after)
rewrite_args->rewriter->setDoneGuarding();
rewrite_args->out_rtn = std::move(hrewrite_args.out_rtn);
rewrite_args->out_success = true;
rewrite_args->obj.setDoneUsing();
return val;
}
} else {
val = obj->getattr(attr, NULL);
if (val) {
return val;
}
}
// If val doesn't exist, then we move up to the parent closure
// TODO closures should get their own treatment, but now just piggy-back on the
// normal hidden-class IC logic.
// Can do better since we don't need to guard on the cls (always going to be closure)
BoxedClosure* closure = static_cast<BoxedClosure*>(obj);
if (closure->parent) {
if (rewrite_args) {
rewrite_args->obj = rewrite_args->obj.getAttr(offsetof(BoxedClosure, parent), RewriterVarUsage::NoKill);
if (!rewrite_args->more_guards_after)
rewrite_args->rewriter->setDoneGuarding();
}
return getattrInternal(closure->parent, attr, rewrite_args);
}
raiseExcHelper(NameError, "free variable '%s' referenced before assignment in enclosing scope", attr.c_str());
}
if (!cls_only) {
// Don't need to pass icentry args, since we special-case __getattribtue__ and __getattr__ to use
// invalidation rather than guards
Box* getattribute = getclsattr_internal(obj, "__getattribute__", NULL);
// TODO since you changed this to typeLookup you need to guard
Box* getattribute = typeLookup(obj->cls, "__getattribute__", NULL);
if (getattribute) {
// TODO this is a good candidate for interning?
Box* boxstr = boxString(attr);
Box* rtn = runtimeCall1(getattribute, ArgPassSpec(1), boxstr);
Box* rtn = runtimeCall2(getattribute, ArgPassSpec(2), obj, boxstr);
return rtn;
}
......@@ -840,79 +957,312 @@ Box* getattr_internal(Box* obj, const std::string& attr, bool check_cls, bool al
}
}
if (obj->cls == type_cls) {
// Handle descriptor logic here.
// A descriptor is either a data descriptor or a non-data descriptor.
// data descriptors define both __get__ and __set__. non-data descriptors
// only define __get__. Rules are different for the two types, which means
// that even though __get__ is the one we might call, we still have to check
// if __set__ exists.
// If __set__ exists, it's a data descriptor, and it takes precedence over
// the instance attribute.
// Otherwise, it's non-data, and we only call __get__ if the instance
// attribute doesn't exist.
// In the cls_only case, we ignore the instance attribute
// (so we don't have to check if __set__ exists at all)
// Look up the class attribute (called `descr` here because it might
// be a descriptor).
Box* descr = NULL;
RewriterVarUsage r_descr(RewriterVarUsage::empty());
if (rewrite_args) {
GetattrRewriteArgs grewrite_args(rewrite_args->rewriter, rewrite_args->obj.addUse(),
rewrite_args->destination, true);
RewriterVarUsage r_obj_cls
= rewrite_args->obj.getAttr(BOX_CLS_OFFSET, RewriterVarUsage::NoKill, Location::any());
GetattrRewriteArgs grewrite_args(rewrite_args->rewriter, std::move(r_obj_cls), rewrite_args->destination, true);
descr = typeLookup(obj->cls, attr, &grewrite_args);
Box* val = typeLookup(static_cast<BoxedClass*>(obj), attr, &grewrite_args);
if (val) {
rewrite_args->obj.setDoneUsing();
rewrite_args->out_rtn = std::move(grewrite_args.out_rtn);
rewrite_args->out_success = true;
if (!rewrite_args->more_guards_after)
rewrite_args->rewriter->setDoneGuarding();
return val;
if (!grewrite_args.out_success) {
rewrite_args = NULL;
} else if (descr) {
r_descr = std::move(grewrite_args.out_rtn);
}
} else {
descr = typeLookup(obj->cls, attr, NULL);
}
// Check if it's a data descriptor
Box* _get_ = NULL;
RewriterVarUsage r_get(RewriterVarUsage::empty());
if (descr) {
if (rewrite_args)
r_descr.addAttrGuard(BOX_CLS_OFFSET, (uint64_t)descr->cls);
// Special-case data descriptors (e.g., member descriptors)
Box* res = dataDescriptorInstanceSpecialCases(rewrite_args, obj, descr, r_descr, for_call, should_bind_out);
if (res) {
return res;
}
// Let's only check if __get__ exists if it's not a special case
// nondata descriptor. The nondata case is handled below, but
// we can immediately know to skip this part if it's one of the
// special case nondata descriptors.
if (!isNondataDescriptorInstanceSpecialCase(descr)) {
// Check if __get__ exists
if (rewrite_args) {
RewriterVarUsage r_descr_cls
= r_descr.getAttr(BOX_CLS_OFFSET, RewriterVarUsage::NoKill, Location::any());
GetattrRewriteArgs grewrite_args(rewrite_args->rewriter, std::move(r_descr_cls), Location::any(), true);
_get_ = typeLookup(descr->cls, "__get__", &grewrite_args);
if (!grewrite_args.out_success) {
rewrite_args = NULL;
} else if (_get_) {
r_get = std::move(grewrite_args.out_rtn);
}
} else {
Box* val = typeLookup(static_cast<BoxedClass*>(obj), attr, NULL);
if (val)
return val;
_get_ = typeLookup(descr->cls, "__get__", NULL);
}
if (_get_ && !cls_only) {
// Check if __set__ exists
Box* _set_ = NULL;
if (rewrite_args) {
RewriterVarUsage r_descr_cls
= r_descr.getAttr(BOX_CLS_OFFSET, RewriterVarUsage::NoKill, Location::any());
GetattrRewriteArgs grewrite_args(rewrite_args->rewriter, std::move(r_descr_cls), Location::any(),
true);
_set_ = typeLookup(descr->cls, "__set__", &grewrite_args);
if (!grewrite_args.out_success) {
rewrite_args = NULL;
} else if (_set_) {
grewrite_args.out_rtn.setDoneUsing();
}
} else {
Box* val = NULL;
_set_ = typeLookup(descr->cls, "__set__", NULL);
}
// Call __get__(descr, obj, obj->cls)
if (_set_) {
// this could happen for the callattr path...
if (rewrite_args && rewrite_args->more_guards_after)
rewrite_args = NULL;
Box* res;
if (rewrite_args) {
CallRewriteArgs crewrite_args(rewrite_args->rewriter, std::move(r_get),
rewrite_args->destination, rewrite_args->more_guards_after);
crewrite_args.arg1 = std::move(r_descr);
crewrite_args.arg2 = rewrite_args->obj.addUse();
crewrite_args.arg3
= rewrite_args->obj.getAttr(BOX_CLS_OFFSET, RewriterVarUsage::Kill, Location::any());
res = runtimeCallInternal(_get_, &crewrite_args, ArgPassSpec(3), descr, obj, obj->cls, NULL,
NULL);
if (!crewrite_args.out_success) {
rewrite_args = NULL;
} else {
rewrite_args->out_success = true;
rewrite_args->out_rtn = std::move(crewrite_args.out_rtn);
}
} else {
r_descr.ensureDoneUsing();
r_get.ensureDoneUsing();
res = runtimeCallInternal(_get_, NULL, ArgPassSpec(3), descr, obj, obj->cls, NULL, NULL);
}
return res;
}
}
}
}
if (!cls_only) {
if (obj->cls != type_cls) {
// Look up the val in the object's dictionary and if you find it, return it.
Box* val;
RewriterVarUsage r_val(RewriterVarUsage::empty());
if (rewrite_args) {
GetattrRewriteArgs hrewrite_args(rewrite_args->rewriter, rewrite_args->obj.addUse(),
rewrite_args->destination, true);
val = obj->getattr(attr, &hrewrite_args);
if (hrewrite_args.out_success) {
if (!hrewrite_args.out_success) {
rewrite_args = NULL;
} else if (val) {
r_val = std::move(hrewrite_args.out_rtn);
}
} else {
val = obj->getattr(attr, NULL);
}
if (val) {
rewrite_args->out_rtn = std::move(hrewrite_args.out_rtn);
if (rewrite_args) {
if (!rewrite_args->more_guards_after)
rewrite_args->rewriter->setDoneGuarding();
rewrite_args->obj.setDoneUsing();
rewrite_args->out_rtn = std::move(r_val);
rewrite_args->out_success = true;
}
r_descr.ensureDoneUsing();
r_get.ensureDoneUsing();
return val;
}
} else {
// More complicated when obj is a type
// We have to look up the attr in the entire
// class hierarchy, and we also have to check if it is a descriptor,
// in addition to the data/nondata descriptor logic.
// (in CPython, see type_getattro in typeobject.c)
Box* val;
RewriterVarUsage r_val(RewriterVarUsage::empty());
if (rewrite_args) {
GetattrRewriteArgs grewrite_args(rewrite_args->rewriter, rewrite_args->obj.addUse(),
rewrite_args->destination, true);
val = typeLookup(static_cast<BoxedClass*>(obj), attr, &grewrite_args);
if (!grewrite_args.out_success) {
rewrite_args = NULL;
} else if (val) {
r_val = std::move(grewrite_args.out_rtn);
}
} else {
val = obj->getattr(attr, NULL);
val = typeLookup(static_cast<BoxedClass*>(obj), attr, NULL);
}
if (val) {
r_get.ensureDoneUsing();
r_descr.ensureDoneUsing();
Box* res = descriptorClsSpecialCases(rewrite_args, static_cast<BoxedClass*>(obj), val, r_val, for_call,
should_bind_out);
if (res) {
return res;
}
// Lookup __get__
RewriterVarUsage r_get(RewriterVarUsage::empty());
Box* local_get;
if (rewrite_args) {
RewriterVarUsage r_val_cls
= r_val.getAttr(BOX_CLS_OFFSET, RewriterVarUsage::NoKill, Location::any());
GetattrRewriteArgs grewrite_args(rewrite_args->rewriter, std::move(r_val_cls), Location::any(),
true);
local_get = typeLookup(val->cls, "__get__", &grewrite_args);
if (!grewrite_args.out_success) {
rewrite_args = NULL;
} else if (local_get) {
r_get = std::move(grewrite_args.out_rtn);
}
} else {
local_get = typeLookup(val->cls, "__get__", NULL);
}
// Call __get__(val, None, obj)
if (local_get) {
Box* res;
// this could happen for the callattr path...
if (rewrite_args && rewrite_args->more_guards_after)
rewrite_args = NULL;
if (rewrite_args) {
CallRewriteArgs crewrite_args(rewrite_args->rewriter, std::move(r_get),
rewrite_args->destination, rewrite_args->more_guards_after);
crewrite_args.arg1 = std::move(r_val);
crewrite_args.arg2 = rewrite_args->rewriter->loadConst((intptr_t)None, Location::any());
crewrite_args.arg3 = std::move(rewrite_args->obj);
res = runtimeCallInternal(local_get, &crewrite_args, ArgPassSpec(3), val, None, obj, NULL,
NULL);
if (!crewrite_args.out_success) {
rewrite_args = NULL;
} else {
rewrite_args->out_success = true;
rewrite_args->out_rtn = std::move(crewrite_args.out_rtn);
}
} else {
r_val.ensureDoneUsing();
r_get.ensureDoneUsing();
res = runtimeCallInternal(local_get, NULL, ArgPassSpec(3), val, None, obj, NULL, NULL);
}
return res;
}
// If there was no local __get__, just return val
if (rewrite_args) {
if (!rewrite_args->more_guards_after)
rewrite_args->rewriter->setDoneGuarding();
rewrite_args->obj.setDoneUsing();
rewrite_args->out_rtn = std::move(r_val);
rewrite_args->out_success = true;
} else {
r_val.ensureDoneUsing();
}
return val;
}
}
}
// If descr and __get__ exist, then call __get__
if (descr) {
// Special cases first
Box* res = nondataDescriptorInstanceSpecialCases(rewrite_args, obj, descr, r_descr, for_call, should_bind_out);
if (res) {
return res;
}
// We looked up __get__ above. If we found it, call it and return
// the result.
if (_get_) {
// this could happen for the callattr path...
if (rewrite_args && rewrite_args->more_guards_after)
rewrite_args = NULL;
// TODO closures should get their own treatment, but now just piggy-back on the
// normal hidden-class IC logic.
// Can do better since we don't need to guard on the cls (always going to be closure)
if (obj->cls == closure_cls) {
BoxedClosure* closure = static_cast<BoxedClosure*>(obj);
if (closure->parent) {
Box* res;
if (rewrite_args) {
rewrite_args->obj = rewrite_args->obj.getAttr(offsetof(BoxedClosure, parent), RewriterVarUsage::NoKill);
CallRewriteArgs crewrite_args(rewrite_args->rewriter, std::move(r_get), rewrite_args->destination,
rewrite_args->more_guards_after);
crewrite_args.arg1 = std::move(r_descr);
crewrite_args.arg2 = rewrite_args->obj.addUse();
crewrite_args.arg3 = rewrite_args->obj.getAttr(BOX_CLS_OFFSET, RewriterVarUsage::Kill, Location::any());
res = runtimeCallInternal(_get_, &crewrite_args, ArgPassSpec(3), descr, obj, obj->cls, NULL, NULL);
if (!crewrite_args.out_success) {
rewrite_args = NULL;
} else {
rewrite_args->out_success = true;
rewrite_args->out_rtn = std::move(crewrite_args.out_rtn);
}
return getattr_internal(closure->parent, attr, false, false, rewrite_args);
} else {
r_descr.ensureDoneUsing();
r_get.ensureDoneUsing();
res = runtimeCallInternal(_get_, NULL, ArgPassSpec(3), descr, obj, obj->cls, NULL, NULL);
}
raiseExcHelper(NameError, "free variable '%s' referenced before assignment in enclosing scope", attr.c_str());
return res;
}
// Otherwise, just return descr.
if (rewrite_args) {
rewrite_args->obj.setDoneUsing();
if (!rewrite_args->more_guards_after)
rewrite_args->rewriter->setDoneGuarding();
rewrite_args->out_rtn = std::move(r_descr);
rewrite_args->out_success = true;
}
return descr;
}
// Finally, check __getattr__
if (allow_custom) {
// Don't need to pass icentry args, since we special-case __getattribtue__ and __getattr__ to use
if (!cls_only) {
// Don't need to pass icentry args, since we special-case __getattribute__ and __getattr__ to use
// invalidation rather than guards
Box* getattr = getclsattr_internal(obj, "__getattr__", NULL);
rewrite_args = NULL;
Box* getattr = typeLookup(obj->cls, "__getattr__", NULL);
if (getattr) {
Box* boxstr = boxString(attr);
Box* rtn = runtimeCall1(getattr, ArgPassSpec(1), boxstr);
Box* rtn = runtimeCall2(getattr, ArgPassSpec(2), obj, boxstr);
if (rewrite_args)
rewrite_args->out_rtn.ensureDoneUsing();
return rtn;
}
......@@ -921,31 +1271,17 @@ Box* getattr_internal(Box* obj, const std::string& attr, bool check_cls, bool al
}
}
Box* rtn = NULL;
if (check_cls) {
if (rewrite_args) {
GetattrRewriteArgs crewrite_args(rewrite_args->rewriter, std::move(rewrite_args->obj),
rewrite_args->destination, rewrite_args->more_guards_after);
rtn = getclsattr_internal(obj, attr, &crewrite_args);
if (!crewrite_args.out_success) {
rewrite_args = NULL;
} else {
if (rtn)
rewrite_args->out_rtn = std::move(crewrite_args.out_rtn);
else
rewrite_args->obj = std::move(crewrite_args.obj);
}
} else {
rtn = getclsattr_internal(obj, attr, NULL);
}
}
if (rewrite_args) {
rewrite_args->obj.ensureDoneUsing();
rewrite_args->out_success = true;
}
return NULL;
}
return rtn;
Box* getattrInternal(Box* obj, const std::string& attr, GetattrRewriteArgs* rewrite_args) {
return getattrInternalGeneral(obj, attr, rewrite_args,
/* cls_only */ false,
/* for_call */ false, NULL);
}
extern "C" Box* getattr(Box* obj, const char* attr) {
......@@ -965,7 +1301,6 @@ extern "C" Box* getattr(Box* obj, const char* attr) {
Box* val;
if (rewriter.get()) {
// rewriter->trap();
Location dest;
TypeRecorder* recorder = rewriter->getTypeRecorder();
if (recorder)
......@@ -973,7 +1308,12 @@ extern "C" Box* getattr(Box* obj, const char* attr) {
else
dest = rewriter->getReturnDestination();
GetattrRewriteArgs rewrite_args(rewriter.get(), rewriter->getArg(0), dest, false);
val = getattr_internal(obj, attr, true, true, &rewrite_args);
val = getattrInternal(obj, attr, &rewrite_args);
// should make sure getattrInternal calls finishes using obj itself
// if it is successful
if (!rewrite_args.out_success)
rewrite_args.obj.ensureDoneUsing();
if (rewrite_args.out_success && val) {
if (recorder) {
......@@ -988,7 +1328,7 @@ extern "C" Box* getattr(Box* obj, const char* attr) {
}
}
} else {
val = getattr_internal(obj, attr, true, true, NULL);
val = getattrInternal(obj, attr, NULL);
}
if (val) {
......@@ -997,6 +1337,71 @@ extern "C" Box* getattr(Box* obj, const char* attr) {
raiseAttributeError(obj, attr);
}
static void setattr_internal(Box* obj, const std::string& attr, Box* val, SetattrRewriteArgs* rewrite_args) {
// Lookup a descriptor
Box* descr = NULL;
RewriterVarUsage r_descr(RewriterVarUsage::empty());
// TODO probably check that the cls is user-defined or something like that
// (figure out exactly what)
// (otherwise no need to check descriptor logic)
if (rewrite_args) {
RewriterVarUsage r_cls
= rewrite_args->obj.getAttr(BOX_CLS_OFFSET, RewriterVarUsage::KillFlag::NoKill, Location::any());
GetattrRewriteArgs crewrite_args(rewrite_args->rewriter, std::move(r_cls),
rewrite_args->rewriter->getReturnDestination(), true);
descr = typeLookup(obj->cls, attr, &crewrite_args);
if (!crewrite_args.out_success) {
rewrite_args = NULL;
} else if (descr) {
r_descr = std::move(crewrite_args.out_rtn);
}
} else {
descr = typeLookup(obj->cls, attr, NULL);
}
Box* _set_ = NULL;
RewriterVarUsage r_set(RewriterVarUsage::empty());
if (descr) {
if (rewrite_args) {
RewriterVarUsage r_cls
= r_descr.getAttr(BOX_CLS_OFFSET, RewriterVarUsage::KillFlag::NoKill, Location::any());
GetattrRewriteArgs trewrite_args(rewrite_args->rewriter, std::move(r_cls), Location::any(), true);
_set_ = typeLookup(descr->cls, "__set__", &trewrite_args);
if (!trewrite_args.out_success) {
rewrite_args = NULL;
} else if (_set_) {
r_set = std::move(trewrite_args.out_rtn);
}
} else {
_set_ = typeLookup(descr->cls, "__set__", NULL);
}
}
// If `descr` has __set__ (thus making it a descriptor) we should call
// __set__ with `val` rather than directly calling setattr
if (descr && _set_) {
if (rewrite_args) {
CallRewriteArgs crewrite_args(rewrite_args->rewriter, std::move(r_set), Location::any(),
rewrite_args->more_guards_after);
crewrite_args.arg1 = std::move(r_descr);
crewrite_args.arg2 = std::move(rewrite_args->obj);
crewrite_args.arg3 = std::move(rewrite_args->attrval);
runtimeCallInternal(_set_, &crewrite_args, ArgPassSpec(3), descr, obj, val, NULL, NULL);
if (crewrite_args.out_success) {
crewrite_args.out_rtn.setDoneUsing();
rewrite_args->out_success = true;
}
} else {
runtimeCallInternal(_set_, NULL, ArgPassSpec(3), descr, obj, val, NULL, NULL);
}
} else {
r_descr.ensureDoneUsing();
r_set.ensureDoneUsing();
obj->setattr(attr, val, rewrite_args);
}
}
extern "C" void setattr(Box* obj, const char* attr, Box* attr_val) {
assert(strcmp(attr, "__class__") != 0);
......@@ -1021,15 +1426,15 @@ extern "C" void setattr(Box* obj, const char* attr, Box* attr_val) {
if (rewriter.get()) {
// rewriter->trap();
SetattrRewriteArgs rewrite_args(rewriter.get(), rewriter->getArg(0), rewriter->getArg(2), false);
obj->setattr(attr, attr_val, &rewrite_args);
setattr_internal(obj, attr, attr_val, &rewrite_args);
if (rewrite_args.out_success) {
rewriter->commit();
} else {
rewrite_args.obj.setDoneUsing();
rewrite_args.attrval.setDoneUsing();
rewrite_args.obj.ensureDoneUsing();
rewrite_args.attrval.ensureDoneUsing();
}
} else {
obj->setattr(attr, attr_val, NULL);
setattr_internal(obj, attr, attr_val, NULL);
}
}
......@@ -1101,7 +1506,9 @@ extern "C" bool nonzero(Box* obj) {
// int id = Stats::getStatId("slowpath_nonzero_" + *getTypeName(obj));
// Stats::log(id);
// go through descriptor logic
Box* func = getclsattr_internal(obj, "__nonzero__", NULL);
if (func == NULL) {
RELEASE_ASSERT(isUserDefined(obj->cls), "%s.__nonzero__", getTypeName(obj)->c_str()); // TODO
return true;
......@@ -1188,7 +1595,9 @@ extern "C" BoxedInt* hash(Box* obj) {
static StatCounter slowpath_hash("slowpath_hash");
slowpath_hash.log();
// goes through descriptor logic
Box* hash = getclsattr_internal(obj, "__hash__", NULL);
if (hash == NULL) {
ASSERT(isUserDefined(obj->cls), "%s.__hash__", getTypeName(obj)->c_str());
// TODO not the best way to handle this...
......@@ -1318,21 +1727,6 @@ extern "C" Box* callattrInternal(Box* obj, const std::string* attr, LookupScope
const std::vector<const std::string*>* keyword_names) {
int npassed_args = argspec.totalPassed();
// if (rewrite_args) {
// if (VERBOSITY()) {
// printf("callattrInternal: %d", rewrite_args->obj.getArgnum());
// if (npassed_args >= 1) printf(" %d", rewrite_args->arg1.getArgnum());
// if (npassed_args >= 2) printf(" %d", rewrite_args->arg2.getArgnum());
// if (npassed_args >= 3) printf(" %d", rewrite_args->arg3.getArgnum());
// if (npassed_args >= 4) printf(" %d", rewrite_args->args.getArgnum());
// printf("\n");
//}
// if (rewrite_args->obj.getArgnum() == -1) {
// rewrite_args->rewriter->trap();
// rewrite_args->obj = rewrite_args->obj.move(-3);
// }
// }
if (rewrite_args && !rewrite_args->args_guarded) {
// TODO duplication with runtime_call
// TODO should know which args don't need to be guarded, ex if we're guaranteed that they
......@@ -1358,90 +1752,41 @@ extern "C" Box* callattrInternal(Box* obj, const std::string* attr, LookupScope
}
}
// right now I don't think this is ever called with INST_ONLY?
assert(scope != INST_ONLY);
if (checkInst(scope)) {
Box* inst_attr;
RewriterVarUsage r_instattr(RewriterVarUsage::empty());
if (rewrite_args) {
GetattrRewriteArgs ga_rewrite_args(rewrite_args->rewriter, rewrite_args->obj.addUse(),
rewrite_args->destination, true);
inst_attr = getattr_internal(obj, *attr, false, true, &ga_rewrite_args);
if (!ga_rewrite_args.out_success) {
rewrite_args = NULL;
} else {
if (inst_attr) {
r_instattr = std::move(ga_rewrite_args.out_rtn);
}
}
} else {
inst_attr = getattr_internal(obj, *attr, false, true, NULL);
}
if (inst_attr) {
Box* rtn;
if (inst_attr->cls != function_cls) {
rewrite_args = NULL;
}
if (rewrite_args) {
rewrite_args->args_guarded = true;
r_instattr.addGuard((intptr_t)inst_attr);
r_instattr.setDoneUsing();
rewrite_args->func_guarded = true;
rtn = runtimeCallInternal(inst_attr, rewrite_args, argspec, arg1, arg2, arg3, args, keyword_names);
} else {
rtn = runtimeCallInternal(inst_attr, NULL, argspec, arg1, arg2, arg3, args, keyword_names);
}
if (!rtn) {
raiseExcHelper(TypeError, "'%s' object is not callable", getTypeName(inst_attr)->c_str());
}
r_instattr.ensureDoneUsing();
return rtn;
}
r_instattr.ensureDoneUsing();
}
Box* clsattr = NULL;
RewriterVarUsage r_clsattr(RewriterVarUsage::empty());
if (checkClass(scope)) {
// Look up the argument. Pass in the arguments to getattrInternalGeneral or getclsattr_general
// that will shortcut functions by not putting them into instancemethods
bool should_bind;
Box* val;
RewriterVarUsage r_val(RewriterVarUsage::empty());
if (rewrite_args) {
RewriterVarUsage r_cls = std::move(
rewrite_args->obj.getAttr(BOX_CLS_OFFSET, RewriterVarUsage::KillFlag::NoKill, Location::any()));
GetattrRewriteArgs ga_rewrite_args(rewrite_args->rewriter, std::move(r_cls), rewrite_args->destination,
true);
// r_cls.assertValid();
clsattr = typeLookup(obj->cls, *attr, &ga_rewrite_args);
if (!ga_rewrite_args.out_success) {
GetattrRewriteArgs grewrite_args(rewrite_args->rewriter, rewrite_args->obj.addUse(), Location::any(), true);
val = getattrInternalGeneral(obj, *attr, &grewrite_args, scope == CLASS_ONLY, true, &should_bind);
if (!grewrite_args.out_success) {
rewrite_args = NULL;
} else {
if (clsattr)
r_clsattr = std::move(ga_rewrite_args.out_rtn);
} else if (val) {
r_val = std::move(grewrite_args.out_rtn);
}
} else {
clsattr = typeLookup(obj->cls, *attr, NULL);
}
val = getattrInternalGeneral(obj, *attr, NULL, scope == CLASS_ONLY, true, &should_bind);
}
if (!clsattr) {
if (val == NULL) {
if (rewrite_args) {
rewrite_args->ensureAllDone();
rewrite_args->arg1.ensureDoneUsing();
rewrite_args->arg2.ensureDoneUsing();
rewrite_args->arg3.ensureDoneUsing();
rewrite_args->args.ensureDoneUsing();
rewrite_args->out_success = true;
rewrite_args->obj.setDoneUsing();
}
return NULL;
return val;
}
if (clsattr->cls == function_cls) {
if (should_bind) {
if (rewrite_args) {
r_clsattr.addGuard((int64_t)clsattr);
r_val.addGuard((int64_t)val);
}
// TODO copy from runtimeCall
......@@ -1450,7 +1795,7 @@ extern "C" Box* callattrInternal(Box* obj, const std::string* attr, LookupScope
if (npassed_args <= 2) {
Box* rtn;
if (rewrite_args) {
CallRewriteArgs srewrite_args(rewrite_args->rewriter, std::move(r_clsattr), rewrite_args->destination,
CallRewriteArgs srewrite_args(rewrite_args->rewriter, std::move(r_val), rewrite_args->destination,
rewrite_args->more_guards_after);
srewrite_args.arg1 = std::move(rewrite_args->obj);
......@@ -1463,9 +1808,8 @@ extern "C" Box* callattrInternal(Box* obj, const std::string* attr, LookupScope
srewrite_args.func_guarded = true;
srewrite_args.args_guarded = true;
rtn = runtimeCallInternal(
clsattr, &srewrite_args,
ArgPassSpec(argspec.num_args + 1, argspec.num_keywords, argspec.has_starargs, argspec.has_kwargs),
rtn = runtimeCallInternal(val, &srewrite_args, ArgPassSpec(argspec.num_args + 1, argspec.num_keywords,
argspec.has_starargs, argspec.has_kwargs),
obj, arg1, arg2, NULL, keyword_names);
if (!srewrite_args.out_success) {
......@@ -1474,7 +1818,7 @@ extern "C" Box* callattrInternal(Box* obj, const std::string* attr, LookupScope
rewrite_args->out_rtn = std::move(srewrite_args.out_rtn);
}
} else {
rtn = runtimeCallInternal(clsattr, NULL, ArgPassSpec(argspec.num_args + 1, argspec.num_keywords,
rtn = runtimeCallInternal(val, NULL, ArgPassSpec(argspec.num_args + 1, argspec.num_keywords,
argspec.has_starargs, argspec.has_kwargs),
obj, arg1, arg2, NULL, keyword_names);
}
......@@ -1491,49 +1835,7 @@ extern "C" Box* callattrInternal(Box* obj, const std::string* attr, LookupScope
Box* rtn;
if (rewrite_args) {
// const bool annotate = 0;
// if (annotate)
// rewrite_args->rewriter->trap();
// if (VERBOSITY()) printf("have to remunge: %d %d %d %d\n", rewrite_args->arg1.getArgnum(),
// rewrite_args->arg2.getArgnum(), rewrite_args->arg3.getArgnum(), rewrite_args->args.getArgnum());
// The above line seems to print one of:
// 4 5 6 7
// 2 3 4 5
// Want to move them to
// 1 2 X X
// if (npassed_args >= 1) rewrite_args->arg1 = rewrite_args->arg1.move(1);
// if (npassed_args >= 2) rewrite_args->arg2 = rewrite_args->arg2.move(2);
// if (npassed_args >= 3) rewrite_args->arg3 = rewrite_args->arg3.move(4);
// if (npassed_args >= 4) rewrite_args->args = rewrite_args->args.move(5);
// int new_alloca_reg = -3;
// RewriterVar r_new_args = rewrite_args->rewriter->alloca_(alloca_size, new_alloca_reg);
// r_clsattr.push();
// if (rewrite_args->arg3.isInReg())
// r_new_args.setAttr(0, rewrite_args->arg3, /* user_visible = */ false);
// else {
// r_new_args.setAttr(0, rewrite_args->arg3.move(-2), /* user_visible = */ false);
//}
// arg3 is now dead
// for (int i = 0; i < npassed_args - 3; i++) {
// RewriterVar arg;
// if (rewrite_args->args.isInReg())
// arg = rewrite_args->args.getAttr(i * sizeof(Box*), -2);
// else {
// // TODO this is really bad:
// arg = rewrite_args->args.move(-2).getAttr(i * sizeof(Box*), -2);
// }
// r_new_args.setAttr((i + 1) * sizeof(Box*), arg, /* user_visible = */ false);
//}
// args is now dead
// RewriterVarUsage r_new_args = rewrite_args->alloc
CallRewriteArgs srewrite_args(rewrite_args->rewriter, std::move(r_clsattr), rewrite_args->destination,
CallRewriteArgs srewrite_args(rewrite_args->rewriter, std::move(r_val), rewrite_args->destination,
rewrite_args->more_guards_after);
srewrite_args.arg1 = std::move(rewrite_args->obj);
srewrite_args.arg2 = std::move(rewrite_args->arg1);
......@@ -1545,14 +1847,9 @@ extern "C" Box* callattrInternal(Box* obj, const std::string* attr, LookupScope
srewrite_args.args_guarded = true;
srewrite_args.func_guarded = true;
// if (annotate)
// rewrite_args->rewriter->annotate(0);
rtn = runtimeCallInternal(
clsattr, &srewrite_args,
ArgPassSpec(argspec.num_args + 1, argspec.num_keywords, argspec.has_starargs, argspec.has_kwargs),
rtn = runtimeCallInternal(val, &srewrite_args, ArgPassSpec(argspec.num_args + 1, argspec.num_keywords,
argspec.has_starargs, argspec.has_kwargs),
obj, arg1, arg2, new_args, keyword_names);
// if (annotate)
// rewrite_args->rewriter->annotate(1);
if (!srewrite_args.out_success) {
rewrite_args = NULL;
......@@ -1561,10 +1858,8 @@ extern "C" Box* callattrInternal(Box* obj, const std::string* attr, LookupScope
rewrite_args->out_success = true;
}
// if (annotate)
// rewrite_args->rewriter->annotate(2);
} else {
rtn = runtimeCallInternal(clsattr, NULL, ArgPassSpec(argspec.num_args + 1, argspec.num_keywords,
rtn = runtimeCallInternal(val, NULL, ArgPassSpec(argspec.num_args + 1, argspec.num_keywords,
argspec.has_starargs, argspec.has_kwargs),
obj, arg1, arg2, new_args, keyword_names);
}
......@@ -1576,18 +1871,13 @@ extern "C" Box* callattrInternal(Box* obj, const std::string* attr, LookupScope
}
Box* rtn;
if (clsattr->cls != function_cls) {
if (val->cls != function_cls && val->cls != instancemethod_cls) {
rewrite_args = NULL;
r_clsattr.ensureDoneUsing();
r_val.ensureDoneUsing();
}
auto old_clsattr = clsattr;
clsattr = _handleClsAttr(obj, clsattr);
if (clsattr != old_clsattr)
rewrite_args = NULL;
if (rewrite_args) {
CallRewriteArgs srewrite_args(rewrite_args->rewriter, std::move(r_clsattr), rewrite_args->destination,
CallRewriteArgs srewrite_args(rewrite_args->rewriter, std::move(r_val), rewrite_args->destination,
rewrite_args->more_guards_after);
if (npassed_args >= 1)
srewrite_args.arg1 = std::move(rewrite_args->arg1);
......@@ -1599,7 +1889,7 @@ extern "C" Box* callattrInternal(Box* obj, const std::string* attr, LookupScope
srewrite_args.args = std::move(rewrite_args->args);
srewrite_args.args_guarded = true;
rtn = runtimeCallInternal(clsattr, &srewrite_args, argspec, arg1, arg2, arg3, args, keyword_names);
rtn = runtimeCallInternal(val, &srewrite_args, argspec, arg1, arg2, arg3, args, keyword_names);
if (!srewrite_args.out_success) {
rewrite_args = NULL;
......@@ -1607,11 +1897,11 @@ extern "C" Box* callattrInternal(Box* obj, const std::string* attr, LookupScope
rewrite_args->out_rtn = std::move(srewrite_args.out_rtn);
}
} else {
rtn = runtimeCallInternal(clsattr, NULL, argspec, arg1, arg2, arg3, args, keyword_names);
rtn = runtimeCallInternal(val, NULL, argspec, arg1, arg2, arg3, args, keyword_names);
}
if (!rtn) {
raiseExcHelper(TypeError, "'%s' object is not callable", getTypeName(clsattr)->c_str());
raiseExcHelper(TypeError, "'%s' object is not callable", getTypeName(val)->c_str());
}
if (rewrite_args)
......@@ -2117,8 +2407,6 @@ Box* runtimeCallInternal(Box* obj, CallRewriteArgs* rewrite_args, ArgPassSpec ar
Box** args, const std::vector<const std::string*>* keyword_names) {
int npassed_args = argspec.totalPassed();
Box* orig_obj = obj;
if (obj->cls != function_cls && obj->cls != instancemethod_cls) {
Box* rtn;
if (rewrite_args) {
......@@ -2178,6 +2466,26 @@ Box* runtimeCallInternal(Box* obj, CallRewriteArgs* rewrite_args, ArgPassSpec ar
rewrite_args->obj.addAttrGuard(INSTANCEMETHOD_FUNC_OFFSET, (intptr_t)im->func);
}
// Guard on which type of instancemethod (bound or unbound)
// That is, if im->obj is NULL, guard on it being NULL
// otherwise, guard on it being non-NULL
if (rewrite_args) {
rewrite_args->obj.addAttrGuard(INSTANCEMETHOD_OBJ_OFFSET, 0, im->obj != NULL);
}
// TODO guard on im->obj being NULL or not
if (im->obj == NULL) {
Box* f = im->func;
if (rewrite_args) {
rewrite_args->func_guarded = true;
rewrite_args->args_guarded = true;
rewrite_args->obj
= rewrite_args->obj.getAttr(INSTANCEMETHOD_FUNC_OFFSET, RewriterVarUsage::Kill, Location::any());
}
Box* res = runtimeCallInternal(f, rewrite_args, argspec, arg1, arg2, arg3, args, keyword_names);
return res;
}
if (npassed_args <= 2) {
Box* rtn;
if (rewrite_args) {
......@@ -2245,8 +2553,6 @@ extern "C" Box* runtimeCall(Box* obj, ArgPassSpec argspec, Box* arg1, Box* arg2,
Box* rtn;
if (rewriter.get()) {
// rewriter->trap();
// TODO feel weird about doing this; it either isn't necessary
// or this kind of thing is necessary in a lot more places
// rewriter->getArg(1).addGuard(npassed_args);
......@@ -2283,8 +2589,6 @@ extern "C" Box* binopInternal(Box* lhs, Box* rhs, int op_type, bool inplace, Bin
// of the other!
if (rewrite_args) {
// rewriter->trap();
// TODO probably don't need to guard on the lhs_cls since it
// will get checked no matter what, but the check that should be
// removed is probably the later one.
......@@ -2304,9 +2608,9 @@ extern "C" Box* binopInternal(Box* lhs, Box* rhs, int op_type, bool inplace, Bin
srewrite_args.args_guarded = true;
irtn = callattrInternal1(lhs, &iop_name, CLASS_ONLY, &srewrite_args, ArgPassSpec(1), rhs);
if (!srewrite_args.out_success)
if (!srewrite_args.out_success) {
rewrite_args = NULL;
else if (irtn) {
} else if (irtn) {
if (irtn == NotImplemented)
srewrite_args.out_rtn.ensureDoneUsing();
else
......@@ -2457,15 +2761,15 @@ extern "C" Box* augbinop(Box* lhs, Box* rhs, int op_type) {
Box* rtn;
if (rewriter.get()) {
// rewriter->trap();
BinopRewriteArgs rewrite_args(rewriter.get(), rewriter->getArg(0), rewriter->getArg(1),
rewriter->getReturnDestination(), false);
rtn = binopInternal(lhs, rhs, op_type, true, &rewrite_args);
if (!rewrite_args.out_success) {
rewrite_args.ensureAllDone();
rewriter.reset(NULL);
} else
} else {
rewriter->commitReturning(std::move(rewrite_args.out_rtn));
}
} else {
rtn = binopInternal(lhs, rhs, op_type, true, NULL);
}
......@@ -2805,10 +3109,10 @@ extern "C" void delattr_internal(Box* obj, const std::string& attr, bool allow_c
}
}
// first check wether the deleting attribute is a descriptor
// first check whether the deleting attribute is a descriptor
Box* clsAttr = typeLookup(obj->cls, attr, NULL);
if (clsAttr != NULL) {
Box* delAttr = getattr_internal(clsAttr, delete_str, false, true, NULL);
Box* delAttr = typeLookup(static_cast<BoxedClass*>(clsAttr->cls), delete_str, NULL);
if (delAttr != NULL) {
Box* boxstr = boxString(attr);
......@@ -2818,7 +3122,7 @@ extern "C" void delattr_internal(Box* obj, const std::string& attr, bool allow_c
}
// check if the attribute is in the instance's __dict__
Box* attrVal = getattr_internal(obj, attr, false, false, NULL);
Box* attrVal = obj->getattr(attr, NULL);
if (attrVal != NULL) {
obj->delattr(attr, NULL);
} else {
......
......@@ -102,8 +102,9 @@ extern "C" void delattr_internal(Box* obj, const std::string& attr, bool allow_c
DelattrRewriteArgs* rewrite_args);
struct CompareRewriteArgs;
Box* compareInternal(Box* lhs, Box* rhs, int op_type, CompareRewriteArgs* rewrite_args);
Box* getattr_internal(Box* obj, const std::string& attr, bool check_cls, bool allow_custom,
GetattrRewriteArgs* rewrite_args);
Box* getattrInternal(Box* obj, const std::string& attr, GetattrRewriteArgs* rewrite_args);
Box* getattrInternalGeneral(Box* obj, const std::string& attr, GetattrRewriteArgs* rewrite_args, bool cls_only,
bool for_call, bool* should_bind_out);
Box* typeLookup(BoxedClass* cls, const std::string& attr, GetattrRewriteArgs* rewrite_args);
......
......@@ -203,7 +203,9 @@ extern "C" void typeGCHandler(GCVisitor* v, Box* b) {
extern "C" void instancemethodGCHandler(GCVisitor* v, Box* b) {
BoxedInstanceMethod* im = (BoxedInstanceMethod*)b;
if (im->obj) {
v->visit(im->obj);
}
v->visit(im->func);
}
......@@ -331,6 +333,10 @@ extern "C" Box* boxInstanceMethod(Box* obj, Box* func) {
return new BoxedInstanceMethod(obj, func);
}
extern "C" Box* boxUnboundInstanceMethod(Box* func) {
return new BoxedInstanceMethod(NULL, func);
}
extern "C" BoxedString* noneRepr(Box* v) {
return new BoxedString("None");
}
......@@ -412,6 +418,27 @@ Box* instancemethodRepr(BoxedInstanceMethod* self) {
return boxStrConstant("<unbound instancemethod object>");
}
Box* instancemethodEq(BoxedInstanceMethod* self, Box* rhs) {
if (rhs->cls != instancemethod_cls) {
return boxBool(false);
}
BoxedInstanceMethod* rhs_im = static_cast<BoxedInstanceMethod*>(rhs);
if (self->func == rhs_im->func) {
if (self->obj == NULL && rhs_im->obj == NULL) {
return boxBool(true);
} else {
if (self->obj != NULL && rhs_im->obj != NULL) {
return compareInternal(self->obj, rhs_im->obj, AST_TYPE::Eq, NULL);
} else {
return boxBool(false);
}
}
} else {
return boxBool(false);
}
}
Box* sliceRepr(BoxedSlice* self) {
BoxedString* start = static_cast<BoxedString*>(repr(self->start));
BoxedString* stop = static_cast<BoxedString*>(repr(self->stop));
......@@ -701,6 +728,7 @@ void setupRuntime() {
instancemethod_cls->giveAttr("__name__", boxStrConstant("instancemethod"));
instancemethod_cls->giveAttr("__repr__", new BoxedFunction(boxRTFunction((void*)instancemethodRepr, STR, 1)));
instancemethod_cls->giveAttr("__eq__", new BoxedFunction(boxRTFunction((void*)instancemethodEq, UNKNOWN, 2)));
instancemethod_cls->freeze();
slice_cls->giveAttr("__name__", boxStrConstant("slice"));
......
......@@ -90,6 +90,7 @@ extern "C" Box* boxInt(i64);
extern "C" i64 unboxInt(Box*);
extern "C" Box* boxFloat(double d);
extern "C" Box* boxInstanceMethod(Box* obj, Box* func);
extern "C" Box* boxUnboundInstanceMethod(Box* func);
extern "C" Box* boxStringPtr(const std::string* s);
Box* boxString(const std::string& s);
Box* boxString(std::string&& s);
......@@ -212,6 +213,7 @@ public:
class BoxedInstanceMethod : public Box {
public:
// obj is NULL for unbound instancemethod
Box* obj, *func;
BoxedInstanceMethod(Box* obj, Box* func) __attribute__((visibility("default")))
......@@ -331,7 +333,8 @@ public:
int offset;
BoxedMemberDescriptor(MemberType type, int offset) : Box(member_cls), type(type), offset(offset) {}
BoxedMemberDescriptor(PyMemberDef* member) : Box(member_cls), type((MemberType)member->type), offset(member->offset) {}
BoxedMemberDescriptor(PyMemberDef* member)
: Box(member_cls), type((MemberType)member->type), offset(member->offset) {}
};
// TODO is there any particular reason to make this a Box, ie a python-level object?
......
# expected: fail
# Regression test:
# If the init function doesn't exist, shouldn't just silently ignore any args
# that got passed
......
class DataDescriptor(object):
def __get__(self, obj, type):
print "__get__ called"
if obj != None:
return obj.a_store
def __set__(self, obj, value):
print "__set__ called with value", value
obj.a_store = value + 1
class NonDataDescriptor(object):
def __get__(self, obj, type):
return 2
# Don't define __set__
class C(object):
dd = DataDescriptor()
ndd = NonDataDescriptor()
inst = C()
print 'ndd is %s' % str(inst.ndd)
inst.dd = 14
print 'dd is %s' % str(inst.dd)
inst.ndd = 20
print inst.ndd # should print out 20, having overridden the NonDataDescriptor
inst.dd2 = 99
C.dd2 = DataDescriptor()
print 'inst.dd2 is %s' % str(inst.dd2)
print 'C.dd is %s' % str(C.dd)
print 'C.ndd is %s' % str(C.ndd)
C.dd = 6
C.ndd = 7
#TODO it would be nice to print these out (once __dict__ is implemented)
#print C.__dict__['dd']
#print C.__dict__['ndd']
print c.dd
print c.ndd
# Repeat all of the above for subclasses of the descriptors
class SubDataDescriptor(DataDescriptor):
pass
class SubNonDataDescriptor(NonDataDescriptor):
pass
class D(object):
dd = SubDataDescriptor()
ndd = SubNonDataDescriptor()
inst = D()
print 'ndd is %d' % inst.ndd
inst.dd = 14
print 'dd is %d' % inst.dd
inst.ndd = 20
print inst.ndd # should print out 20, having overridden the NonDataDescriptor
inst.dd2 = 99
C.dd2 = DataDescriptor()
print 'inst.dd2 is %s' % str(inst.dd2)
print 'D.dd is %s' % str(D.dd)
print 'D.ndd is %s' % str(D.ndd)
D.dd = 6
D.ndd = 7
#print D.__dict__['dd']
#print D.__dict__['ndd']
class DataDescriptor(object):
def __get__(self, obj, type): return 1
def __set__(self, obj, value): pass
class NonDataDescriptor(object):
def __get__(self, obj, type): return 2
# Don't define __set__
class C1(object):
a = DataDescriptor()
class D1(C1):
a = 3
d1 = D1()
print d1.a
print 'D1.a is %s' % str(D1.a)
D1.a = 6
#print D1.__dict__['a']
class C2(object):
a = 4
class D2(C2):
a = DataDescriptor()
d2 = D2()
print d2.a
print 'D2.a is %s' % str(D2.a)
D2.a = 6
#print D2.__dict__['a']
class C3(object):
a = NonDataDescriptor()
class D3(C3):
a = 5
d3 = D3()
print d3.a
print 'D3.a is %s' % str(D3.a)
D3.a = 6
#print D3.__dict__['a']
class C4(object):
a = 6
class D4(C4):
a = NonDataDescriptor()
d4 = D4()
#print d4.a
print 'D4.a is %s' % str(D4.a)
D4.a = 6
#print D4.__dict__['a']
class DataDescriptor(object):
def __get__(self, obj, type):
if obj != None:
return obj.a_store
else:
def f():
print '__get__ function called'
return 100
return f
def __set__(self, obj, value):
obj.a_store = value
class NonDataDescriptor(object):
def __get__(self, obj, type):
def f():
print '__get__ function called'
return 1
return f
# Don't define __set__
class C(object):
dd = DataDescriptor()
ndd = NonDataDescriptor()
inst = C()
print 'ndd() is %d' % inst.ndd()
inst.dd = lambda : 14
print 'dd() is %d' % inst.dd()
inst.ndd = lambda : 20
print inst.ndd() # should print out 20, having overridden the NonDataDescriptor
inst.dd2 = lambda : 99
C.dd2 = DataDescriptor()
print 'inst.dd2 is %s' % str(inst.dd2())
print 'C.dd() is %s' % str(C.dd())
print 'C.ndd() is %s' % str(C.ndd())
C.dd = lambda : 6
C.ndd = lambda : 7
#TODO uncomment these
#print C.__dict__['dd']()
#print C.__dict__['ndd']()
# Repeat all of the above for subclasses of the descriptors
class SubDataDescriptor(DataDescriptor):
pass
class SubNonDataDescriptor(NonDataDescriptor):
pass
class D(object):
dd = SubDataDescriptor()
ndd = SubNonDataDescriptor()
inst = D()
print 'ndd() is %d' % inst.ndd()
inst.dd = lambda : 14
print 'dd() is %d' % inst.dd()
inst.ndd = lambda : 20
print inst.ndd() # should print out 20, having overridden the NonDataDescriptor
inst.dd2 = lambda : 99
C.dd2 = DataDescriptor()
print 'inst.dd2 is %s' % str(inst.dd2())
print 'D.dd() is %s' % str(D.dd())
print 'D.ndd() is %s' % str(D.ndd())
D.dd = lambda : 6
D.ndd = lambda : 7
#print D.__dict__['dd']()
#print D.__dict__['ndd']()
class DataDescriptor(object):
def __get__(self, obj, type): return (lambda : 1)
def __set__(self, obj, value): pass
class NonDataDescriptor(object):
def __get__(self, obj, type): return (lambda : 2)
# Don't define __set__
class C1(object):
a = DataDescriptor()
class D1(C1):
a = lambda self : 3
d1 = D1()
print d1.a()
print 'D1.a() is %s' % str(D1.a(d1))
D1.a = lambda : 6
#print D1.__dict__['a']()
class C2(object):
a = lambda self : 4
class D2(C2):
a = DataDescriptor()
d2 = D2()
print d2.a()
print 'D2.a() is %s' % str(D2.a())
D2.a = lambda : 6
#print D2.__dict__['a']()
class C3(object):
a = NonDataDescriptor()
class D3(C3):
a = lambda self : 5
d3 = D3()
print d3.a()
print 'D3.a() is %s' % str(D3.a(d3))
D3.a = lambda : 6
#print D3.__dict__['a']()
class C4(object):
a = lambda self : 6
class D4(C4):
a = NonDataDescriptor()
d4 = D4()
print d4.a()
print 'D4.a() is %s' % str(D4.a())
D4.a = lambda : 6
#print D4.__dict__['a']()
class DataDescriptor(object):
def __get__(self, obj, type):
print "__get__ called"
if obj != None:
return obj.a_store
def __set__(self, obj, value):
print "__set__ called with value", value
obj.a_store = value + 1
class NonDataDescriptor(object):
def __get__(self, obj, type):
return 2
# Don't define __set__
class C(object):
# see what happens when we just use the class of the descriptor rather than an instance
dd = DataDescriptor
ndd = NonDataDescriptor
inst = C()
print 'ndd is %s' % str(inst.ndd)
inst.dd = 14
print 'dd is %s' % str(inst.dd)
inst.ndd = 20
print inst.ndd # should print out 20, having overridden the NonDataDescriptor
print 'C.dd is %s' % str(C.dd)
print 'C.ndd is %s', str(C.ndd)
C.dd = 6
C.ndd = 7
#TODO uncomment these:
#print C.__dict__['dd']
#print C.__dict__['ndd']
# expected: statfail
# run_args: -n
# statcheck: stats['slowpath_callattr'] <= 100
# Right now this won't work because callattr involves two calls
# one call to __get__ and then another call to the returned function.
# Of course, if the callattr were split up into getattr and a call,
# each could be re-written separately...
# Not sure if this is a case worth handling or what is the best way
# to handle it, but I'm throwing the test in here anyway to remind us.
# Note: difference between DataDescriptor and NonDataDescriptor shouldn't matter
# __enter__ is looked up via callattr with class_only set to true
class DataDescriptor(object):
def __get__(self, obj, typ):
print '__get__ called'
print type(self)
print type(obj)
print typ
def enter():
print 'enter called (1)'
return enter
def __set__(self, obj, value):
pass
class NonDataDescriptor(object):
def __get__(self, obj, typ):
print '__get__ called'
print type(self)
print type(obj)
print typ
def enter():
print 'enter called (2)'
return enter
class C1(object):
__enter__ = DataDescriptor()
def __exit__(self, typ, value, traceback):
pass
class C2(object):
__enter__ = NonDataDescriptor()
def __exit__(self, typ, value, traceback):
pass
class C3(object):
__enter__ = NonDataDescriptor()
def __exit__(self, typ, value, traceback):
pass
class C4(object):
def __exit__(self, typ, value, traceback):
pass
c1 = C1()
c2 = C2()
c3 = C3()
c4 = C4()
def enter3(type):
print 'enter called (3)'
c3.__enter__ = enter3 # this should not get called
def enter4(type):
print 'enter called (4)'
c4.__enter__ = enter4 # this should not get called
C4.__enter__ = DataDescriptor()
def f():
with c1:
print 'in with statement (1)'
with c2:
print 'in with statement (2)'
with c3:
print 'in with statement (3)'
with c4:
print 'in with statement (4)'
for i in xrange(1000):
f()
# expected: statfail
# run_args: -n
# statcheck: stats['slowpath_callattr'] <= 80
# statcheck: stats['slowpath_getattr'] <= 80
# Right now this won't work because callattr involves two calls
# one call to __get__ and then another call to the returned function.
# Of course, if the callattr were split up into getattr and a call,
# each could be re-written separately...
# Not sure if this is a case worth handling or what is the best way
# to handle it, but I'm throwing the test in here anyway to remind us.
def g():
print 'in g'
return 0
def h():
print 'in h'
return 1
class DataDescriptor(object):
def __get__(self, obj, typ):
print '__get__ called'
print type(self)
print type(obj)
print typ
return g
def __set__(self, obj, value):
pass
class NonDataDescriptor(object):
def __get__(self, obj, typ):
print '__get__ called'
print type(self)
print type(obj)
print typ
return g
class C(object):
a = DataDescriptor()
b = NonDataDescriptor()
c = NonDataDescriptor()
inst = C()
inst.c = h
inst.d = h
C.d = DataDescriptor()
def f():
print inst.a()
print inst.b()
print inst.c()
print inst.d()
print C.a()
print C.b()
for i in xrange(1000):
f()
# TODO This is a hodgepodge of stuff, should probably organize it better
# maybe merge some of it into dunder_descriptors?
class Descriptor2(object):
def __get__(self, obj, type):
def get2(self, obj, type):
print '__get__ called'
print self
print obj
print type
return get2
class Descriptor(object):
__get__ = Descriptor2()
class DescriptorNonzero(object):
def __get__(self, obj, type):
print 'nonzero __get__ called'
def nonzero():
print 'nonzero called'
return True
return nonzero
class C(object):
desc = Descriptor()
__nonzero__ = DescriptorNonzero()
# Should throw type error: Descriptor2 is not callable
try:
print C().desc
except TypeError:
# TODO should print the whole stacktrace and error message probably
print 'got type error (1)'
# Should print True; in particular, it should look up __nonzero__
# using the descriptor protocol
if C():
print 'True'
else:
print 'False'
# this should *not* override it
c = C()
c.__nonzero__ = lambda x : False
if c:
print 'True'
else:
print 'False'
# this should
C.__nonzero__ = lambda x : False
if c:
print 'True'
else:
print 'False'
# __getattr__ and __setattr__
# Looks like __getattr__ and __setattr__ should *not* be looked up with
# the descriptor protocol
class DescriptorGetattr(object):
def __get__(self, obj, type):
print 'getattr __get__ called'
def getattr(attr):
print 'getattr called for attr', attr
return 1337
return getattr
class DescriptorSetattr(object):
def __get__(self, obj, type):
print 'setattr __get__ called'
def setattr(attr, val):
print 'setattr called for attr', attr, val
class D(object):
__getattr__ = DescriptorGetattr
__setattr__ = DescriptorSetattr
d = D()
try:
print d.a
except TypeError:
print 'got type error (2)'
#TODO enable this once __setattr__ is implemented
#try:
# d.b = 12
#except TypeError:
# print 'got type error (3)'
# with, __enter__ and __exit__
class DescriptorEnter(object):
def __get__(self, obj, type):
print 'enter __get__ called'
def enter():
print 'enter called'
return 1337
return enter
class DescriptorExit(object):
def __get__(self, obj, type):
print 'exit __get__ called'
def exit(type, value, traceback):
print 'enter called'
return 1337
return exit
class E(object):
__enter__ = DescriptorEnter()
__exit__ = DescriptorExit()
with E():
print 'in with'
# what if __get__ is just an instance attribute
class Descriptor(object):
pass
desc = Descriptor()
desc.__get__ = lambda self, obj, type : 5
def s(self, obj, value):
print 'in __set__'
desc.__set__ = s
class F(object):
at = desc
f = F()
print type(f.at) # should not call __get__
f.at = 12 # should not call __set__, should print nothing
#TODO uncomment this:
#print f.__dict__['at']
# run_args: -n
# statcheck: stats['slowpath_getattr'] <= 80
class DataDescriptor(object):
def __get__(self, obj, typ):
print '__get__ called'
print type(self)
print type(obj)
print typ
return 0
def __set__(self, obj, value):
pass
class NonDataDescriptor(object):
def __get__(self, obj, typ):
print '__get__ called'
print type(self)
print type(obj)
print typ
return 1
class C(object):
a = DataDescriptor()
b = NonDataDescriptor()
c = NonDataDescriptor()
inst = C()
inst.c = 100
inst.d = 101
C.d = DataDescriptor()
def f():
print inst.a
print inst.b
print inst.c
print inst.d
print C.a
print C.b
for i in xrange(1000):
f()
# run_args: -n
# statcheck: stats['slowpath_getclsattr'] <= 60
# Note: difference between DataDescriptor and NonDataDescriptor shouldn't matter
# for getclsattr (which is how __exit__ is looked up for with statements)
class DataDescriptor(object):
def __get__(self, obj, typ):
print '__get__ called'
print type(self)
print type(obj)
print typ
def exit(type, value, traceback):
print 'exit called (1)'
return exit
def __set__(self, obj, value):
pass
class NonDataDescriptor(object):
def __get__(self, obj, typ):
print '__get__ called'
print type(self)
print type(obj)
print typ
def exit(type, value, traceback):
print 'exit called (2)'
return exit
class C1(object):
__exit__ = DataDescriptor()
def __enter__(self):
pass
class C2(object):
__exit__ = NonDataDescriptor()
def __enter__(self):
pass
class C3(object):
__exit__ = NonDataDescriptor()
def __enter__(self):
pass
class C4(object):
def __enter__(self):
pass
c1 = C1()
c2 = C2()
c3 = C3()
c4 = C4()
def exit3(type, value, traceback):
print 'exit called (3)'
c3.__exit__ = exit3 # this should not get called
def exit4(type, value, traceback):
print 'exit called (4)'
c4.__exit__ = exit4 # this should not get called
C4.__exit__ = DataDescriptor()
def f():
with c1:
print 'in with statement (1)'
with c2:
print 'in with statement (2)'
with c3:
print 'in with statement (3)'
with c4:
print 'in with statement (4)'
for i in xrange(1000):
f()
# All the ways of invalidating getattr
# TODO should test getclsattr as well
# TODO should also test some crazier stuff, like descriptors with inheritance
def get(self, obj, typ):
print '__get__ called'
print type(self)
print type(obj)
print typ
return self.elem
def set(self, obj, typ):
print '__get__ called'
print type(self)
print type(obj)
print typ
class Descriptor(object):
def __init__(self, elem):
self.elem = elem
def __str__(self):
return 'Descriptor object'
class C(object):
a = Descriptor(0)
b = Descriptor(lambda : 0)
c = C()
def f():
print c.a
print C.a
def g():
try:
print c.b()
except TypeError:
print 'got TypeError'
try:
print C.b()
except TypeError:
print 'got TypeError'
def h():
c.c = 10
for i in xrange(2000):
f()
g()
h()
if i == 50:
Descriptor.__get__ = get
if i == 100:
Descriptor.__set__ = set
if i == 150:
del Descriptor.__get__
if i == 200:
del Descriptor.__set__
if i == 250:
Descriptor.__set__ = set
if i == 300:
Descriptor.__get__ = get
if i == 350:
del Descriptor.__set__
if i == 400:
del Descriptor.__get__
if i == 450:
Descriptor.__get__ = get
Descriptor.__set__ = set
if i == 500:
del Descriptor.__get__
del Descriptor.__set__
if i == 550:
Descriptor.__get__ = get
if i == 600:
Descriptor.__set__ = set
del Descriptor.__get__
if i == 650:
Descriptor.__get__ = get
del Descriptor.__set__
if i == 700:
c.a = 5
c.b = lambda : 5
if i == 750:
del c.a
del c.b
if i == 800:
Descriptor.__set__ = set
if i == 850:
del Descriptor.__set__
c.a = 5
c.b = lambda : 5
Descriptor.__set__ = set
if i == 900:
del Descriptor.__set__
del c.a
del c.b
Descriptor.__set__ = set
if i == 950:
del Descriptor.__get__
if i == 1000:
del Descriptor.__set__
c.a = 5
c.b = lambda : 5
Descriptor.__set__ = set
if i == 1050:
del Descriptor.__set__
del c.a
del c.b
Descriptor.__set__ = set
if i == 1100:
del Descriptor.__set__
if i == 1150:
c.a = 5
c.b = lambda : 5
if i == 1200:
del c.a
del c.b
if i == 1250:
c.a = 5
c.b = lambda : 5
if i == 1350:
Descriptor.__get__ = get
if i == 1400:
Descriptor.__set__ = set
if i == 1450:
del Descriptor.__get__
if i == 1500:
del Descriptor.__set__
if i == 1550:
Descriptor.__set__ = set
if i == 1600:
Descriptor.__get__ = get
if i == 1650:
del Descriptor.__set__
if i == 1700:
del Descriptor.__get__
if i == 1750:
Descriptor.__get__ = get
Descriptor.__set__ = set
if i == 1800:
del Descriptor.__get__
del Descriptor.__set__
if i == 1850:
Descriptor.__get__ = get
if i == 1900:
Descriptor.__set__ = set
del Descriptor.__get__
if i == 1950:
Descriptor.__get__ = get
del Descriptor.__set__
# expected: fail
# See what happens when we make __get__ and __set__ things other than functions...
# TODO add some with __del__
import traceback
class CallableGet(object):
def __call__(self, a, b, c):
print 'Callable get'
print self
print a
print b
print c
class CallableSet(object):
def __call__(a, b, c):
print 'Callable set'
print a
print b
print c
class InstanceMethodMaker(object):
def getBoundInstanceMethod(self, a, b, c):
print '__get__ bound'
print a
print b
print c
def setBoundInstanceMethod(a, b, c):
print '__set__ bound'
print a
print b
print c
def getUnboundInstanceMethod(a, b, c):
print '__get__ unbound'
print a
print b
print c
def setUnboundInstanceMethod(a, b, c):
print '__set__ unbound'
print a
print b
print c
imm = InstanceMethodMaker()
def closureGet():
a = 5
def f(b, c, d):
print 'closure __get__'
print a
print b
print c
print d
return f
def closureSet():
def f(b, c, d):
print 'closure __set__'
print a
print b
print c
print d
return f
class A(object):
# If __get__ or __set__ is an int
class DescGetInt(object):
__get__ = 1
descGetInt = DescGetInt()
class DescSetInt(object):
__set__ = 1
descSetInt = DescSetInt()
class DescGetSetInt(object):
def __get__(a, b, c):
print 'DescGetSetInt __get__ called'
print a
print b
print c
__set__ = 1
descGetSetInt = DescGetSetInt()
class DescGetCall(object):
__get__ = CallableGet()
descGetCall = DescGetCall()
class DescSetCall(object):
__set__ = CallableSet()
descSetCall = DescSetCall()
class DescGetSetCall(object):
def __get__(a, b, c):
print 'DescGetSetCall __get__ called'
print a
print b
print c
__set__ = CallableSet()
descGetSetCall = DescGetSetCall()
class DescGetBoundInstanceMethod(object):
__get__ = imm.getBoundInstanceMethod
descGetBoundInstanceMethod = DescGetBoundInstanceMethod()
class DescSetBoundInstanceMethod(object):
__set__ = imm.setBoundInstanceMethod
descSetBoundInstanceMethod = DescSetBoundInstanceMethod()
class DescGetSetBoundInstanceMethod(object):
def __get__(a, b, c):
print 'DescGetSetBoundInstanceMethod __get__ called'
print a
print b
print c
__set__ = imm.setBoundInstanceMethod
descGetSetBoundInstanceMethod = DescGetSetBoundInstanceMethod()
class DescGetUnboundInstanceMethod(object):
__get__ = InstanceMethodMaker.getUnboundInstanceMethod
descGetUnboundInstanceMethod = DescGetUnboundInstanceMethod()
class DescSetUnboundInstanceMethod(object):
__set__ = InstanceMethodMaker.setUnboundInstanceMethod
descSetUnboundInstanceMethod = DescSetUnboundInstanceMethod()
class DescGetSetUnboundInstanceMethod(object):
def __get__(a, b, c):
print 'DescGetSetUnboundInstanceMethod __get__ called'
print a
print b
print c
__set__ = imm.setUnboundInstanceMethod
descGetSetUnboundInstanceMethod = DescGetSetUnboundInstanceMethod()
class DescGetClosure(object):
__get__ = closureGet()
descGetClosure = DescGetClosure()
class DescSetClosure(object):
__set__ = closureSet()
descSetClosure = DescSetClosure()
class DescGetSetClosure(object):
def __get__(a, b, c):
print 'DescGetSetClosure __get__ called'
print a
print b
print c
__set__ = closureSet()
descGetSetClosure = DescGetSetClosure()
class DescGetGenerator(object):
def __get__(self, obj, type):
print 'DescGetGenerator __get__ called'
print self
print obj
print type
yield 15
print '__get__ post yield'
descGetGenerator = DescGetGenerator()
class DescSetGenerator(object):
def __set__(self, obj, value):
print 'DescSetGenerator __set__ called'
print self
print obj
print value
yield 15
print '__set__ post yield'
descSetGenerator = DescSetGenerator()
class DescGetSetGenerator(object):
def __get__(a, b, c):
print 'DescGetSetGenerator __get__ called'
print a
print b
print c
def __set__(self, obj, value):
print 'DescGetSetGenerator __set__ called'
print self
print obj
print value
yield 15
print 'DescGetSetGenerator __set__ post yield'
descGetSetGenerator = DescGetSetGenerator()
descSetClosure = DescSetClosure()
a = A()
print 'int'
try:
print a.descGetInt
except:
traceback.print_exc()
try:
a.descSetInt = 5
except:
traceback.print_exc()
a.__dict__['descGetSetInt'] = 3
print a.descGetSetInt
print 'object with __call__'
print a.descGetCall
a.descSetCall = 5
a.__dict__['descGetSetCall'] = 3
print a.descGetSetCall
print 'bound instance method'
print a.descGetBoundInstanceMethod
a.descSetBoundInstanceMethod = 5
a.__dict__['descGetSetBoundInstanceMethod'] = 3
print a.descGetSetBoundInstanceMethod
print 'unbound instance method'
try:
print a.descGetUnboundInstanceMethod
except:
traceback.print_exc()
try:
a.descSetUnboundInstanceMethod = 5
except:
traceback.print_exc()
a.__dict__['descGetSetUnboundInstanceMethod'] = 3
print a.descGetSetUnboundInstanceMethod
print 'closure'
print a.descGetClosure
a.descSetClosure = 5
a.__dict__['descGetSetClosure'] = 3
print a.descGetClosure
print 'generator'
print a.descGetGenerator
a.descSetGenerator = 5
a.__dict__['descGetSetGenerator'] = 3
print a.descGetGenerator
# run_args: -n
# statcheck: stats['slowpath_setattr'] <= 120
class Descriptor(object):
def __set__(self, obj, value):
print '__set__ called'
print type(self)
print type(obj)
print type(value)
class C(object):
a = Descriptor()
c = C()
def f(i):
c.a = i
for i in xrange(1000):
f(i)
# expected: fail
# - descriptors
# Descriptors get processed when fetched as part of a dunder lookup
......
# TODO test all of this with getclsattr
# TODO should make an ics test
class C(object):
def f():
pass
def g():
print 'running g'
print C.f == C.f
print C.f is C.f
print C().f == C().f
print C().f is C().f
#### Check the types of stuff
print type(C.f) # instancemethod
print type(C().f) # instancemethod
print type(g) # function
C.g = g
print type(C.g) # instancemethod
print type(C().g) # instancemethod
#### Assign a function to an instance
c = C()
c.g = g
print type(c.g) # function
c.g()
print c.g == c.g
print c.g is c.g
#### Assign a function to a class
def l(inst):
print 'running l', inst.i
C.l = l
print type(C.l) #instancemethod
print type(C().l) #instancemethod
c1 = C()
c1.i = 1
C.l(c1)
c1.l()
print c1.l == c1.l
print c1.l is c1.l
print C.l == C.l
print C.l is C.l
#### Assign a bound instancemethod to a class
C.k = c1.l # set C.k to a bound instancemethod
C.k() # this should call l with with c1 as the arg
c2 = C()
c2.i = 2
c2.k() # this should just call l with c1 as the arg, not try to bind anything else
print type(C.k) # instancemethod
print type(c2.k) # instancemethod
print c2.k == c2.k
print c2.k is c2.k
print C.k == C.k
print C.k is C.k
print C.k == c2.k
print C.k is c2.k
#### Assign an unbound instancemethod to a class
#### Getting is will bind it like a normal function
# TODO implement instancemethod stuff so this case works
"""
C.m = C.l
print type(C.m) #instancemethod
print type(C().m) #instancemethod
c3 = C()
c3.i = 3
C.m(c3)
c3.m()
print c3.m == c3.m
print c3.m is c3.m
print C.m == C.m
print C.m is C.m
"""
### Assign a bound instancemethod to an instance
c4 = C()
c4.i = 4
c4.z = c1.l
print type(c4.z) # instancemethod
c4.z() # should call l(c1)
print c4.z == c4.z
print c4.z is c4.z
### Assign an unbound instancemethod to an instance
c4 = C()
c4.i = 4
c4.z = C.l
print type(c4.z) # instancemethod
c4.z(c1) # should call l(c1)
print c4.z == c4.z
print c4.z is c4.z
### Call a bound instancemethod on its own (not through the callattr path)
bound_instancemethod = c1.l
bound_instancemethod()
print type(bound_instancemethod)
### Call an unbound instancemethod on its own (not through the callattr path)
unbound_instancemethod = C.l
unbound_instancemethod(c2)
print type(unbound_instancemethod)
class C(object):
i = 5
def __getattr__(self, attr):
print '__getattr__ called on attr', attr
return 6
c = C()
print c.i # should print 5, not call __getattr__
class D(object):
i = 5
class E(D):
def __getattr__(self, attr):
print '__getattr__ called on attr', attr
return 6
pass
e = E()
print e.i # should print 5, not call __getattr__
# Test what happens when we swap out a function (which we handle special-cased
# in the descriptor logic) for a descriptor of another type
# TODO should be more thorough
# TODO should write similar tests for MemberDescriptors too
def g():
print 'in g'
class Descriptor(object):
def __str__(self):
return 'Descriptor'
def __get__(self, obj, objtype):
return g
class C(object):
def f(self):
print 'in f'
c = C()
def run():
c.f()
t = c.f
t()
for i in xrange(100):
run()
if i == 50:
C.f = Descriptor()
# Swap an unbound for a bound
class C(object):
def f(self, a=0):
print "f", a
def __str__(self):
return "C obj"
def call(f, c):
#TODO uncomment this
#print f
f(c)
c = C()
call(C.f, c)
call(c.f, c)
# expected: fail
# object.__new__ doesn't complain if __init__ is overridden:
class C1(object):
......
......@@ -197,7 +197,10 @@ def run_test(fn, check_stats, run_memcheck):
os.unlink(out_fn)
raise Exception("Failed on %s:\n%s" % (fn, diff))
elif not TEST_PYPY and canonicalize_stderr(stderr) != canonicalize_stderr(expected_err):
if KEEP_GOING:
if expected == "fail":
r += " Expected failure (bad stderr)"
return r
elif KEEP_GOING:
r += " \033[31mFAILED\033[0m (bad stderr)"
failed.append(fn)
return r
......
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