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:
ConcreteCompilerVariable* rtn = new ConcreteCompilerVariable(DICT, v, true);
for (auto& p : symbol_table) {
if (p.first[0] == '!')
if (p.first[0] == '!' || p.first[0] == '#')
continue;
ConcreteCompilerVariable* is_defined_var = static_cast<ConcreteCompilerVariable*>(
......@@ -1260,14 +1260,33 @@ private:
void _doUnpackTuple(AST_Tuple* target, CompilerVariable* val, ExcInfo exc_info) {
assert(state != PARTIAL);
int ntargets = target->elts.size();
// TODO do type recording here?
ConcreteCompilerVariable* len = val->len(emitter, getEmptyOpInfo(exc_info));
emitter.createCall2(exc_info, g.funcs.checkUnpackingLength, getConstantInt(ntargets, g.i64), len->getValue());
// TODO can do faster unpacking of non-instantiated tuples; ie for something like
// 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++) {
CompilerVariable* unpacked = val->getitem(emitter, getEmptyOpInfo(exc_info), makeInt(i));
_doSet(target->elts[i], unpacked, exc_info);
unpacked->decvref(emitter);
llvm::Value* ptr = emitter.getBuilder()->CreateConstGEP1_32(unpacked, i);
llvm::Value* val = emitter.getBuilder()->CreateLoad(ptr);
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) {
GET(yield);
GET(getiter);
GET(checkUnpackingLength);
GET(unpackIntoArray);
GET(raiseAttributeError);
GET(raiseAttributeErrorStr);
GET(raiseNotIterableError);
......
......@@ -39,7 +39,7 @@ struct GlobalFuncs {
*unboxedLen, *getitem, *getclsattr, *getGlobal, *setitem, *unaryop, *import, *importFrom, *importStar, *repr,
*str, *isinstance, *yield, *getiter;
llvm::Value* checkUnpackingLength, *raiseAttributeError, *raiseAttributeErrorStr, *raiseNotIterableError,
llvm::Value* unpackIntoArray, *raiseAttributeError, *raiseAttributeErrorStr, *raiseNotIterableError,
*assertNameDefined, *assertFail;
llvm::Value* printFloat, *listAppendInternal;
llvm::Value* runtimeCall0, *runtimeCall1, *runtimeCall2, *runtimeCall3, *runtimeCall;
......
......@@ -914,6 +914,7 @@ public:
// but aren't directly *exactly* representable as normal Python.
// ClsAttribute would fall into this category, as would isinstance (which
// 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 {
public:
enum Opcodes {
......
This diff is collapsed.
......@@ -92,7 +92,7 @@ void force() {
FORCE(yield);
FORCE(getiter);
FORCE(checkUnpackingLength);
FORCE(unpackIntoArray);
FORCE(raiseAttributeError);
FORCE(raiseAttributeErrorStr);
FORCE(raiseNotIterableError);
......
......@@ -368,7 +368,7 @@ extern "C" void raiseNotIterableError(const char* 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)
return;
......@@ -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,
int instance_size, bool is_user_defined)
: 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) {
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
static void assertInitNone(Box* obj) {
if (obj != None) {
......
......@@ -78,7 +78,7 @@ extern "C" Box* unaryop(Box* operand, int op_type);
extern "C" Box* import(const std::string* name);
extern "C" Box* importFrom(Box* obj, const std::string* attr);
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 assertFail(BoxedModule* inModule, Box* msg);
extern "C" bool isSubclass(BoxedClass* child, BoxedClass* parent);
......
......@@ -81,17 +81,6 @@ void BoxIterator::gcHandler(GCVisitor* v) {
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__");
extern "C" BoxedFunction::BoxedFunction(CLFunction* f)
......
# run_args: -n
# statcheck: stats['slowpath_unboxedlen'] < 10
d = {}
for i in xrange(1000):
d[i] = i ** 2
......
# expected: fail
# - we currently can't even compile this correctly
#
# 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.
# Test the behavior of tuple unpacking in the face of exceptions being thrown at certain points.
# - 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
class C(object):
def __init__(self, n):
self.n = n
def __getitem__(self, i):
print "__getitem__", i
if i == 0:
return 1
if i < self.n:
return i
raise IndexError
def __len__(self):
print "len"
return 2
def f():
def f1():
print "f1"
try:
x, y = C()
except ValueError:
pass
x, y = C(1)
except ValueError, e:
print e
try:
print x
......@@ -30,5 +31,76 @@ def f():
print y
except NameError, 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