Commit b0e93d19 authored by Kevin Modzelewski's avatar Kevin Modzelewski

Improve tuple unpacking behavior

- Use the right unpacking protocol (ie don't check __len__, just try to iterate)
- Handle unpacking exceptions appropriately
- Expand the targets of assigns correctly (think: f().x = 1)
-- this was not just for tuples but came up here first; this also was broken:
[0 for i in xrange(5)][0] = 1
(silly but legal)
parent 1fbf29e8
...@@ -353,7 +353,7 @@ private: ...@@ -353,7 +353,7 @@ private:
ConcreteCompilerVariable* rtn = new ConcreteCompilerVariable(DICT, v, true); ConcreteCompilerVariable* rtn = new ConcreteCompilerVariable(DICT, v, true);
for (auto& p : symbol_table) { for (auto& p : symbol_table) {
if (p.first[0] == '!') if (p.first[0] == '!' || p.first[0] == '#')
continue; continue;
ConcreteCompilerVariable* is_defined_var = static_cast<ConcreteCompilerVariable*>( ConcreteCompilerVariable* is_defined_var = static_cast<ConcreteCompilerVariable*>(
...@@ -1260,14 +1260,33 @@ private: ...@@ -1260,14 +1260,33 @@ private:
void _doUnpackTuple(AST_Tuple* target, CompilerVariable* val, ExcInfo exc_info) { void _doUnpackTuple(AST_Tuple* target, CompilerVariable* val, ExcInfo exc_info) {
assert(state != PARTIAL); assert(state != PARTIAL);
int ntargets = target->elts.size(); int ntargets = target->elts.size();
// TODO do type recording here?
ConcreteCompilerVariable* len = val->len(emitter, getEmptyOpInfo(exc_info)); // TODO can do faster unpacking of non-instantiated tuples; ie for something like
emitter.createCall2(exc_info, g.funcs.checkUnpackingLength, getConstantInt(ntargets, g.i64), len->getValue()); // a, b = 1, 2
// We shouldn't need to do any runtime error checking or allocations
#ifndef NDEBUG
for (auto e : target->elts) {
ASSERT(e->type == AST_TYPE::Name && ast_cast<AST_Name>(e)->id[0] == '#',
"should only be unpacking tuples into cfg-generated names!");
}
#endif
ConcreteCompilerVariable* converted_val = val->makeConverted(emitter, val->getBoxType());
llvm::Value* unpacked = emitter.createCall2(exc_info, g.funcs.unpackIntoArray, converted_val->getValue(),
getConstantInt(ntargets, g.i64)).getInstruction();
assert(unpacked->getType() == g.llvm_value_type_ptr->getPointerTo());
converted_val->decvref(emitter);
for (int i = 0; i < ntargets; i++) { for (int i = 0; i < ntargets; i++) {
CompilerVariable* unpacked = val->getitem(emitter, getEmptyOpInfo(exc_info), makeInt(i)); llvm::Value* ptr = emitter.getBuilder()->CreateConstGEP1_32(unpacked, i);
_doSet(target->elts[i], unpacked, exc_info); llvm::Value* val = emitter.getBuilder()->CreateLoad(ptr);
unpacked->decvref(emitter); assert(val->getType() == g.llvm_value_type_ptr);
CompilerVariable* thisval = new ConcreteCompilerVariable(UNKNOWN, val, true);
_doSet(target->elts[i], thisval, exc_info);
thisval->decvref(emitter);
} }
} }
......
...@@ -200,7 +200,7 @@ void initGlobalFuncs(GlobalState& g) { ...@@ -200,7 +200,7 @@ void initGlobalFuncs(GlobalState& g) {
GET(yield); GET(yield);
GET(getiter); GET(getiter);
GET(checkUnpackingLength); GET(unpackIntoArray);
GET(raiseAttributeError); GET(raiseAttributeError);
GET(raiseAttributeErrorStr); GET(raiseAttributeErrorStr);
GET(raiseNotIterableError); GET(raiseNotIterableError);
......
...@@ -39,7 +39,7 @@ struct GlobalFuncs { ...@@ -39,7 +39,7 @@ struct GlobalFuncs {
*unboxedLen, *getitem, *getclsattr, *getGlobal, *setitem, *unaryop, *import, *importFrom, *importStar, *repr, *unboxedLen, *getitem, *getclsattr, *getGlobal, *setitem, *unaryop, *import, *importFrom, *importStar, *repr,
*str, *isinstance, *yield, *getiter; *str, *isinstance, *yield, *getiter;
llvm::Value* checkUnpackingLength, *raiseAttributeError, *raiseAttributeErrorStr, *raiseNotIterableError, llvm::Value* unpackIntoArray, *raiseAttributeError, *raiseAttributeErrorStr, *raiseNotIterableError,
*assertNameDefined, *assertFail; *assertNameDefined, *assertFail;
llvm::Value* printFloat, *listAppendInternal; llvm::Value* printFloat, *listAppendInternal;
llvm::Value* runtimeCall0, *runtimeCall1, *runtimeCall2, *runtimeCall3, *runtimeCall; llvm::Value* runtimeCall0, *runtimeCall1, *runtimeCall2, *runtimeCall3, *runtimeCall;
......
...@@ -914,6 +914,7 @@ public: ...@@ -914,6 +914,7 @@ public:
// but aren't directly *exactly* representable as normal Python. // but aren't directly *exactly* representable as normal Python.
// ClsAttribute would fall into this category, as would isinstance (which // ClsAttribute would fall into this category, as would isinstance (which
// is not the same as the "isinstance" name since that could get redefined). // is not the same as the "isinstance" name since that could get redefined).
// These are basically bytecodes, framed as pseudo-AST-nodes.
class AST_LangPrimitive : public AST_expr { class AST_LangPrimitive : public AST_expr {
public: public:
enum Opcodes { enum Opcodes {
......
This diff is collapsed.
...@@ -92,7 +92,7 @@ void force() { ...@@ -92,7 +92,7 @@ void force() {
FORCE(yield); FORCE(yield);
FORCE(getiter); FORCE(getiter);
FORCE(checkUnpackingLength); FORCE(unpackIntoArray);
FORCE(raiseAttributeError); FORCE(raiseAttributeError);
FORCE(raiseAttributeErrorStr); FORCE(raiseAttributeErrorStr);
FORCE(raiseNotIterableError); FORCE(raiseNotIterableError);
......
...@@ -368,7 +368,7 @@ extern "C" void raiseNotIterableError(const char* typeName) { ...@@ -368,7 +368,7 @@ extern "C" void raiseNotIterableError(const char* typeName) {
raiseExcHelper(TypeError, "'%s' object is not iterable", typeName); raiseExcHelper(TypeError, "'%s' object is not iterable", typeName);
} }
extern "C" void checkUnpackingLength(i64 expected, i64 given) { static void _checkUnpackingLength(i64 expected, i64 given) {
if (given == expected) if (given == expected)
return; return;
...@@ -382,6 +382,32 @@ extern "C" void checkUnpackingLength(i64 expected, i64 given) { ...@@ -382,6 +382,32 @@ extern "C" void checkUnpackingLength(i64 expected, i64 given) {
} }
} }
extern "C" Box** unpackIntoArray(Box* obj, int64_t expected_size) {
assert(expected_size > 0);
if (obj->cls == tuple_cls) {
BoxedTuple* t = static_cast<BoxedTuple*>(obj);
_checkUnpackingLength(expected_size, t->elts.size());
return &t->elts[0];
}
if (obj->cls == list_cls) {
BoxedList* l = static_cast<BoxedList*>(obj);
_checkUnpackingLength(expected_size, l->size);
return &l->elts->elts[0];
}
BoxedTuple::GCVector elts;
for (auto e : obj->pyElements()) {
elts.push_back(e);
if (elts.size() > expected_size)
break;
}
_checkUnpackingLength(expected_size, elts.size());
return &elts[0];
}
BoxedClass::BoxedClass(BoxedClass* metaclass, BoxedClass* base, gcvisit_func gc_visit, int attrs_offset, BoxedClass::BoxedClass(BoxedClass* metaclass, BoxedClass* base, gcvisit_func gc_visit, int attrs_offset,
int instance_size, bool is_user_defined) int instance_size, bool is_user_defined)
: BoxVar(metaclass, 0), tp_basicsize(instance_size), tp_dealloc(NULL), base(base), gc_visit(gc_visit), : BoxVar(metaclass, 0), tp_basicsize(instance_size), tp_dealloc(NULL), base(base), gc_visit(gc_visit),
...@@ -3216,6 +3242,12 @@ extern "C" Box* getiter(Box* o) { ...@@ -3216,6 +3242,12 @@ extern "C" Box* getiter(Box* o) {
raiseExcHelper(TypeError, "'%s' object is not iterable", getTypeName(o)->c_str()); raiseExcHelper(TypeError, "'%s' object is not iterable", getTypeName(o)->c_str());
} }
llvm::iterator_range<BoxIterator> Box::pyElements() {
Box* iter = getiter(this);
assert(iter);
return llvm::iterator_range<BoxIterator>(++BoxIterator(iter), BoxIterator(nullptr));
}
// For use on __init__ return values // For use on __init__ return values
static void assertInitNone(Box* obj) { static void assertInitNone(Box* obj) {
if (obj != None) { if (obj != None) {
......
...@@ -78,7 +78,7 @@ extern "C" Box* unaryop(Box* operand, int op_type); ...@@ -78,7 +78,7 @@ extern "C" Box* unaryop(Box* operand, int op_type);
extern "C" Box* import(const std::string* name); extern "C" Box* import(const std::string* name);
extern "C" Box* importFrom(Box* obj, const std::string* attr); extern "C" Box* importFrom(Box* obj, const std::string* attr);
extern "C" void importStar(Box* from_module, BoxedModule* to_module); extern "C" void importStar(Box* from_module, BoxedModule* to_module);
extern "C" void checkUnpackingLength(i64 expected, i64 given); extern "C" Box** unpackIntoArray(Box* obj, int64_t expected_size);
extern "C" void assertNameDefined(bool b, const char* name, BoxedClass* exc_cls, bool local_var_msg); extern "C" void assertNameDefined(bool b, const char* name, BoxedClass* exc_cls, bool local_var_msg);
extern "C" void assertFail(BoxedModule* inModule, Box* msg); extern "C" void assertFail(BoxedModule* inModule, Box* msg);
extern "C" bool isSubclass(BoxedClass* child, BoxedClass* parent); extern "C" bool isSubclass(BoxedClass* child, BoxedClass* parent);
......
...@@ -81,17 +81,6 @@ void BoxIterator::gcHandler(GCVisitor* v) { ...@@ -81,17 +81,6 @@ void BoxIterator::gcHandler(GCVisitor* v) {
v->visitPotential(value); v->visitPotential(value);
} }
llvm::iterator_range<BoxIterator> Box::pyElements() {
static std::string iter_str("__iter__");
Box* iter = callattr(const_cast<Box*>(this), &iter_str, true, ArgPassSpec(0), NULL, NULL, NULL, NULL, NULL);
if (iter) {
return llvm::iterator_range<BoxIterator>(++BoxIterator(iter), BoxIterator(nullptr));
}
raiseExcHelper(TypeError, "'%s' object is not iterable", getTypeName(this)->c_str());
}
std::string builtinStr("__builtin__"); std::string builtinStr("__builtin__");
extern "C" BoxedFunction::BoxedFunction(CLFunction* f) extern "C" BoxedFunction::BoxedFunction(CLFunction* f)
......
# run_args: -n
# statcheck: stats['slowpath_unboxedlen'] < 10
d = {} d = {}
for i in xrange(1000): for i in xrange(1000):
d[i] = i ** 2 d[i] = i ** 2
......
# expected: fail # Test the behavior of tuple unpacking in the face of exceptions being thrown at certain points.
# - we currently can't even compile this correctly # - If an exception gets thrown in the "unpack to a given size" part, none of the targets get set
# # - If setting a target throws an exception, then the previous targets had been set, but not the future ones
# The unpacking should be atomic: ie either all of the names get set or none of them do.
# We currently assume that unpacking can only throw an exception from the tuple being the
# wrong length, but instead we should be emulating the UNPACK_SEQUENCE bytecode.
class C(object): class C(object):
def __init__(self, n):
self.n = n
def __getitem__(self, i): def __getitem__(self, i):
print "__getitem__", i print "__getitem__", i
if i == 0: if i < self.n:
return 1 return i
raise IndexError raise IndexError
def __len__(self): def __len__(self):
print "len" print "len"
return 2 return 2
def f(): def f1():
print "f1"
try: try:
x, y = C() x, y = C(1)
except ValueError: except ValueError, e:
pass print e
try: try:
print x print x
...@@ -30,5 +31,76 @@ def f(): ...@@ -30,5 +31,76 @@ def f():
print y print y
except NameError, e: except NameError, e:
print e print e
f1()
def f2():
print "f2"
class D(object):
pass
d = D()
def f():
print "f"
return d
try:
f().x, f().y = C(1)
except ValueError, e:
print e
print hasattr(d, "x")
print hasattr(d, "y")
f2()
def f3():
print "f3"
class D(object):
pass
d = D()
def f(should_raise):
print "f", should_raise
if should_raise:
1/0
return d
try:
f(False).x, f(True).y, f(False).z = (1, 2, 3)
except ZeroDivisionError, e:
print e
print hasattr(d, "x")
print hasattr(d, "y")
print hasattr(d, "z")
f3()
f() def f4():
print "f4"
c = C(10000)
try:
x, y = c
except ValueError, e:
print e
f4()
def f5():
print "f5"
def f():
1/0
try:
x, f().x, y = (1, 2, 3)
except ZeroDivisionError, e:
print e
try:
print x
except NameError, e:
print e
try:
print y
except NameError, e:
print e
f5()
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