Commit 8b94bbc4 authored by Kevin Modzelewski's avatar Kevin Modzelewski

Support setting __dict__

It's trickier than just setting all of the attributes, since any updates
to either the original dict or to the object's attributes will get
mirrored in the other object.  I don't know if anyone uses this,
but I don't think there's any good way for us to tell if this is
going to happen so we just have to be conservative.

So, add a new distinction between "types" of hidden classes, and add
a new "dict backed" type.  Instead of having an array of attributes,
has a single attribute which is a dict.

There are some pretty tricky corner cases that we don't support yet,
such as if you access and save __dict__, then set __dict__, and then
try to access the saved version.  At least we can detect that and fail.
parent 415673dc
...@@ -658,7 +658,7 @@ BoxedDict* getLocals(bool only_user_visible, bool includeClosure) { ...@@ -658,7 +658,7 @@ BoxedDict* getLocals(bool only_user_visible, bool includeClosure) {
// Add the locals from the closure // Add the locals from the closure
for (; closure != NULL; closure = closure->parent) { for (; closure != NULL; closure = closure->parent) {
assert(closure->cls == closure_cls); assert(closure->cls == closure_cls);
for (auto& attr_offset : closure->attrs.hcls->attr_offsets) { for (auto& attr_offset : closure->attrs.hcls->getAttrOffsets()) {
const std::string& name = attr_offset.first(); const std::string& name = attr_offset.first();
int offset = attr_offset.second; int offset = attr_offset.second;
Box* val = closure->attrs.attr_list->attrs[offset]; Box* val = closure->attrs.attr_list->attrs[offset];
......
...@@ -414,7 +414,7 @@ struct DelattrRewriteArgs; ...@@ -414,7 +414,7 @@ struct DelattrRewriteArgs;
struct HCAttrs { struct HCAttrs {
public: public:
struct AttrList : public GCAllocated<gc::GCKind::PRECISE> { struct AttrList {
Box* attrs[0]; Box* attrs[0];
}; };
...@@ -428,6 +428,9 @@ class BoxedDict; ...@@ -428,6 +428,9 @@ class BoxedDict;
class BoxedString; class BoxedString;
class Box { class Box {
private:
BoxedDict** getDictPtr();
public: public:
// Add a no-op constructor to make sure that we don't zero-initialize cls // Add a no-op constructor to make sure that we don't zero-initialize cls
Box() {} Box() {}
...@@ -441,6 +444,7 @@ public: ...@@ -441,6 +444,7 @@ public:
llvm::iterator_range<BoxIterator> pyElements(); llvm::iterator_range<BoxIterator> pyElements();
HCAttrs* getHCAttrsPtr(); HCAttrs* getHCAttrsPtr();
void setDict(BoxedDict* d);
BoxedDict* getDict(); BoxedDict* getDict();
void setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite_args); void setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite_args);
......
...@@ -170,12 +170,12 @@ extern "C" Box* dir(Box* obj) { ...@@ -170,12 +170,12 @@ extern "C" Box* dir(Box* obj) {
result = new BoxedList(); result = new BoxedList();
} }
for (auto const& kv : obj->cls->attrs.hcls->attr_offsets) { for (auto const& kv : obj->cls->attrs.hcls->getAttrOffsets()) {
listAppend(result, boxString(kv.first())); listAppend(result, boxString(kv.first()));
} }
if (obj->cls->instancesHaveHCAttrs()) { if (obj->cls->instancesHaveHCAttrs()) {
HCAttrs* attrs = obj->getHCAttrsPtr(); HCAttrs* attrs = obj->getHCAttrsPtr();
for (auto const& kv : attrs->hcls->attr_offsets) { for (auto const& kv : attrs->hcls->getAttrOffsets()) {
listAppend(result, boxString(kv.first())); listAppend(result, boxString(kv.first()));
} }
} }
......
...@@ -87,6 +87,8 @@ Box* dictValues(BoxedDict* self) { ...@@ -87,6 +87,8 @@ Box* dictValues(BoxedDict* self) {
} }
Box* dictKeys(BoxedDict* self) { Box* dictKeys(BoxedDict* self) {
RELEASE_ASSERT(isSubclass(self->cls, dict_cls), "");
BoxedList* rtn = new BoxedList(); BoxedList* rtn = new BoxedList();
for (const auto& p : self->d) { for (const auto& p : self->d) {
listAppendInternal(rtn, p.first); listAppendInternal(rtn, p.first);
...@@ -321,14 +323,14 @@ Box* dictDelitem(BoxedDict* self, Box* k) { ...@@ -321,14 +323,14 @@ Box* dictDelitem(BoxedDict* self, Box* k) {
} }
extern "C" int PyDict_DelItem(PyObject* op, PyObject* key) noexcept { extern "C" int PyDict_DelItem(PyObject* op, PyObject* key) noexcept {
ASSERT(isSubclass(op->cls, dict_cls) || op->cls == attrwrapper_cls, "%s", getTypeName(op));
try { try {
dictDelitem((BoxedDict*)op, key); delitem(op, key);
return 0;
} catch (ExcInfo e) { } catch (ExcInfo e) {
setCAPIException(e); setCAPIException(e);
return -1; return -1;
} }
return 0;
} }
extern "C" int PyDict_DelItemString(PyObject* v, const char* key) noexcept { extern "C" int PyDict_DelItemString(PyObject* v, const char* key) noexcept {
......
...@@ -481,6 +481,8 @@ const char* getNameOfClass(BoxedClass* cls) { ...@@ -481,6 +481,8 @@ const char* getNameOfClass(BoxedClass* cls) {
} }
HiddenClass* HiddenClass::getOrMakeChild(const std::string& attr) { HiddenClass* HiddenClass::getOrMakeChild(const std::string& attr) {
assert(type == NORMAL);
auto it = children.find(attr); auto it = children.find(attr);
if (it != children.end()) if (it != children.end())
return it->second; return it->second;
...@@ -498,6 +500,7 @@ HiddenClass* HiddenClass::getOrMakeChild(const std::string& attr) { ...@@ -498,6 +500,7 @@ HiddenClass* HiddenClass::getOrMakeChild(const std::string& attr) {
* del attr from current HiddenClass, pertain the orders of remaining attrs * del attr from current HiddenClass, pertain the orders of remaining attrs
*/ */
HiddenClass* HiddenClass::delAttrToMakeHC(const std::string& attr) { HiddenClass* HiddenClass::delAttrToMakeHC(const std::string& attr) {
assert(type == NORMAL);
int idx = getOffset(attr); int idx = getOffset(attr);
assert(idx >= 0); assert(idx >= 0);
...@@ -527,13 +530,26 @@ HCAttrs* Box::getHCAttrsPtr() { ...@@ -527,13 +530,26 @@ HCAttrs* Box::getHCAttrsPtr() {
return reinterpret_cast<HCAttrs*>(p); return reinterpret_cast<HCAttrs*>(p);
} }
BoxedDict* Box::getDict() { BoxedDict** Box::getDictPtr() {
assert(cls->instancesHaveDictAttrs()); assert(cls->instancesHaveDictAttrs());
char* p = reinterpret_cast<char*>(this); char* p = reinterpret_cast<char*>(this);
p += cls->tp_dictoffset; p += cls->tp_dictoffset;
BoxedDict** d_ptr = reinterpret_cast<BoxedDict**>(p); BoxedDict** d_ptr = reinterpret_cast<BoxedDict**>(p);
return d_ptr;
}
void Box::setDict(BoxedDict* d) {
assert(cls->instancesHaveDictAttrs());
*getDictPtr() = d;
}
BoxedDict* Box::getDict() {
assert(cls->instancesHaveDictAttrs());
BoxedDict** d_ptr = getDictPtr();
BoxedDict* d = *d_ptr; BoxedDict* d = *d_ptr;
if (!d) { if (!d) {
d = *d_ptr = new BoxedDict(); d = *d_ptr = new BoxedDict();
...@@ -574,12 +590,25 @@ Box* Box::getattr(const std::string& attr, GetattrRewriteArgs* rewrite_args) { ...@@ -574,12 +590,25 @@ Box* Box::getattr(const std::string& attr, GetattrRewriteArgs* rewrite_args) {
// structure (ex user class) and the same hidden classes, because // structure (ex user class) and the same hidden classes, because
// otherwise the guard will fail anyway.; // otherwise the guard will fail anyway.;
if (cls->instancesHaveHCAttrs()) { if (cls->instancesHaveHCAttrs()) {
if (rewrite_args)
rewrite_args->out_success = true;
HCAttrs* attrs = getHCAttrsPtr(); HCAttrs* attrs = getHCAttrsPtr();
HiddenClass* hcls = attrs->hcls; HiddenClass* hcls = attrs->hcls;
if (hcls->type == HiddenClass::DICT_BACKED) {
if (rewrite_args)
assert(!rewrite_args->out_success);
rewrite_args = NULL;
Box* d = attrs->attr_list->attrs[0];
assert(d);
Box* r = PyDict_GetItemString(d, attr.c_str());
// r can be NULL if the item didn't exist
return r;
}
assert(hcls->type == HiddenClass::NORMAL);
if (rewrite_args)
rewrite_args->out_success = true;
if (rewrite_args) { if (rewrite_args) {
if (!rewrite_args->obj_hcls_guarded) if (!rewrite_args->obj_hcls_guarded)
rewrite_args->obj->addAttrGuard(cls->attrs_offset + HCATTRS_HCLS_OFFSET, (intptr_t)hcls); rewrite_args->obj->addAttrGuard(cls->attrs_offset + HCATTRS_HCLS_OFFSET, (intptr_t)hcls);
...@@ -641,7 +670,20 @@ void Box::setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite ...@@ -641,7 +670,20 @@ void Box::setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite
if (cls->instancesHaveHCAttrs()) { if (cls->instancesHaveHCAttrs()) {
HCAttrs* attrs = getHCAttrsPtr(); HCAttrs* attrs = getHCAttrsPtr();
HiddenClass* hcls = attrs->hcls; HiddenClass* hcls = attrs->hcls;
int numattrs = hcls->attr_offsets.size();
if (hcls->type == HiddenClass::DICT_BACKED) {
if (rewrite_args)
assert(!rewrite_args->out_success);
rewrite_args = NULL;
Box* d = attrs->attr_list->attrs[0];
assert(d);
PyDict_SetItemString(d, attr.c_str(), val);
checkAndThrowCAPIException();
return;
}
assert(hcls->type == HiddenClass::NORMAL);
int numattrs = hcls->getAttrOffsets().size();
int offset = hcls->getOffset(attr); int offset = hcls->getOffset(attr);
...@@ -672,10 +714,10 @@ void Box::setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite ...@@ -672,10 +714,10 @@ void Box::setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite
HiddenClass* new_hcls = hcls->getOrMakeChild(attr); HiddenClass* new_hcls = hcls->getOrMakeChild(attr);
// TODO need to make sure we don't need to rearrange the attributes // TODO need to make sure we don't need to rearrange the attributes
assert(new_hcls->attr_offsets[attr] == numattrs); assert(new_hcls->getAttrOffsets().lookup(attr) == numattrs);
#ifndef NDEBUG #ifndef NDEBUG
for (const auto& p : hcls->attr_offsets) { for (const auto& p : hcls->getAttrOffsets()) {
assert(new_hcls->attr_offsets[p.first()] == p.second); assert(new_hcls->getAttrOffsets().lookup(p.first()) == p.second);
} }
#endif #endif
...@@ -3629,12 +3671,25 @@ void Box::delattr(const std::string& attr, DelattrRewriteArgs* rewrite_args) { ...@@ -3629,12 +3671,25 @@ void Box::delattr(const std::string& attr, DelattrRewriteArgs* rewrite_args) {
// as soon as the hcls changes, the guard on hidden class won't pass. // as soon as the hcls changes, the guard on hidden class won't pass.
HCAttrs* attrs = getHCAttrsPtr(); HCAttrs* attrs = getHCAttrsPtr();
HiddenClass* hcls = attrs->hcls; HiddenClass* hcls = attrs->hcls;
if (hcls->type == HiddenClass::DICT_BACKED) {
if (rewrite_args)
assert(!rewrite_args->out_success);
rewrite_args = NULL;
Box* d = attrs->attr_list->attrs[0];
assert(d);
PyDict_DelItemString(d, attr.c_str());
checkAndThrowCAPIException();
return;
}
assert(hcls->type == HiddenClass::NORMAL);
HiddenClass* new_hcls = hcls->delAttrToMakeHC(attr); HiddenClass* new_hcls = hcls->delAttrToMakeHC(attr);
// The order of attributes is pertained as delAttrToMakeHC constructs // The order of attributes is pertained as delAttrToMakeHC constructs
// the new HiddenClass by invoking getOrMakeChild in the prevous order // the new HiddenClass by invoking getOrMakeChild in the prevous order
// of remaining attributes // of remaining attributes
int num_attrs = hcls->attr_offsets.size(); int num_attrs = hcls->getAttrOffsets().size();
int offset = hcls->getOffset(attr); int offset = hcls->getOffset(attr);
assert(offset >= 0); assert(offset >= 0);
Box** start = attrs->attr_list->attrs; Box** start = attrs->attr_list->attrs;
...@@ -4372,7 +4427,7 @@ extern "C" Box* importStar(Box* _from_module, BoxedModule* to_module) { ...@@ -4372,7 +4427,7 @@ extern "C" Box* importStar(Box* _from_module, BoxedModule* to_module) {
} }
HCAttrs* module_attrs = from_module->getHCAttrsPtr(); HCAttrs* module_attrs = from_module->getHCAttrsPtr();
for (auto& p : module_attrs->hcls->attr_offsets) { for (auto& p : module_attrs->hcls->getAttrOffsets()) {
if (p.first()[0] == '_') if (p.first()[0] == '_')
continue; continue;
......
This diff is collapsed.
...@@ -264,14 +264,29 @@ static_assert(sizeof(pyston::BoxedHeapClass) == sizeof(PyHeapTypeObject), ""); ...@@ -264,14 +264,29 @@ static_assert(sizeof(pyston::BoxedHeapClass) == sizeof(PyHeapTypeObject), "");
class HiddenClass : public GCAllocated<gc::GCKind::HIDDEN_CLASS> { class HiddenClass : public GCAllocated<gc::GCKind::HIDDEN_CLASS> {
public:
// We have a couple different storage strategies for attributes, which
// are distinguished by having a different hidden class type.
enum HCType {
NORMAL, // attributes stored in attributes array, name->offset map stored in hidden class
DICT_BACKED, // first attribute in array is a dict-like object which stores the attributes
} const type;
static HiddenClass* dict_backed;
private: private:
HiddenClass() {} HiddenClass(HCType type) : type(type) {}
HiddenClass(HiddenClass* parent) : attr_offsets() { HiddenClass(HiddenClass* parent) : type(NORMAL), attr_offsets() {
assert(parent->type == NORMAL);
for (auto& p : parent->attr_offsets) { for (auto& p : parent->attr_offsets) {
this->attr_offsets.insert(&p); this->attr_offsets.insert(&p);
} }
} }
// Only makes sense for NORMAL hidden classes. Clients should access through getAttrOffsets():
llvm::StringMap<int> attr_offsets;
llvm::StringMap<HiddenClass*> children;
public: public:
static HiddenClass* makeRoot() { static HiddenClass* makeRoot() {
#ifndef NDEBUG #ifndef NDEBUG
...@@ -279,27 +294,45 @@ public: ...@@ -279,27 +294,45 @@ public:
assert(!made); assert(!made);
made = true; made = true;
#endif #endif
return new HiddenClass(); return new HiddenClass(NORMAL);
}
static HiddenClass* makeDictBacked() {
#ifndef NDEBUG
static bool made = false;
assert(!made);
made = true;
#endif
return new HiddenClass(DICT_BACKED);
} }
llvm::StringMap<int> attr_offsets; void gc_visit(GCVisitor* visitor) {
llvm::StringMap<HiddenClass*> children; // Visit children even for the dict-backed case, since children will just be empty
for (const auto& p : children) {
visitor->visit(p.second);
}
}
// Only makes sense for NORMAL hidden classes:
const llvm::StringMap<int>& getAttrOffsets() {
assert(type == NORMAL);
return attr_offsets;
}
// Only makes sense for NORMAL hidden classes:
HiddenClass* getOrMakeChild(const std::string& attr); HiddenClass* getOrMakeChild(const std::string& attr);
// Only makes sense for NORMAL hidden classes:
int getOffset(const std::string& attr) { int getOffset(const std::string& attr) {
assert(type == NORMAL);
auto it = attr_offsets.find(attr); auto it = attr_offsets.find(attr);
if (it == attr_offsets.end()) if (it == attr_offsets.end())
return -1; return -1;
return it->second; return it->second;
} }
HiddenClass* delAttrToMakeHC(const std::string& attr);
void gc_visit(GCVisitor* visitor) { // Only makes sense for NORMAL hidden classes:
for (const auto& p : children) { HiddenClass* delAttrToMakeHC(const std::string& attr);
visitor->visit(p.second);
}
}
}; };
class BoxedInt : public Box { class BoxedInt : public Box {
......
# expected: fail try:
# - we don't support setting __dict__ yet object().__dict__ = 1
except AttributeError as e:
print e
class C(object): class C(object):
pass pass
...@@ -23,3 +25,16 @@ p() ...@@ -23,3 +25,16 @@ p()
c1.a = 6 c1.a = 6
c2.b = 7 c2.b = 7
p() p()
print c1.a, c1.b, c2.a, c2.b
del c1.a
try:
del c1.a
except AttributeError as e:
# the error message CPython gives here is just "a" which I don't think we should copy.
print "caught AttributeError"
p()
c1.__dict__ = d = {}
d['i'] = 5
p()
# expected: fail
# - haven't bothered to implement this yet
# Funny case: if we get the attrwrapper of an object, then change it to be dict-backed,
# the original attrwrapper should remain valid but no longer connected to that object.
class C(object):
pass
c1 = C()
aw = c1.__dict__
c1.a = 1
print aw.items()
c1.__dict__ = d = {}
print aw.items()
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