Commit 4ca839f9 authored by Kevin Modzelewski's avatar Kevin Modzelewski

Add new "SINGLETON" hidden class type

The storage strategy per-object is the same as NORMAL, but instead of having
immutable chains of hidden classes so that we can share them between instances,
a SINGLETON hcls is unique to the single object that it is for.  This means
that it can be mutable.

This has the advantage that we don't have to track all the parents
of the hidden class.  This is especially helpful since the cases that
it's used for (Python classes and modules) tend to have a large number
of attributes (>100 not uncommon).

We could also use the DICT_BACKED strategy for those cases, but compared to
DICT_BACKED, SINGLETON should have much better lookup performance since
it is patchpoint-compatible.  Adding new attributes is probably slower,
and deleting attributes is probably much slower, but I think those are
hopefully rare.

This cuts non-jit 'import pip' memory by 15%, from 113MB to 98MB.
parent 0d936939
......@@ -2717,13 +2717,14 @@ extern "C" int PyType_Ready(PyTypeObject* cls) noexcept {
RELEASE_ASSERT(cls->tp_del == NULL, "");
RELEASE_ASSERT(cls->tp_version_tag == 0, "");
// I think it is safe to ignore these for for now:
// RELEASE_ASSERT(cls->tp_weaklistoffset == 0, "");
// RELEASE_ASSERT(cls->tp_traverse == NULL, "");
// RELEASE_ASSERT(cls->tp_clear == NULL, "");
// I think it is safe to ignore these for for now:
// RELEASE_ASSERT(cls->tp_weaklistoffset == 0, "");
// RELEASE_ASSERT(cls->tp_traverse == NULL, "");
// RELEASE_ASSERT(cls->tp_clear == NULL, "");
assert(cls->attrs.hcls == NULL);
new (&cls->attrs) HCAttrs(HiddenClass::makeSingleton());
#define INITIALIZE(a) new (&(a)) decltype(a)
INITIALIZE(cls->attrs);
INITIALIZE(cls->dependent_icgetattrs);
#undef INITIALIZE
......
......@@ -418,7 +418,7 @@ public:
HiddenClass* hcls;
AttrList* attr_list;
HCAttrs() : hcls(root_hcls), attr_list(nullptr) {}
HCAttrs(HiddenClass* hcls = root_hcls) : hcls(hcls), attr_list(nullptr) {}
};
class BoxedDict;
......@@ -428,11 +428,8 @@ class Box {
private:
BoxedDict** getDictPtr();
// Adds a new attribute to a HCAttrs-backed object. Must pass in the new hidden class object
// which must be the same as the current hidden class but with the new attribute at the end.
// Swaps the hidden class, reallocates and copies and updates the attribute array.
// The value of the current hidden class should be guarded before calling this.
void addNewHCAttr(HiddenClass* new_hcls, Box* val, SetattrRewriteArgs* rewrite_args);
// Appends a new value to the hcattrs array.
void appendNewHCAttr(Box* val, SetattrRewriteArgs* rewrite_args);
public:
// Add a no-op constructor to make sure that we don't zero-initialize cls
......
......@@ -208,7 +208,7 @@ struct HeapStatistics {
TypeStats total;
HeapStatistics(bool collect_cls_stats, bool collect_hcls_stats)
: collect_cls_stats(collect_cls_stats), collect_hcls_stats(collect_hcls_stats) {
: collect_cls_stats(collect_cls_stats), collect_hcls_stats(collect_hcls_stats), num_hcls_by_attrs_exceed(0) {
memset(num_hcls_by_attrs, 0, sizeof(num_hcls_by_attrs));
}
};
......@@ -233,6 +233,10 @@ void addStatistic(HeapStatistics* stats, GCAllocation* al, int nbytes) {
Box* b = (Box*)al->user_data;
if (b->cls->instancesHaveHCAttrs()) {
HCAttrs* attrs = b->getHCAttrsPtr();
if (attrs->hcls->attributeArraySize() >= 20) {
printf("%s object has %d attributes\n", b->cls->tp_name, attrs->hcls->attributeArraySize());
}
stats->hcls_uses[attrs->hcls]++;
}
}
......
......@@ -228,7 +228,6 @@ extern "C" int PyDict_SetItem(PyObject* mp, PyObject* _key, PyObject* _item) noe
Box* item = static_cast<Box*>(_item);
try {
// TODO should demote GIL?
setitem(b, key, item);
} catch (ExcInfo e) {
abort();
......
......@@ -318,8 +318,8 @@ void BoxedClass::freeze() {
BoxedClass::BoxedClass(BoxedClass* base, gcvisit_func gc_visit, int attrs_offset, int weaklist_offset,
int instance_size, bool is_user_defined)
: BoxVar(0), gc_visit(gc_visit), simple_destructor(NULL), attrs_offset(attrs_offset), is_constant(false),
is_user_defined(is_user_defined), is_pyston_class(true) {
: BoxVar(0), attrs(HiddenClass::makeSingleton()), gc_visit(gc_visit), simple_destructor(NULL),
attrs_offset(attrs_offset), is_constant(false), is_user_defined(is_user_defined), is_pyston_class(true) {
// Zero out the CPython tp_* slots:
memset(&tp_name, 0, (char*)(&tp_version_tag + 1) - (char*)(&tp_name));
......@@ -481,6 +481,43 @@ const char* getNameOfClass(BoxedClass* cls) {
return cls->tp_name;
}
void HiddenClass::appendAttribute(llvm::StringRef attr) {
assert(type == SINGLETON);
dependent_getattrs.invalidateAll();
assert(attr_offsets.count(attr) == 0);
int n = this->attributeArraySize();
attr_offsets[attr] = n;
}
void HiddenClass::appendAttrwrapper() {
assert(type == SINGLETON);
dependent_getattrs.invalidateAll();
assert(attrwrapper_offset == -1);
attrwrapper_offset = this->attributeArraySize();
}
void HiddenClass::delAttribute(llvm::StringRef attr) {
assert(type == SINGLETON);
dependent_getattrs.invalidateAll();
assert(attr_offsets.count(attr));
int prev_idx = attr_offsets[attr];
attr_offsets.erase(attr);
for (auto it = attr_offsets.begin(), end = attr_offsets.end(); it != end; ++it) {
assert(it->second != prev_idx);
if (it->second > prev_idx)
it->second--;
}
if (attrwrapper_offset != -1 && attrwrapper_offset > prev_idx)
attrwrapper_offset--;
}
void HiddenClass::addDependence(Rewriter* rewriter) {
assert(type == SINGLETON);
rewriter->addDependenceOn(dependent_getattrs);
}
HiddenClass* HiddenClass::getOrMakeChild(const std::string& attr) {
STAT_TIMER(t0, "us_timer_hiddenclass_getOrMakeChild");
assert(type == NORMAL);
......@@ -629,14 +666,16 @@ Box* Box::getattr(const std::string& attr, GetattrRewriteArgs* rewrite_args) {
return r;
}
assert(hcls->type == HiddenClass::NORMAL);
if (rewrite_args)
rewrite_args->out_success = true;
assert(hcls->type == HiddenClass::NORMAL || hcls->type == HiddenClass::SINGLETON);
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);
if (hcls->type == HiddenClass::SINGLETON)
hcls->addDependence(rewrite_args->rewriter);
}
rewrite_args->out_success = true;
}
int offset = hcls->getOffset(attr);
......@@ -645,8 +684,6 @@ Box* Box::getattr(const std::string& attr, GetattrRewriteArgs* rewrite_args) {
}
if (rewrite_args) {
// TODO using the output register as the temporary makes register allocation easier
// since we don't need to clobber a register, but does it make the code slower?
RewriterVar* r_attrs
= rewrite_args->obj->getAttr(cls->attrs_offset + HCATTRS_ATTRS_OFFSET, Location::any());
rewrite_args->out_rtn = r_attrs->getAttr(offset * sizeof(Box*) + ATTRLIST_ATTRS_OFFSET, Location::any());
......@@ -676,20 +713,12 @@ Box* Box::getattr(const std::string& attr, GetattrRewriteArgs* rewrite_args) {
return NULL;
}
void Box::addNewHCAttr(HiddenClass* new_hcls, Box* new_attr, SetattrRewriteArgs* rewrite_args) {
void Box::appendNewHCAttr(Box* new_attr, SetattrRewriteArgs* rewrite_args) {
assert(cls->instancesHaveHCAttrs());
HCAttrs* attrs = getHCAttrsPtr();
HiddenClass* hcls = attrs->hcls;
#ifndef NDEBUG
// make sure we don't need to rearrange the attributes
assert(new_hcls->attributeArraySize() == hcls->attributeArraySize() + 1);
for (const auto& p : hcls->getStrAttrOffsets()) {
assert(new_hcls->getStrAttrOffsets().lookup(p.first()) == p.second);
}
if (hcls->getAttrwrapperOffset() != -1)
assert(hcls->getAttrwrapperOffset() == new_hcls->getAttrwrapperOffset());
#endif
assert(hcls->type == HiddenClass::NORMAL || hcls->type == HiddenClass::SINGLETON);
int numattrs = hcls->attributeArraySize();
......@@ -711,18 +740,11 @@ void Box::addNewHCAttr(HiddenClass* new_hcls, Box* new_attr, SetattrRewriteArgs*
r_new_array2 = rewrite_args->rewriter->call(true, (void*)gc::gc_realloc, r_oldarray, r_newsize);
}
}
// Don't set the new hcls until after we do the allocation for the new attr_list;
// that allocation can cause a collection, and we want the collector to always
// see a consistent state between the hcls and the attr_list
attrs->hcls = new_hcls;
if (rewrite_args) {
r_new_array2->setAttr(numattrs * sizeof(Box*) + ATTRLIST_ATTRS_OFFSET, rewrite_args->attrval);
rewrite_args->obj->setAttr(cls->attrs_offset + HCATTRS_ATTRS_OFFSET, r_new_array2);
RewriterVar* r_hcls = rewrite_args->rewriter->loadConst((intptr_t)new_hcls);
rewrite_args->obj->setAttr(cls->attrs_offset + HCATTRS_HCLS_OFFSET, r_hcls);
rewrite_args->out_success = true;
}
attrs->attr_list->attrs[numattrs] = new_attr;
......@@ -760,13 +782,14 @@ void Box::setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite
return;
}
assert(hcls->type == HiddenClass::NORMAL);
assert(hcls->type == HiddenClass::NORMAL || hcls->type == HiddenClass::SINGLETON);
int offset = hcls->getOffset(attr);
if (rewrite_args) {
rewrite_args->obj->addAttrGuard(cls->attrs_offset + HCATTRS_HCLS_OFFSET, (intptr_t)hcls);
// rewrite_args->rewriter->addDecision(offset == -1 ? 1 : 0);
if (hcls->type == HiddenClass::SINGLETON)
hcls->addDependence(rewrite_args->rewriter);
}
if (offset >= 0) {
......@@ -788,12 +811,33 @@ void Box::setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite
}
assert(offset == -1);
HiddenClass* new_hcls = hcls->getOrMakeChild(attr);
// make sure we don't need to rearrange the attributes
assert(new_hcls->getStrAttrOffsets().lookup(attr) == hcls->attributeArraySize());
if (hcls->type == HiddenClass::NORMAL) {
HiddenClass* new_hcls = hcls->getOrMakeChild(attr);
// make sure we don't need to rearrange the attributes
assert(new_hcls->getStrAttrOffsets().lookup(attr) == hcls->attributeArraySize());
addNewHCAttr(new_hcls, val, rewrite_args);
this->appendNewHCAttr(val, rewrite_args);
attrs->hcls = new_hcls;
if (rewrite_args) {
if (!rewrite_args->out_success) {
rewrite_args = NULL;
} else {
RewriterVar* r_hcls = rewrite_args->rewriter->loadConst((intptr_t)new_hcls);
rewrite_args->obj->setAttr(cls->attrs_offset + HCATTRS_HCLS_OFFSET, r_hcls);
rewrite_args->out_success = true;
}
}
} else {
assert(hcls->type == HiddenClass::SINGLETON);
assert(!rewrite_args || !rewrite_args->out_success);
rewrite_args = NULL;
this->appendNewHCAttr(val, NULL);
hcls->appendAttribute(attr);
}
return;
}
......@@ -3825,8 +3869,7 @@ void Box::delattr(const std::string& attr, DelattrRewriteArgs* rewrite_args) {
return;
}
assert(hcls->type == HiddenClass::NORMAL);
HiddenClass* new_hcls = hcls->delAttrToMakeHC(attr);
assert(hcls->type == HiddenClass::NORMAL || hcls->type == HiddenClass::SINGLETON);
// The order of attributes is pertained as delAttrToMakeHC constructs
// the new HiddenClass by invoking getOrMakeChild in the prevous order
......@@ -3837,7 +3880,13 @@ void Box::delattr(const std::string& attr, DelattrRewriteArgs* rewrite_args) {
Box** start = attrs->attr_list->attrs;
memmove(start + offset, start + offset + 1, (num_attrs - offset - 1) * sizeof(Box*));
attrs->hcls = new_hcls;
if (hcls->type == HiddenClass::NORMAL) {
HiddenClass* new_hcls = hcls->delAttrToMakeHC(attr);
attrs->hcls = new_hcls;
} else {
assert(hcls->type == HiddenClass::SINGLETON);
hcls->delAttribute(attr);
}
// guarantee the size of the attr_list equals the number of attrs
int new_size = sizeof(HCAttrs::AttrList) + sizeof(Box*) * (num_attrs - 1);
......
......@@ -383,7 +383,8 @@ static void functionDtor(Box* b) {
}
// TODO(kmod): builtin modules are not supposed to have a __file__ attribute
BoxedModule::BoxedModule(const std::string& name, const std::string& fn, const char* doc) {
BoxedModule::BoxedModule(const std::string& name, const std::string& fn, const char* doc)
: attrs(HiddenClass::makeSingleton()) {
this->giveAttr("__name__", boxString(name));
this->giveAttr("__file__", boxString(fn));
this->giveAttr("__doc__", doc ? boxStrConstant(doc) : None);
......@@ -1255,7 +1256,8 @@ public:
// This check doesn't cover all cases, since an attrwrapper could be created around
// a normal object which then becomes dict-backed, so we RELEASE_ASSERT later
// that that doesn't happen.
assert(b->getHCAttrsPtr()->hcls->type == HiddenClass::NORMAL);
assert(b->getHCAttrsPtr()->hcls->type == HiddenClass::NORMAL
|| b->getHCAttrsPtr()->hcls->type == HiddenClass::SINGLETON);
}
DEFAULT_CLASS(attrwrapper_cls);
......@@ -1374,7 +1376,7 @@ public:
os << "attrwrapper({";
HCAttrs* attrs = self->b->getHCAttrsPtr();
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL, "");
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL || attrs->hcls->type == HiddenClass::SINGLETON, "");
bool first = true;
for (const auto& p : attrs->hcls->getStrAttrOffsets()) {
if (!first)
......@@ -1407,7 +1409,7 @@ public:
BoxedList* rtn = new BoxedList();
HCAttrs* attrs = self->b->getHCAttrsPtr();
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL, "");
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL || attrs->hcls->type == HiddenClass::SINGLETON, "");
for (const auto& p : attrs->hcls->getStrAttrOffsets()) {
listAppend(rtn, boxString(p.first()));
}
......@@ -1422,7 +1424,7 @@ public:
BoxedList* rtn = new BoxedList();
HCAttrs* attrs = self->b->getHCAttrsPtr();
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL, "");
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL || attrs->hcls->type == HiddenClass::SINGLETON, "");
for (const auto& p : attrs->hcls->getStrAttrOffsets()) {
listAppend(rtn, attrs->attr_list->attrs[p.second]);
}
......@@ -1437,7 +1439,7 @@ public:
BoxedList* rtn = new BoxedList();
HCAttrs* attrs = self->b->getHCAttrsPtr();
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL, "");
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL || attrs->hcls->type == HiddenClass::SINGLETON, "");
for (const auto& p : attrs->hcls->getStrAttrOffsets()) {
BoxedTuple* t = BoxedTuple::create({ boxString(p.first()), attrs->attr_list->attrs[p.second] });
listAppend(rtn, t);
......@@ -1452,7 +1454,7 @@ public:
BoxedDict* rtn = new BoxedDict();
HCAttrs* attrs = self->b->getHCAttrsPtr();
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL, "");
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL || attrs->hcls->type == HiddenClass::SINGLETON, "");
for (const auto& p : attrs->hcls->getStrAttrOffsets()) {
rtn->d[boxString(p.first())] = attrs->attr_list->attrs[p.second];
}
......@@ -1464,7 +1466,7 @@ public:
AttrWrapper* self = static_cast<AttrWrapper*>(_self);
HCAttrs* attrs = self->b->getHCAttrsPtr();
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL, "");
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL || attrs->hcls->type == HiddenClass::SINGLETON, "");
return boxInt(attrs->hcls->getStrAttrOffsets().size());
}
......@@ -1477,7 +1479,7 @@ public:
AttrWrapper* container = static_cast<AttrWrapper*>(_container);
HCAttrs* attrs = container->b->getHCAttrsPtr();
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL, "");
RELEASE_ASSERT(attrs->hcls->type == HiddenClass::NORMAL || attrs->hcls->type == HiddenClass::SINGLETON, "");
for (const auto& p : attrs->hcls->getStrAttrOffsets()) {
self->b->setattr(p.first(), attrs->attr_list->attrs[p.second], NULL);
}
......@@ -1506,14 +1508,14 @@ public:
AttrWrapperIter::AttrWrapperIter(AttrWrapper* aw) {
hcls = aw->b->getHCAttrsPtr()->hcls;
assert(hcls);
RELEASE_ASSERT(hcls->type == HiddenClass::NORMAL, "");
RELEASE_ASSERT(hcls->type == HiddenClass::NORMAL || hcls->type == HiddenClass::SINGLETON, "");
it = hcls->getStrAttrOffsets().begin();
}
Box* AttrWrapperIter::hasnext(Box* _self) {
RELEASE_ASSERT(_self->cls == attrwrapperiter_cls, "");
AttrWrapperIter* self = static_cast<AttrWrapperIter*>(_self);
RELEASE_ASSERT(self->hcls->type == HiddenClass::NORMAL, "");
RELEASE_ASSERT(self->hcls->type == HiddenClass::NORMAL || self->hcls->type == HiddenClass::SINGLETON, "");
return boxBool(self->it != self->hcls->getStrAttrOffsets().end());
}
......@@ -1521,7 +1523,7 @@ Box* AttrWrapperIter::hasnext(Box* _self) {
Box* AttrWrapperIter::next(Box* _self) {
RELEASE_ASSERT(_self->cls == attrwrapperiter_cls, "");
AttrWrapperIter* self = static_cast<AttrWrapperIter*>(_self);
RELEASE_ASSERT(self->hcls->type == HiddenClass::NORMAL, "");
RELEASE_ASSERT(self->hcls->type == HiddenClass::NORMAL || self->hcls->type == HiddenClass::SINGLETON, "");
assert(self->it != self->hcls->getStrAttrOffsets().end());
Box* r = boxString(self->it->first());
......@@ -1541,8 +1543,17 @@ Box* Box::getAttrWrapper() {
int offset = hcls->getAttrwrapperOffset();
if (offset == -1) {
Box* aw = new AttrWrapper(this);
addNewHCAttr(hcls->getAttrwrapperChild(), aw, NULL);
return aw;
if (hcls->type == HiddenClass::NORMAL) {
auto new_hcls = hcls->getAttrwrapperChild();
appendNewHCAttr(aw, NULL);
attrs->hcls = new_hcls;
return aw;
} else {
assert(hcls->type == HiddenClass::SINGLETON);
appendNewHCAttr(aw, NULL);
hcls->appendAttrwrapper();
return aw;
}
}
return attrs->attr_list->attrs[offset];
}
......
......@@ -278,6 +278,7 @@ public:
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
SINGLETON, // name->offset map stored in hidden class, but hcls is mutable
} const type;
static HiddenClass* dict_backed;
......@@ -291,14 +292,21 @@ private:
}
}
// These fields only make sense for NORMAL hidden classes:
// These fields only make sense for NORMAL or SINGLETON hidden classes:
llvm::StringMap<int> attr_offsets;
ContiguousMap<llvm::StringRef, HiddenClass*, llvm::StringMap<int>> children;
// If >= 0, is the offset where we stored an attrwrapper object
int attrwrapper_offset = -1;
// These are only for NORMAL hidden classes:
ContiguousMap<llvm::StringRef, HiddenClass*, llvm::StringMap<int>> children;
HiddenClass* attrwrapper_child = NULL;
// Only for SINGLETON hidden classes:
ICInvalidator dependent_getattrs;
public:
static HiddenClass* makeSingleton() { return new HiddenClass(SINGLETON); }
static HiddenClass* makeRoot() {
#ifndef NDEBUG
static bool made = false;
......@@ -329,7 +337,7 @@ public:
if (type == DICT_BACKED)
return 1;
ASSERT(type == NORMAL, "%d", type);
ASSERT(type == NORMAL || type == SINGLETON, "%d", type);
int r = attr_offsets.size();
if (attrwrapper_offset != -1)
r += 1;
......@@ -338,18 +346,18 @@ public:
// The mapping from string attribute names to attribute offsets. There may be other objects in the attributes
// array.
// Only valid for NORMAL hidden classes
// Only valid for NORMAL or SINGLETON hidden classes
const llvm::StringMap<int>& getStrAttrOffsets() {
assert(type == NORMAL);
assert(type == NORMAL || type == SINGLETON);
return attr_offsets;
}
// Only valid for NORMAL hidden classes:
HiddenClass* getOrMakeChild(const std::string& attr);
// Only valid for NORMAL hidden classes:
// Only valid for NORMAL or SINGLETON hidden classes:
int getOffset(const std::string& attr) {
assert(type == NORMAL);
assert(type == NORMAL || type == SINGLETON);
auto it = attr_offsets.find(attr);
if (it == attr_offsets.end())
return -1;
......@@ -357,10 +365,17 @@ public:
}
int getAttrwrapperOffset() {
assert(type == NORMAL);
assert(type == NORMAL || type == SINGLETON);
return attrwrapper_offset;
}
// Only valid for SINGLETON hidden classes:
void appendAttribute(llvm::StringRef attr);
void appendAttrwrapper();
void delAttribute(llvm::StringRef attr);
void addDependence(Rewriter* rewriter);
// Only valid for NORMAL hidden classes:
HiddenClass* getAttrwrapperChild();
// Only valid for NORMAL hidden classes:
......
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