Commit b5f06903 authored by Kevin Modzelewski's avatar Kevin Modzelewski

Merge commit 'pr/466'

Conflicts:
	src/runtime/objmodel.cpp

Closes #466
parents 87b8302d b332f703
......@@ -20,6 +20,7 @@
#include "core/common.h"
#include "core/types.h"
#include "core/util.h"
#include "runtime/types.h"
namespace pyston {
......@@ -52,6 +53,30 @@ bool containsYield(AST* ast) {
return visitor.containsYield;
}
// TODO
// Combine this with the below? Basically the same logic with different string types...
// Also should this go in this file?
BoxedString* mangleNameBoxedString(BoxedString* id, BoxedString* private_name) {
assert(id);
assert(private_name);
int len = id->s.size();
if (len < 2 || id->s[0] != '_' || id->s[1] != '_')
return id;
if ((id->s[len - 2] == '_' && id->s[len - 1] == '_') || id->s.find('.') != llvm::StringRef::npos)
return id;
const char* p = private_name->s.data();
while (*p == '_') {
p++;
len--;
}
if (*p == '\0')
return id;
return static_cast<BoxedString*>(boxStringTwine("_" + (p + id->s)));
}
static void mangleNameInPlace(InternedString& id, const std::string* private_name,
InternedStringPool& interned_strings) {
if (!private_name)
......
......@@ -178,6 +178,9 @@ public:
};
bool containsYield(AST* ast);
class BoxedString;
BoxedString* mangleNameBoxedString(BoxedString* id, BoxedString* private_name);
}
#endif
......@@ -443,10 +443,12 @@ public:
llvm::iterator_range<BoxIterator> pyElements();
size_t getHCAttrsOffset();
HCAttrs* getHCAttrsPtr();
void setDict(BoxedDict* d);
BoxedDict* getDict();
void setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite_args);
void giveAttr(const std::string& attr, Box* val) {
assert(!this->hasattr(attr));
......@@ -476,9 +478,11 @@ extern "C" PyObject* PystonType_GenericAlloc(BoxedClass* cls, Py_ssize_t nitems)
#define DEFAULT_CLASS(default_cls) \
void* operator new(size_t size, BoxedClass * cls) __attribute__((visibility("default"))) { \
assert(cls->tp_itemsize == 0); \
return Box::operator new(size, cls); \
} \
void* operator new(size_t size) __attribute__((visibility("default"))) { \
assert(default_cls->tp_itemsize == 0); \
return Box::operator new(size, default_cls); \
}
......@@ -488,8 +492,27 @@ extern "C" PyObject* PystonType_GenericAlloc(BoxedClass* cls, Py_ssize_t nitems)
std::string per_name_allocsize_name = "allocsize." + std::string(cls->tp_name); \
Stats::log(Stats::getStatId(per_name_alloc_name)); \
Stats::log(Stats::getStatId(per_name_allocsize_name), size);
#define ALLOC_STATS_VAR(cls) \
if (cls->tp_name) { \
std::string per_name_alloc_name = "alloc." + std::string(cls->tp_name); \
std::string per_name_alloc_name0 = "alloc." + std::string(cls->tp_name) + "(0)"; \
std::string per_name_allocsize_name = "allocsize." + std::string(cls->tp_name); \
std::string per_name_allocsize_name0 = "allocsize." + std::string(cls->tp_name) + "(0)"; \
static StatCounter alloc_name(per_name_alloc_name); \
static StatCounter alloc_name0(per_name_alloc_name0); \
static StatCounter allocsize_name(per_name_allocsize_name); \
static StatCounter allocsize_name0(per_name_allocsize_name0); \
if (nitems == 0) { \
alloc_name0.log(); \
allocsize_name0.log(_PyObject_VAR_SIZE(cls, nitems)); \
} else { \
alloc_name.log(); \
allocsize_name.log(_PyObject_VAR_SIZE(cls, nitems)); \
} \
}
#else
#define ALLOC_STATS(cls)
#define ALLOC_STATS_VAR(cls)
#endif
......@@ -525,12 +548,59 @@ extern "C" PyObject* PystonType_GenericAlloc(BoxedClass* cls, Py_ssize_t nitems)
/* TODO: there should be a way to not have to do this nested inlining by hand */ \
}
#define DEFAULT_CLASS_VAR(default_cls, itemsize) \
static_assert(itemsize > 0, ""); \
/* asserts that the class in question is a subclass of BoxVar */ \
inline void _base_check() { \
static_assert(std::is_base_of<BoxVar, std::remove_pointer<decltype(this)>::type>::value, ""); \
} \
\
void* operator new(size_t size, BoxedClass * cls, size_t nitems) __attribute__((visibility("default"))) { \
assert(cls->tp_itemsize == itemsize); \
return BoxVar::operator new(size, cls, nitems); \
} \
void* operator new(size_t size, size_t nitems) __attribute__((visibility("default"))) { \
assert(default_cls->tp_itemsize == itemsize); \
return BoxVar::operator new(size, default_cls, nitems); \
}
#define DEFAULT_CLASS_VAR_SIMPLE(default_cls, itemsize) \
static_assert(itemsize > 0, ""); \
inline void _base_check() { \
static_assert(std::is_base_of<BoxVar, std::remove_pointer<decltype(this)>::type>::value, ""); \
} \
\
void* operator new(size_t size, BoxedClass * cls, size_t nitems) __attribute__((visibility("default"))) { \
ALLOC_STATS_VAR(default_cls) \
assert(cls->tp_itemsize == itemsize); \
return BoxVar::operator new(size, cls, nitems); \
} \
void* operator new(size_t size, size_t nitems) __attribute__((visibility("default"))) { \
ALLOC_STATS_VAR(default_cls) \
assert(default_cls->tp_alloc == PystonType_GenericAlloc); \
assert(default_cls->tp_itemsize == itemsize); \
assert(default_cls->tp_basicsize == size); \
assert(default_cls->is_pyston_class); \
assert(default_cls->attrs_offset == 0); \
\
void* mem = gc_alloc(size + nitems * itemsize, gc::GCKind::PYTHON); \
assert(mem); \
\
BoxVar* rtn = static_cast<BoxVar*>(mem); \
rtn->cls = default_cls; \
rtn->ob_size = nitems; \
return rtn; \
}
// CPython C API compatibility class:
class BoxVar : public Box {
public:
// This field gets initialized in operator new.
Py_ssize_t ob_size;
BoxVar(Py_ssize_t ob_size) : ob_size(ob_size) {}
BoxVar() {}
void* operator new(size_t size, BoxedClass* cls, size_t nitems) __attribute__((visibility("default")));
};
static_assert(offsetof(BoxVar, ob_size) == offsetof(struct _varobject, ob_size), "");
......
......@@ -461,8 +461,8 @@ void setupSys() {
sys_module->giveAttr("maxint", boxInt(PYSTON_INT_MAX));
sys_module->giveAttr("maxsize", boxInt(PY_SSIZE_T_MAX));
sys_flags_cls = new BoxedHeapClass(object_cls, BoxedSysFlags::gcHandler, 0, 0, sizeof(BoxedSysFlags), false,
static_cast<BoxedString*>(boxString("flags")));
sys_flags_cls = new (0) BoxedHeapClass(object_cls, BoxedSysFlags::gcHandler, 0, 0, sizeof(BoxedSysFlags), false,
static_cast<BoxedString*>(boxString("flags")));
sys_flags_cls->giveAttr("__new__",
new BoxedFunction(boxRTFunction((void*)BoxedSysFlags::__new__, UNKNOWN, 1, 0, true, true)));
#define ADD(name) \
......
......@@ -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), 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) {
: 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));
......@@ -375,16 +375,6 @@ BoxedClass::BoxedClass(BoxedClass* base, gcvisit_func gc_visit, int attrs_offset
if (base && cls && str_cls)
giveAttr("__base__", base);
// this isn't strictly correct, as it permits subclasses from
// e.g. Tuples/Longs to have weakrefs, which cpython disallows.
if (tp_weaklistoffset == 0 && base)
tp_weaklistoffset = base->tp_weaklistoffset;
if (is_user_defined && tp_weaklistoffset == 0) {
tp_weaklistoffset = tp_basicsize;
tp_basicsize += sizeof(Box**);
}
assert(tp_basicsize % sizeof(void*) == 0); // Not critical I suppose, but probably signals a bug
if (attrs_offset) {
assert(tp_basicsize >= attrs_offset + sizeof(HCAttrs));
assert(attrs_offset % sizeof(void*) == 0); // Not critical I suppose, but probably signals a bug
......@@ -433,13 +423,13 @@ BoxedHeapClass* BoxedHeapClass::create(BoxedClass* metaclass, BoxedClass* base,
int weaklist_offset, int instance_size, bool is_user_defined,
const std::string& name) {
return create(metaclass, base, gc_visit, attrs_offset, weaklist_offset, instance_size, is_user_defined,
static_cast<BoxedString*>(boxString(name)), NULL);
static_cast<BoxedString*>(boxString(name)), NULL, 0);
}
BoxedHeapClass* BoxedHeapClass::create(BoxedClass* metaclass, BoxedClass* base, gcvisit_func gc_visit, int attrs_offset,
int weaklist_offset, int instance_size, bool is_user_defined, BoxedString* name,
BoxedTuple* bases) {
BoxedHeapClass* made = new (metaclass)
BoxedTuple* bases, size_t nslots) {
BoxedHeapClass* made = new (metaclass, nslots)
BoxedHeapClass(base, gc_visit, attrs_offset, weaklist_offset, instance_size, is_user_defined, name);
assert((name || str_cls == NULL) && "name can only be NULL before str_cls has been initialized.");
......@@ -584,16 +574,33 @@ HiddenClass* HiddenClass::delAttrToMakeHC(const std::string& attr) {
return cur;
}
HCAttrs* Box::getHCAttrsPtr() {
size_t Box::getHCAttrsOffset() {
assert(cls->instancesHaveHCAttrs());
if (unlikely(cls->attrs_offset < 0)) {
// negative indicates an offset from the end of an object
if (cls->tp_itemsize != 0) {
size_t ob_size = static_cast<BoxVar*>(this)->ob_size;
return cls->tp_basicsize + ob_size * cls->tp_itemsize + cls->attrs_offset;
} else {
// This case is unlikely: why would we use a negative attrs_offset
// if it wasn't a var-sized object? But I guess it's technically allowed.
return cls->attrs_offset;
}
} else {
return cls->attrs_offset;
}
}
HCAttrs* Box::getHCAttrsPtr() {
char* p = reinterpret_cast<char*>(this);
p += cls->attrs_offset;
p += this->getHCAttrsOffset();
return reinterpret_cast<HCAttrs*>(p);
}
BoxedDict** Box::getDictPtr() {
assert(cls->instancesHaveDictAttrs());
RELEASE_ASSERT(cls->tp_dictoffset > 0, "not implemented: handle < 0 case like in getHCAttrsPtr");
char* p = reinterpret_cast<char*>(this);
p += cls->tp_dictoffset;
......@@ -670,23 +677,39 @@ Box* Box::getattr(const std::string& attr, GetattrRewriteArgs* rewrite_args) {
if (rewrite_args) {
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);
if (cls->attrs_offset < 0) {
REWRITE_ABORTED("");
rewrite_args = NULL;
} else {
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);
if (offset == -1) {
if (rewrite_args) {
rewrite_args->out_success = true;
}
return NULL;
}
if (rewrite_args) {
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());
if (cls->attrs_offset < 0) {
REWRITE_ABORTED("");
rewrite_args = NULL;
} else {
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());
}
}
if (rewrite_args) {
rewrite_args->out_success = true;
}
Box* rtn = attrs->attr_list->attrs[offset];
......@@ -707,8 +730,9 @@ Box* Box::getattr(const std::string& attr, GetattrRewriteArgs* rewrite_args) {
return it->second;
}
if (rewrite_args)
if (rewrite_args) {
rewrite_args->out_success = true;
}
return NULL;
}
......@@ -734,10 +758,15 @@ void Box::appendNewHCAttr(Box* new_attr, SetattrRewriteArgs* rewrite_args) {
} else {
attrs->attr_list = (HCAttrs::AttrList*)gc::gc_realloc(attrs->attr_list, new_size);
if (rewrite_args) {
RewriterVar* r_oldarray
= rewrite_args->obj->getAttr(cls->attrs_offset + HCATTRS_ATTRS_OFFSET, Location::forArg(0));
RewriterVar* r_newsize = rewrite_args->rewriter->loadConst(new_size, Location::forArg(1));
r_new_array2 = rewrite_args->rewriter->call(true, (void*)gc::gc_realloc, r_oldarray, r_newsize);
if (cls->attrs_offset < 0) {
REWRITE_ABORTED("");
rewrite_args = NULL;
} else {
RewriterVar* r_oldarray
= rewrite_args->obj->getAttr(cls->attrs_offset + HCATTRS_ATTRS_OFFSET, Location::forArg(0));
RewriterVar* r_newsize = rewrite_args->rewriter->loadConst(new_size, Location::forArg(1));
r_new_array2 = rewrite_args->rewriter->call(true, (void*)gc::gc_realloc, r_oldarray, r_newsize);
}
}
}
......@@ -787,9 +816,14 @@ void Box::setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite
int offset = hcls->getOffset(attr);
if (rewrite_args) {
rewrite_args->obj->addAttrGuard(cls->attrs_offset + HCATTRS_HCLS_OFFSET, (intptr_t)hcls);
if (hcls->type == HiddenClass::SINGLETON)
hcls->addDependence(rewrite_args->rewriter);
if (cls->attrs_offset < 0) {
REWRITE_ABORTED("");
rewrite_args = NULL;
} else {
rewrite_args->obj->addAttrGuard(cls->attrs_offset + HCATTRS_HCLS_OFFSET, (intptr_t)hcls);
if (hcls->type == HiddenClass::SINGLETON)
hcls->addDependence(rewrite_args->rewriter);
}
}
if (offset >= 0) {
......@@ -799,12 +833,17 @@ void Box::setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite
if (rewrite_args) {
RewriterVar* r_hattrs
= rewrite_args->obj->getAttr(cls->attrs_offset + HCATTRS_ATTRS_OFFSET, Location::any());
if (cls->attrs_offset < 0) {
REWRITE_ABORTED("");
rewrite_args = NULL;
} else {
RewriterVar* r_hattrs
= rewrite_args->obj->getAttr(cls->attrs_offset + HCATTRS_ATTRS_OFFSET, Location::any());
r_hattrs->setAttr(offset * sizeof(Box*) + ATTRLIST_ATTRS_OFFSET, rewrite_args->attrval);
r_hattrs->setAttr(offset * sizeof(Box*) + ATTRLIST_ATTRS_OFFSET, rewrite_args->attrval);
rewrite_args->out_success = true;
rewrite_args->out_success = true;
}
}
return;
......@@ -4066,6 +4105,28 @@ static void assertInitNone(Box* obj) {
}
}
void assertValidSlotIdentifier(Box* s) {
// Ported from `valid_identifier` in cpython
unsigned char* p;
size_t i, n;
if (!PyString_Check(s)) {
raiseExcHelper(TypeError, "__slots__ items must be strings, not '%.200s'", Py_TYPE(s)->tp_name);
}
p = (unsigned char*)PyString_AS_STRING(s);
n = PyString_GET_SIZE(s);
/* We must reject an empty name. As a hack, we bump the
length to 1 so that the loop will balk on the trailing \0. */
if (n == 0)
n = 1;
for (i = 0; i < n; i++, p++) {
if (!(i == 0 ? isalpha(*p) : isalnum(*p)) && *p != '_') {
raiseExcHelper(TypeError, "__slots__ must be identifiers");
}
}
}
Box* typeNew(Box* _cls, Box* arg1, Box* arg2, Box** _args) {
STAT_TIMER(t0, "us_timer_typeNew");
......@@ -4134,28 +4195,166 @@ Box* typeNew(Box* _cls, Box* arg1, Box* arg2, Box** _args) {
raiseExcHelper(TypeError, "type '%.100s' is not an acceptable base type", base->tp_name);
assert(isSubclass(base->cls, type_cls));
// Handle slots
Box* boxedSlots = PyDict_GetItemString(attr_dict, "__slots__");
int add_dict = 0;
int add_weak = 0;
bool may_add_dict = base->tp_dictoffset == 0 && base->attrs_offset == 0;
bool may_add_weak = base->tp_weaklistoffset == 0 && base->tp_itemsize == 0;
std::vector<Box*> final_slot_names;
if (boxedSlots == NULL) {
if (may_add_dict) {
add_dict++;
}
if (may_add_weak) {
add_weak++;
}
} else {
// Get a pointer to an array of slots.
std::vector<Box*> slots;
if (PyString_Check(boxedSlots) || PyUnicode_Check(boxedSlots)) {
slots = { boxedSlots };
} else {
BoxedTuple* tuple = static_cast<BoxedTuple*>(PySequence_Tuple(boxedSlots));
checkAndThrowCAPIException();
slots = std::vector<Box*>(tuple->size());
for (size_t i = 0; i < tuple->size(); i++) {
slots[i] = (*tuple)[i];
}
}
// TODO I don't think we have to implement the __slots__ memory savings behavior
// (we get almost all of that automatically with hidden classes), but adding a __slots__
// adds other restrictions (ex for multiple inheritance) that we won't end up enforcing.
// I guess it should be ok if we're more permissive?
// auto slots = PyDict_GetItemString(attr_dict, "__slots__");
// RELEASE_ASSERT(!slots, "__slots__ unsupported");
// Check that slots are allowed
if (slots.size() > 0 && base->tp_itemsize != 0) {
raiseExcHelper(TypeError, "nonempty __slots__ not supported for subtype of '%s'", base->tp_name);
}
// Convert unicode -> string
for (size_t i = 0; i < slots.size(); i++) {
Box* slot_name = slots[i];
if (PyUnicode_Check(slot_name)) {
slots[i] = _PyUnicode_AsDefaultEncodedString(slot_name, NULL);
checkAndThrowCAPIException();
}
}
BoxedClass* made;
// Check for valid slot names and two special cases
// Mangle and sort names
for (size_t i = 0; i < slots.size(); i++) {
Box* tmp = slots[i];
assertValidSlotIdentifier(tmp);
assert(PyString_Check(tmp));
if (static_cast<BoxedString*>(tmp)->s == "__dict__") {
if (!may_add_dict || add_dict) {
raiseExcHelper(TypeError, "__dict__ slot disallowed: "
"we already got one");
}
add_dict++;
continue;
} else if (static_cast<BoxedString*>(tmp)->s == "__weakref__") {
if (!may_add_weak || add_weak) {
raiseExcHelper(TypeError, "__weakref__ slot disallowed: "
"either we already got one, "
"or __itemsize__ != 0");
}
add_weak++;
continue;
}
if (base->instancesHaveDictAttrs() || base->instancesHaveHCAttrs()) {
made = BoxedHeapClass::create(metatype, base, NULL, base->attrs_offset, base->tp_weaklistoffset,
base->tp_basicsize, true, name, bases);
} else {
assert(base->tp_basicsize % sizeof(void*) == 0);
made = BoxedHeapClass::create(metatype, base, NULL, base->tp_basicsize, base->tp_weaklistoffset,
base->tp_basicsize + sizeof(HCAttrs), true, name, bases);
assert(tmp->cls == str_cls);
final_slot_names.push_back(mangleNameBoxedString(static_cast<BoxedString*>(tmp), name));
}
std::sort(final_slot_names.begin(), final_slot_names.end(), PyLt());
if (nbases > 1 && ((may_add_dict && !add_dict) || (may_add_weak && !add_weak))) {
for (size_t i = 0; i < nbases; i++) {
Box* tmp = PyTuple_GET_ITEM(bases, i);
if (tmp == (PyObject*)base)
continue; /* Skip primary base */
if (PyClass_Check(tmp)) {
/* Classic base class provides both */
if (may_add_dict && !add_dict)
add_dict++;
if (may_add_weak && !add_weak)
add_weak++;
break;
}
assert(PyType_Check(tmp));
BoxedClass* tmptype = static_cast<BoxedClass*>(tmp);
if (may_add_dict && !add_dict && (tmptype->tp_dictoffset != 0 || tmptype->attrs_offset != 0))
add_dict++;
if (may_add_weak && !add_weak && tmptype->tp_weaklistoffset != 0)
add_weak++;
if (may_add_dict && !add_dict)
continue;
if (may_add_weak && !add_weak)
continue;
/* Nothing more to check */
break;
}
}
}
// TODO: how much of these should be in BoxedClass::finishInitialization()?
made->tp_dictoffset = base->tp_dictoffset;
int attrs_offset = base->attrs_offset;
int dict_offset = base->tp_dictoffset;
int weaklist_offset = 0;
int basic_size = 0;
int cur_offset = base->tp_basicsize + sizeof(Box*) * final_slot_names.size();
if (add_dict) {
// CPython would set tp_dictoffset here, but we want to use attrs instead.
if (base->tp_itemsize) {
// A negative value indicates an offset from the end of the object
attrs_offset = -(long)sizeof(HCAttrs);
} else {
attrs_offset = cur_offset;
}
cur_offset += sizeof(HCAttrs);
}
if (add_weak) {
assert(!base->tp_itemsize);
weaklist_offset = cur_offset;
cur_offset += sizeof(Box*);
}
basic_size = cur_offset;
size_t total_slots = final_slot_names.size()
+ (base->tp_flags & Py_TPFLAGS_HEAPTYPE ? static_cast<BoxedHeapClass*>(base)->nslots() : 0);
BoxedHeapClass* made = BoxedHeapClass::create(metatype, base, NULL, attrs_offset, weaklist_offset, basic_size, true,
name, bases, total_slots);
made->tp_dictoffset = dict_offset;
if (boxedSlots) {
// Set ht_slots
BoxedTuple* slotsTuple = BoxedTuple::create(final_slot_names.size());
for (size_t i = 0; i < final_slot_names.size(); i++)
(*slotsTuple)[i] = final_slot_names[i];
assert(made->tp_flags & Py_TPFLAGS_HEAPTYPE);
static_cast<BoxedHeapClass*>(made)->ht_slots = slotsTuple;
BoxedHeapClass::SlotOffset* slot_offsets = made->slotOffsets();
size_t slot_offset_offset = made->tp_basicsize;
// Add the member descriptors
size_t offset = base->tp_basicsize;
for (size_t i = 0; i < final_slot_names.size(); i++) {
made->giveAttr(static_cast<BoxedString*>(slotsTuple->elts[i])->s.data(),
new BoxedMemberDescriptor(BoxedMemberDescriptor::OBJECT_EX, offset, false /* read only */));
slot_offsets[i] = offset;
offset += sizeof(Box*);
}
}
// Add slot offsets for slots of the base
// NOTE: CPython does this, but I don't want to have to traverse the class hierarchy to
// traverse all the slots, so I'm putting them all here.
if (base->tp_flags & Py_TPFLAGS_HEAPTYPE) {
BoxedHeapClass::SlotOffset* slot_offsets = made->slotOffsets();
BoxedHeapClass* base_heap_cls = static_cast<BoxedHeapClass*>(base);
BoxedHeapClass::SlotOffset* base_slot_offsets = base_heap_cls->slotOffsets();
memcpy(&slot_offsets[final_slot_names.size()], base_slot_offsets,
base_heap_cls->nslots() * sizeof(BoxedHeapClass::SlotOffset));
}
if (!made->getattr("__dict__") && (made->instancesHaveHCAttrs() || made->instancesHaveDictAttrs()))
made->giveAttr("__dict__", dict_descr);
......
......@@ -94,7 +94,7 @@ bool IN_SHUTDOWN = false;
extern "C" PyObject* PystonType_GenericAlloc(BoxedClass* cls, Py_ssize_t nitems) noexcept {
assert(cls);
const size_t size = _PyObject_VAR_SIZE(cls, nitems + 1);
const size_t size = _PyObject_VAR_SIZE(cls, nitems);
#ifndef NDEBUG
#if 0
......@@ -132,7 +132,10 @@ extern "C" PyObject* PystonType_GenericAlloc(BoxedClass* cls, Py_ssize_t nitems)
Box* rtn = static_cast<Box*>(mem);
PyObject_Init(rtn, cls);
if (cls->tp_itemsize != 0)
static_cast<BoxVar*>(rtn)->ob_size = nitems;
PyObject_INIT(rtn, cls);
assert(rtn->cls);
return rtn;
......@@ -140,7 +143,7 @@ extern "C" PyObject* PystonType_GenericAlloc(BoxedClass* cls, Py_ssize_t nitems)
extern "C" PyObject* PyType_GenericAlloc(PyTypeObject* type, Py_ssize_t nitems) noexcept {
PyObject* obj;
const size_t size = _PyObject_VAR_SIZE(type, nitems + 1);
const size_t size = _PyObject_VAR_SIZE(type, nitems);
/* note that we need to add one, for the sentinel */
if (PyType_IS_GC(type))
......@@ -175,9 +178,21 @@ extern "C" PyObject* _PyObject_New(PyTypeObject* tp) noexcept {
}
// Analogue of PyType_GenericNew
void* BoxVar::operator new(size_t size, BoxedClass* cls, size_t nitems) {
assert(cls);
ASSERT(cls->tp_basicsize >= size, "%s", cls->tp_name);
assert(cls->tp_itemsize > 0);
assert(cls->tp_alloc);
void* mem = cls->tp_alloc(cls, nitems);
RELEASE_ASSERT(mem, "");
return mem;
}
void* Box::operator new(size_t size, BoxedClass* cls) {
assert(cls);
ASSERT(cls->tp_basicsize >= size, "%s", cls->tp_name);
assert(cls->tp_itemsize == 0);
assert(cls->tp_alloc);
void* mem = cls->tp_alloc(cls, 0);
......@@ -449,6 +464,14 @@ extern "C" void boxGCHandler(GCVisitor* v, Box* b) {
if (b->cls->instancesHaveDictAttrs()) {
RELEASE_ASSERT(0, "Shouldn't all of these objects be conservatively scanned?");
}
if (b->cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
BoxedHeapClass* heap_cls = static_cast<BoxedHeapClass*>(b->cls);
BoxedHeapClass::SlotOffset* slotOffsets = heap_cls->slotOffsets();
for (int i = 0; i < heap_cls->nslots(); i++) {
v->visit(*((Box**)((char*)b + slotOffsets[i])));
}
}
} else {
assert(type_cls == NULL || b == type_cls);
}
......@@ -2047,6 +2070,20 @@ extern "C" PyObject* PyObject_Init(PyObject* op, PyTypeObject* tp) noexcept {
// initUserAttrs themselves, though.
initUserAttrs(op, tp);
#ifndef NDEBUG
if (tp->tp_flags & Py_TPFLAGS_HEAPTYPE) {
BoxedHeapClass* heap_cls = static_cast<BoxedHeapClass*>(tp);
if (heap_cls->nslots() > 0) {
BoxedHeapClass::SlotOffset* slotOffsets = heap_cls->slotOffsets();
for (int i = 0; i < heap_cls->nslots(); i++) {
// This should be set to 0 on allocation:
// (If it wasn't, we would need to initialize it to 0 here.)
assert(*(Box**)((char*)op + slotOffsets[i]) == NULL);
}
}
}
#endif
return op;
}
......@@ -2076,18 +2113,20 @@ void setupRuntime() {
type_cls = ::new (mem) BoxedHeapClass(object_cls, &typeGCHandler, offsetof(BoxedClass, attrs),
offsetof(BoxedClass, tp_weaklist), sizeof(BoxedHeapClass), false, NULL);
type_cls->tp_flags |= Py_TPFLAGS_TYPE_SUBCLASS;
type_cls->tp_itemsize = sizeof(BoxedHeapClass::SlotOffset);
PyObject_Init(object_cls, type_cls);
PyObject_Init(type_cls, type_cls);
none_cls = new BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(Box), false, NULL);
none_cls = new (0) BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(Box), false, NULL);
None = new (none_cls) Box();
assert(None->cls);
gc::registerPermanentRoot(None);
// You can't actually have an instance of basestring
basestring_cls = new BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(Box), false, NULL);
basestring_cls = new (0) BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(Box), false, NULL);
str_cls = new BoxedHeapClass(basestring_cls, NULL, 0, 0, sizeof(BoxedString), false, NULL);
// We add 1 to the tp_basicsize of the BoxedString in order to hold the null byte at the end.
str_cls = new (0) BoxedHeapClass(basestring_cls, NULL, 0, 0, sizeof(BoxedString) + 1, false, NULL);
str_cls->tp_flags |= Py_TPFLAGS_STRING_SUBCLASS;
str_cls->tp_itemsize = sizeof(char);
......@@ -2116,58 +2155,60 @@ void setupRuntime() {
object_cls->giveAttr("__base__", None);
tuple_cls
= new BoxedHeapClass(object_cls, &tupleGCHandler, 0, 0, sizeof(BoxedTuple), false, boxStrConstant("tuple"));
tuple_cls = new (0)
BoxedHeapClass(object_cls, &tupleGCHandler, 0, 0, sizeof(BoxedTuple), false, boxStrConstant("tuple"));
tuple_cls->tp_flags |= Py_TPFLAGS_TUPLE_SUBCLASS;
tuple_cls->tp_itemsize = sizeof(Box*);
tuple_cls->tp_mro = BoxedTuple::create({ tuple_cls, object_cls });
EmptyTuple = BoxedTuple::create({});
gc::registerPermanentRoot(EmptyTuple);
list_cls = new BoxedHeapClass(object_cls, &listGCHandler, 0, 0, sizeof(BoxedList), false, boxStrConstant("list"));
list_cls = new (0)
BoxedHeapClass(object_cls, &listGCHandler, 0, 0, sizeof(BoxedList), false, boxStrConstant("list"));
list_cls->tp_flags |= Py_TPFLAGS_LIST_SUBCLASS;
pyston_getset_cls
= new BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedGetsetDescriptor), false, boxStrConstant("getset"));
attrwrapper_cls = new BoxedHeapClass(object_cls, &AttrWrapper::gcHandler, 0, 0, sizeof(AttrWrapper), false,
static_cast<BoxedString*>(boxStrConstant("attrwrapper")));
dict_cls = new BoxedHeapClass(object_cls, &dictGCHandler, 0, 0, sizeof(BoxedDict), false,
static_cast<BoxedString*>(boxStrConstant("dict")));
pyston_getset_cls = new (0)
BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedGetsetDescriptor), false, boxStrConstant("getset"));
attrwrapper_cls = new (0) BoxedHeapClass(object_cls, &AttrWrapper::gcHandler, 0, 0, sizeof(AttrWrapper), false,
static_cast<BoxedString*>(boxStrConstant("attrwrapper")));
dict_cls = new (0) BoxedHeapClass(object_cls, &dictGCHandler, 0, 0, sizeof(BoxedDict), false,
static_cast<BoxedString*>(boxStrConstant("dict")));
dict_cls->tp_flags |= Py_TPFLAGS_DICT_SUBCLASS;
file_cls = new BoxedHeapClass(object_cls, &BoxedFile::gcHandler, 0, offsetof(BoxedFile, weakreflist),
sizeof(BoxedFile), false, static_cast<BoxedString*>(boxStrConstant("file")));
int_cls = new BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedInt), false,
static_cast<BoxedString*>(boxStrConstant("int")));
file_cls = new (0) BoxedHeapClass(object_cls, &BoxedFile::gcHandler, 0, offsetof(BoxedFile, weakreflist),
sizeof(BoxedFile), false, static_cast<BoxedString*>(boxStrConstant("file")));
int_cls = new (0) BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedInt), false,
static_cast<BoxedString*>(boxStrConstant("int")));
int_cls->tp_flags |= Py_TPFLAGS_INT_SUBCLASS;
bool_cls = new BoxedHeapClass(int_cls, NULL, 0, 0, sizeof(BoxedBool), false,
static_cast<BoxedString*>(boxStrConstant("bool")));
complex_cls = new BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedComplex), false,
static_cast<BoxedString*>(boxStrConstant("complex")));
long_cls = new BoxedHeapClass(object_cls, &BoxedLong::gchandler, 0, 0, sizeof(BoxedLong), false,
static_cast<BoxedString*>(boxStrConstant("long")));
bool_cls = new (0) BoxedHeapClass(int_cls, NULL, 0, 0, sizeof(BoxedBool), false,
static_cast<BoxedString*>(boxStrConstant("bool")));
complex_cls = new (0) BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedComplex), false,
static_cast<BoxedString*>(boxStrConstant("complex")));
long_cls = new (0) BoxedHeapClass(object_cls, &BoxedLong::gchandler, 0, 0, sizeof(BoxedLong), false,
static_cast<BoxedString*>(boxStrConstant("long")));
long_cls->tp_flags |= Py_TPFLAGS_LONG_SUBCLASS;
float_cls = new BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedFloat), false,
static_cast<BoxedString*>(boxStrConstant("float")));
function_cls = new BoxedHeapClass(object_cls, &functionGCHandler, offsetof(BoxedFunction, attrs),
offsetof(BoxedFunction, in_weakreflist), sizeof(BoxedFunction), false,
static_cast<BoxedString*>(boxStrConstant("function")));
builtin_function_or_method_cls
= new BoxedHeapClass(object_cls, &functionGCHandler, 0, offsetof(BoxedBuiltinFunctionOrMethod, in_weakreflist),
sizeof(BoxedBuiltinFunctionOrMethod), false,
static_cast<BoxedString*>(boxStrConstant("builtin_function_or_method")));
float_cls = new (0) BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedFloat), false,
static_cast<BoxedString*>(boxStrConstant("float")));
function_cls = new (0) BoxedHeapClass(object_cls, &functionGCHandler, offsetof(BoxedFunction, attrs),
offsetof(BoxedFunction, in_weakreflist), sizeof(BoxedFunction), false,
static_cast<BoxedString*>(boxStrConstant("function")));
builtin_function_or_method_cls = new (0)
BoxedHeapClass(object_cls, &functionGCHandler, 0, offsetof(BoxedBuiltinFunctionOrMethod, in_weakreflist),
sizeof(BoxedBuiltinFunctionOrMethod), false,
static_cast<BoxedString*>(boxStrConstant("builtin_function_or_method")));
function_cls->simple_destructor = builtin_function_or_method_cls->simple_destructor = functionDtor;
module_cls = new BoxedHeapClass(object_cls, &moduleGCHandler, offsetof(BoxedModule, attrs), 0, sizeof(BoxedModule),
false, static_cast<BoxedString*>(boxStrConstant("module")));
member_descriptor_cls = new BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedMemberDescriptor), false,
static_cast<BoxedString*>(boxStrConstant("member_descriptor")));
capifunc_cls = new BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedCApiFunction), false,
static_cast<BoxedString*>(boxStrConstant("capifunc")));
method_cls = new BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedMethodDescriptor), false,
static_cast<BoxedString*>(boxStrConstant("method")));
wrapperobject_cls = new BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedWrapperObject), false,
static_cast<BoxedString*>(boxStrConstant("method-wrapper")));
wrapperdescr_cls = new BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedWrapperDescriptor), false,
static_cast<BoxedString*>(boxStrConstant("wrapper_descriptor")));
module_cls = new (0)
BoxedHeapClass(object_cls, &moduleGCHandler, offsetof(BoxedModule, attrs), 0, sizeof(BoxedModule), false,
static_cast<BoxedString*>(boxStrConstant("module")));
member_descriptor_cls = new (0) BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedMemberDescriptor), false,
static_cast<BoxedString*>(boxStrConstant("member_descriptor")));
capifunc_cls = new (0) BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedCApiFunction), false,
static_cast<BoxedString*>(boxStrConstant("capifunc")));
method_cls = new (0) BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedMethodDescriptor), false,
static_cast<BoxedString*>(boxStrConstant("method")));
wrapperobject_cls = new (0) BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedWrapperObject), false,
static_cast<BoxedString*>(boxStrConstant("method-wrapper")));
wrapperdescr_cls = new (0) BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedWrapperDescriptor), false,
static_cast<BoxedString*>(boxStrConstant("wrapper_descriptor")));
EmptyString = boxStrConstant("");
gc::registerPermanentRoot(EmptyString);
......
......@@ -177,7 +177,10 @@ public:
void (*simple_destructor)(Box*);
// Offset of the HCAttrs object or 0 if there are no hcattrs.
// Negative offset is from the end of the class (useful for variable-size objects with the attrs at the end)
// Analogous to tp_dictoffset
// A class should have at most of one attrs_offset and tp_dictoffset be nonzero.
// (But having nonzero attrs_offset here would map to having nonzero tp_dictoffset in CPython)
const int attrs_offset;
bool instancesHaveHCAttrs() { return attrs_offset != 0; }
......@@ -231,12 +234,16 @@ public:
PyBufferProcs as_buffer;
BoxedString* ht_name;
PyObject** ht_slots;
PyObject* ht_slots;
typedef size_t SlotOffset;
SlotOffset* slotOffsets() { return (BoxedHeapClass::SlotOffset*)((char*)this + this->cls->tp_basicsize); }
size_t nslots() { return this->ob_size; }
// These functions are the preferred way to construct new types:
static BoxedHeapClass* create(BoxedClass* metatype, BoxedClass* base, gcvisit_func gc_visit, int attrs_offset,
int weaklist_offset, int instance_size, bool is_user_defined, BoxedString* name,
BoxedTuple* bases);
BoxedTuple* bases, size_t nslots);
static BoxedHeapClass* create(BoxedClass* metatype, BoxedClass* base, gcvisit_func gc_visit, int attrs_offset,
int weaklist_offset, int instance_size, bool is_user_defined,
const std::string& name);
......@@ -251,7 +258,7 @@ private:
friend void setupRuntime();
friend void setupSys();
DEFAULT_CLASS(type_cls);
DEFAULT_CLASS_VAR(type_cls, sizeof(SlotOffset));
};
static_assert(sizeof(pyston::Box) == sizeof(struct _object), "");
......@@ -417,7 +424,7 @@ public:
DEFAULT_CLASS_SIMPLE(bool_cls);
};
class BoxedString : public Box {
class BoxedString : public BoxVar {
public:
llvm::StringRef s;
char interned_state;
......@@ -425,40 +432,28 @@ public:
char* data() { return const_cast<char*>(s.data()); }
size_t size() { return s.size(); }
void* operator new(size_t size, size_t ssize) __attribute__((visibility("default"))) {
#if STAT_ALLOCATIONS
static StatCounter alloc_str("alloc.str");
static StatCounter alloc_str1("alloc.str(1)");
static StatCounter allocsize_str("allocsize.str");
if (ssize == 1)
alloc_str1.log();
else
alloc_str.log();
// DEFAULT_CLASS_VAR_SIMPLE doesn't work because of the +1 for the null byte
void* operator new(size_t size, BoxedClass* cls, size_t nitems) __attribute__((visibility("default"))) {
ALLOC_STATS_VAR(str_cls)
allocsize_str.log(str_cls->tp_basicsize + ssize + 1);
#endif
Box* rtn = static_cast<Box*>(gc_alloc(str_cls->tp_basicsize + ssize + 1, gc::GCKind::PYTHON));
rtn->cls = str_cls;
return rtn;
assert(cls->tp_itemsize == sizeof(char));
return BoxVar::operator new(size, cls, nitems);
}
void* operator new(size_t size, size_t nitems) __attribute__((visibility("default"))) {
ALLOC_STATS_VAR(str_cls)
void* operator new(size_t size, BoxedClass* cls, size_t ssize) __attribute__((visibility("default"))) {
#if STAT_ALLOCATIONS
static StatCounter alloc_str("alloc.str");
static StatCounter alloc_str1("alloc.str(1)");
static StatCounter allocsize_str("allocsize.str");
assert(str_cls->tp_alloc == PystonType_GenericAlloc);
assert(str_cls->tp_itemsize == 1);
assert(str_cls->tp_basicsize == sizeof(BoxedString) + 1);
assert(str_cls->is_pyston_class);
assert(str_cls->attrs_offset == 0);
if (ssize == 1)
alloc_str1.log();
else
alloc_str.log();
void* mem = gc_alloc(sizeof(BoxedString) + 1 + nitems, gc::GCKind::PYTHON);
assert(mem);
allocsize_str.log(cls->tp_basicsize + ssize + 1);
#endif
Box* rtn = static_cast<Box*>(cls->tp_alloc(cls, ssize + 1));
rtn->cls = cls;
BoxVar* rtn = static_cast<BoxVar*>(mem);
rtn->cls = str_cls;
rtn->ob_size = nitems;
return rtn;
}
......@@ -470,7 +465,8 @@ public:
private:
// used only in ctors to give our llvm::StringRef the proper pointer
char* storage() { return (char*)this + cls->tp_basicsize; }
// Note: sizeof(BoxedString) = str_cls->tp_basicsize - 1
char* storage() { return (char*)this + sizeof(BoxedString); }
void* operator new(size_t size) = delete;
};
......@@ -551,54 +547,13 @@ public:
DEFAULT_CLASS_SIMPLE(list_cls);
};
class BoxedTuple : public Box {
class BoxedTuple : public BoxVar {
public:
typedef std::vector<Box*, StlCompatAllocator<Box*>> GCVector;
Box** elts;
void* operator new(size_t size, size_t nelts) __attribute__((visibility("default"))) {
#if STAT_ALLOCATIONS
static StatCounter alloc_tuple("alloc.tuple");
static StatCounter alloc_tuple0("alloc.tuple(0)");
static StatCounter allocsize_tuple("allocsize.tuple");
static StatCounter allocsize_tuple0("allocsize.tuple(0)");
if (nelts == 0) {
alloc_tuple0.log();
allocsize_tuple0.log(_PyObject_VAR_SIZE(tuple_cls, nelts + 1));
} else {
alloc_tuple.log();
allocsize_tuple.log(_PyObject_VAR_SIZE(tuple_cls, nelts + 1));
}
#endif
Box* rtn = static_cast<Box*>(gc_alloc(_PyObject_VAR_SIZE(tuple_cls, nelts + 1), gc::GCKind::PYTHON));
rtn->cls = tuple_cls;
return rtn;
}
void* operator new(size_t size, BoxedClass* cls, size_t nelts) __attribute__((visibility("default"))) {
#if STAT_ALLOCATIONS
static StatCounter alloc_tuple("alloc.tuple");
static StatCounter alloc_tuple0("alloc.tuple(0)");
static StatCounter allocsize_tuple("allocsize.tuple");
static StatCounter allocsize_tuple0("allocsize.tuple(0)");
if (nelts == 0) {
alloc_tuple0.log();
allocsize_tuple0.log(_PyObject_VAR_SIZE(cls, nelts + 1));
} else {
alloc_tuple.log();
allocsize_tuple.log(_PyObject_VAR_SIZE(cls, nelts + 1));
}
#endif
Box* rtn = static_cast<Box*>(cls->tp_alloc(cls, nelts));
rtn->cls = cls;
return rtn;
}
DEFAULT_CLASS_VAR_SIMPLE(tuple_cls, sizeof(Box*));
static BoxedTuple* create(int64_t size) { return new (size) BoxedTuple(size); }
static BoxedTuple* create(int64_t nelts, Box** elts) {
......@@ -640,18 +595,19 @@ public:
Box** begin() const { return &elts[0]; }
Box** end() const { return &elts[nelts]; }
Box*& operator[](size_t index) { return elts[index]; }
size_t size() const { return nelts; }
private:
size_t nelts;
BoxedTuple(size_t size) : elts(reinterpret_cast<Box**>((char*)this + this->cls->tp_basicsize)), nelts(size) {
BoxedTuple(size_t size) : elts(reinterpret_cast<Box**>((char*)this + tuple_cls->tp_basicsize)), nelts(size) {
memset(elts, 0, sizeof(Box*) * size);
}
BoxedTuple(std::initializer_list<Box*>& members)
: elts(reinterpret_cast<Box**>((char*)this + this->cls->tp_basicsize)), nelts(members.size()) {
: elts(reinterpret_cast<Box**>((char*)this + tuple_cls->tp_basicsize)), nelts(members.size()) {
// by the time we make it here elts[] is big enough to contain members
Box** p = &elts[0];
for (auto b : members) {
......
print 'basic test'
class C(object):
__slots__ = ['a', 'b', '__private_var']
c = C()
try:
print c.a
except AttributeError as e:
print e.message
c.a = 5
print c.a
print c.__slots__
c._C__private_var = 6
print c._C__private_var
try:
c.x = 12
except AttributeError as e:
print e.message
print 'testing __dict__'
class C(object):
__slots__ = ['d', 'e', '__dict__']
c = C()
c.d = 5
print c.d
c.r = 6
print c.r
print c.__dict__.items() # dict should contain only r (not d)
print 'testing inheritance'
class C(object):
__slots__ = ['a', 'b']
class D(object):
__slots__ = ['c', 'd']
class E(object):
pass
class G(C):
__slots__ = ['k', 'l']
g = G()
g.a = 5
print g.a
g.k = 12
print g.k
class G(C, E):
__slots__ = ['k', 'l']
g = G()
g.a = 5
print g.a
g.k = 12
print g.k
class G(E, C):
__slots__ = ['k', 'l']
g = G()
g.a = 5
print g.a
g.k = 12
print g.k
try:
class G(C, D):
pass
except TypeError as e:
print e.message
print 'testing a lot of slots'
class C(object):
__slots__ = ['a' + str(i) for i in xrange(1000)]
c = C()
c.a0 = -8
print c.a0
for i in xrange(1000):
setattr(c, 'a' + str(i), i)
for i in xrange(1000):
print getattr(c, 'a' + str(i))
print 'slots on a subclass of str'
try:
class C(str):
__slots__ = ['a']
except TypeError as e:
print e.message
print 'slots on a class with a metaclass'
class M(type):
def __new__(*args):
print "M.__new__", args[:3]
return type.__new__(*args)
def __call__(*args):
print "M.__call__", args[:3]
return type.__call__(*args)
class C(object):
__metaclass__ = M
__slots__ = ['a', 'b']
c = C()
c.a = 5
print c.a
c.b = 6
print c.b
......@@ -8,3 +8,8 @@ def f():
for i in xrange(100):
s = S(base)
f()
# make sure it has attrs
s = S("blah")
s.blah = 2
print s.blah
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