Index: Python/optimize.c =================================================================== --- Python/optimize.c (revision 67048) +++ Python/optimize.c (working copy) @@ -147,6 +147,42 @@ return 1; } +/* Determines whether an expr represents a (possibly recursive) value + determinable at compile-time */ + +static int +_is_sequence_of_constants_rec(expr_ty expr) +{ + asdl_seq* seq = NULL; + PyObject* value; + switch (expr->kind) { + case Tuple_kind: + seq = expr->v.Tuple.elts; + break; + case List_kind: + seq = expr->v.List.elts; + break; + default: + value = _expr_constant_value(expr); + return value != NULL; + } + if (seq) { + int i; + int length = asdl_seq_LEN(seq); + + for (i = 0; i < length; i++) { + expr_ty expr; + + expr = (expr_ty)asdl_seq_GET(seq, i); + if (!_is_sequence_of_constants_rec(expr)) { + return 0; + } + } + } + return 1; +} + + /** * Build a tuple of constants from an expression sequence. * A precondition is that the given seq returns a true @@ -1225,10 +1261,190 @@ return 1; } + +static int _expr_len(expr_ty expr) { + switch (expr->kind) { + case Tuple_kind: + return asdl_seq_LEN(expr->v.Tuple.elts); + case Const_kind: + return 1; + default: + return 1; + } +} + +typedef struct _new_assign { + expr_ty target; + expr_ty value; + struct _new_assign* next; +} new_assign; + +new_assign* _optimize_assign_rec(optimizer* opt, asdl_seq* targets, expr_ty *value_ptr) { + expr_ty value = *value_ptr; + int i, j, k; + int len = _expr_len(asdl_seq_GET(targets, 0)); + int value_len = _expr_len(value); + int n_targets = asdl_seq_LEN(targets); + if (len == 1) { + if (value_len != 1) { + return NULL; + } + new_assign *result, *last_result = NULL; + for (i = n_targets - 1; i >= 0; --i) { + result = malloc(sizeof(new_assign)); + result->next = last_result; + result->value = value; + result->target = asdl_seq_GET(targets, i); + last_result = result; + } + return result; + } + if (value_len == 1) { + return NULL; + } + if (len != value_len) { + return NULL; + } + asdl_seq* child_targets = asdl_seq_new(n_targets, opt->opt_arena); + new_assign* results = NULL; + new_assign* end_of_results; + + for (j = 0; j < len; ++j) { + for (i = 0; i < n_targets; ++i) { + expr_ty target = asdl_seq_GET(targets, i); + if (target->kind != Tuple_kind) { + /* a, b = c = 1,2 */ + return NULL; + } + + if (j >= asdl_seq_LEN(target->v.Tuple.elts)) { + return NULL; + } + expr_ty child_target = asdl_seq_GET(target->v.Tuple.elts, j); + asdl_seq_SET(child_targets, i, child_target); + + } + expr_ty* child_value_ptr = (expr_ty*) &asdl_seq_GET(value->v.Tuple.elts, j); + new_assign* result = _optimize_assign_rec(opt, child_targets, + child_value_ptr); + + if (result) { + /*this element will be replaced by a separate assignment*/ + for (i = 0; i < n_targets; ++i) { + expr_ty target = (expr_ty) asdl_seq_GET(targets, i); + for (k = j; k < len - 1; ++k) { + asdl_seq_SET(target->v.Tuple.elts, k, asdl_seq_GET(target->v.Tuple.elts, k + 1)); + if (i == 0) { + asdl_seq_SET(value->v.Tuple.elts, k, asdl_seq_GET(value->v.Tuple.elts, k + 1)); + } + } + target->v.Tuple.elts->size -= 1; + } + value->v.Tuple.elts->size -= 1; + len -= 1; + j -= 1; + } + + if (!results) { + results = result; + end_of_results = results; + } else { + end_of_results->next = result; + } + if (end_of_results) { + while(end_of_results->next) { + end_of_results = end_of_results->next; + } + } + } + + + /*if all of these targets are tuples of one element, and the + value is a tuple of one element, then we can replace + the targets with their first element and the value with its. + */ + + if (value->kind == Tuple_kind && asdl_seq_LEN(value->v.Tuple.elts) == 1) { + int replaceable = 1; + for (i = 0; i < n_targets; ++i) { + expr_ty target = (expr_ty) asdl_seq_GET(targets, i); + if (target->kind != Tuple_kind || asdl_seq_LEN(target->v.Tuple.elts) != 1) { + replaceable = 0; + break; + } + } + + if (replaceable) { + for (i = 0; i < n_targets; ++i) { + expr_ty target = (expr_ty) asdl_seq_GET(targets, i); + asdl_seq_SET(targets, i, asdl_seq_GET(target->v.Tuple.elts, 0)); + } + *value_ptr = asdl_seq_GET(value->v.Tuple.elts, 0); + + } + } + return results; +} + static int optimize_assign(optimizer* opt, stmt_ty* stmt_ptr) { stmt_ty stmt = *stmt_ptr; + int i; + + /* Optimize a complex assignment like a, (b, c) = d, e = 1, (2, 3) to + a set of simpler assignments like: + a = 1 + d = 1 + b, c = e = 2, 3 + */ + int len = _expr_len(asdl_seq_GET(stmt->v.Assign.targets, 0)); + if (len > 1 && _is_sequence_of_constants_rec(stmt->v.Assign.value)) { + new_assign* result = _optimize_assign_rec(opt, stmt->v.Assign.targets, &stmt->v.Assign.value); + + if (result) { + int len = 0; + new_assign* r; + asdl_seq* new_assigns; + + for (r = result; r != NULL; r=r->next) + ++len; + + int direct = asdl_seq_LEN(((expr_ty)asdl_seq_GET(stmt->v.Assign.targets, 0))->v.Tuple.elts) == 0; + + if (direct) { + new_assigns = asdl_seq_new(len, opt->opt_arena); + } else { + new_assigns = asdl_seq_new(len + 1, opt->opt_arena); + } + + /* generate new assign statements */ + for (i = 0, r = result; r != NULL; ++i) { + asdl_seq* new_targets = asdl_seq_new(1, opt->opt_arena); + asdl_seq_SET(new_targets, 0, r->target); + stmt_ty assign = Assign(new_targets, r->value, + stmt->lineno, stmt->col_offset, + opt->opt_arena); + asdl_seq_SET(new_assigns, i, assign); + new_assign* next = r->next; + free(r); + r = next; + } + + if (direct) { + *stmt_ptr = Seq(new_assigns, stmt->lineno, stmt->col_offset, opt->opt_arena); + } else { + asdl_seq_SET(new_assigns, len, stmt); + *stmt_ptr = Seq(new_assigns, stmt->lineno, stmt->col_offset, opt->opt_arena); + } + stmt = *stmt_ptr; + for (i = 0; i < asdl_seq_LEN(stmt->v.Seq.body); ++i) { + optimize_assign(opt, (stmt_ty*) &asdl_seq_GET(stmt->v.Seq.body, i)); + } + return 1; + } + } + if (!optimize_expr_seq(opt, &stmt->v.Assign.targets)) return 0; if (!optimize_expr(opt, &stmt->v.Assign.value)) @@ -1557,6 +1773,10 @@ opt->opt_current->b_eliminate = 0; return 1; } + case Seq_kind: + { + return optimize_stmt_seq(opt, &stmt->v.Seq.body); + } default: PyErr_Format(PyExc_ValueError, "unknown stmt_ty kind: %d", stmt->kind); Index: Python/compile.c =================================================================== --- Python/compile.c (revision 67048) +++ Python/compile.c (working copy) @@ -2261,6 +2261,9 @@ return compiler_continue(c); case With_kind: return compiler_with(c, s); + case Seq_kind: + VISIT_SEQ(c, stmt, s->v.Seq.body); + break; } return 1; } @@ -3197,8 +3200,8 @@ return compiler_nameop(c, e->v.Name.id, Store); default: PyErr_Format(PyExc_SystemError, - "invalid node type (%d) for augmented assignment", - e->kind); + "invalid node type (%d) for augmented assignment at %d", + e->kind, e->lineno); return 0; } return 1; Index: Python/Python-ast.c =================================================================== --- Python/Python-ast.c (revision 67048) +++ Python/Python-ast.c (working copy) @@ -150,6 +150,10 @@ static PyTypeObject *Pass_type; static PyTypeObject *Break_type; static PyTypeObject *Continue_type; +static PyTypeObject *Seq_type; +static char *Seq_fields[]={ + "body", +}; static PyTypeObject *expr_type; static char *expr_attributes[] = { "lineno", @@ -689,6 +693,8 @@ if (!Break_type) return 0; Continue_type = make_type("Continue", stmt_type, NULL, 0); if (!Continue_type) return 0; + Seq_type = make_type("Seq", stmt_type, Seq_fields, 1); + if (!Seq_type) return 0; expr_type = make_type("expr", &AST_type, NULL, 0); if (!expr_type) return 0; if (!add_attributes(expr_type, expr_attributes, 2)) return 0; @@ -1429,6 +1435,20 @@ return p; } +stmt_ty +Seq(asdl_seq * body, int lineno, int col_offset, PyArena *arena) +{ + stmt_ty p; + p = (stmt_ty)PyArena_Malloc(arena, sizeof(*p)); + if (!p) + return NULL; + p->kind = Seq_kind; + p->v.Seq.body = body; + p->lineno = lineno; + p->col_offset = col_offset; + return p; +} + expr_ty BoolOp(boolop_ty op, asdl_seq * values, int lineno, int col_offset, PyArena *arena) @@ -2455,6 +2475,15 @@ result = PyType_GenericNew(Continue_type, NULL, NULL); if (!result) goto failed; break; + case Seq_kind: + result = PyType_GenericNew(Seq_type, NULL, NULL); + if (!result) goto failed; + value = ast2obj_list(o->v.Seq.body, ast2obj_stmt); + if (!value) goto failed; + if (PyObject_SetAttrString(result, "body", value) == -1) + goto failed; + Py_DECREF(value); + break; } value = ast2obj_int(o->lineno); if (!value) goto failed; @@ -4445,7 +4474,39 @@ if (*out == NULL) goto failed; return 0; } + if (PyObject_IsInstance(obj, (PyObject*)Seq_type)) { + asdl_seq* body; + if (PyObject_HasAttrString(obj, "body")) { + int res; + Py_ssize_t len; + Py_ssize_t i; + tmp = PyObject_GetAttrString(obj, "body"); + if (tmp == NULL) goto failed; + if (!PyList_Check(tmp)) { + PyErr_Format(PyExc_TypeError, "Seq field \"body\" must be a list, not a %.200s", tmp->ob_type->tp_name); + goto failed; + } + len = PyList_GET_SIZE(tmp); + body = asdl_seq_new(len, arena); + if (body == NULL) goto failed; + for (i = 0; i < len; i++) { + stmt_ty value; + res = obj2ast_stmt(PyList_GET_ITEM(tmp, i), &value, arena); + if (res != 0) goto failed; + asdl_seq_SET(body, i, value); + } + Py_XDECREF(tmp); + tmp = NULL; + } else { + PyErr_SetString(PyExc_TypeError, "required field \"body\" missing from Seq"); + return 1; + } + *out = Seq(body, lineno, col_offset, arena); + if (*out == NULL) goto failed; + return 0; + } + tmp = PyObject_Repr(obj); if (tmp == NULL) goto failed; PyErr_Format(PyExc_TypeError, "expected some sort of stmt, but got %.400s", PyString_AS_STRING(tmp)); @@ -6039,6 +6100,7 @@ if (PyDict_SetItemString(d, "Break", (PyObject*)Break_type) < 0) return; if (PyDict_SetItemString(d, "Continue", (PyObject*)Continue_type) < 0) return; + if (PyDict_SetItemString(d, "Seq", (PyObject*)Seq_type) < 0) return; if (PyDict_SetItemString(d, "expr", (PyObject*)expr_type) < 0) return; if (PyDict_SetItemString(d, "BoolOp", (PyObject*)BoolOp_type) < 0) return; Index: Include/Python-ast.h =================================================================== --- Include/Python-ast.h (revision 67048) +++ Include/Python-ast.h (working copy) @@ -65,7 +65,7 @@ Raise_kind=12, TryExcept_kind=13, TryFinally_kind=14, Assert_kind=15, Import_kind=16, ImportFrom_kind=17, Exec_kind=18, Global_kind=19, Expr_kind=20, Pass_kind=21, - Break_kind=22, Continue_kind=23}; + Break_kind=22, Continue_kind=23, Seq_kind=24}; struct _stmt { enum _stmt_kind kind; union { @@ -179,6 +179,10 @@ expr_ty value; } Expr; + struct { + asdl_seq *body; + } Seq; + } v; int lineno; int col_offset; @@ -435,6 +439,8 @@ stmt_ty _Py_Break(int lineno, int col_offset, PyArena *arena); #define Continue(a0, a1, a2) _Py_Continue(a0, a1, a2) stmt_ty _Py_Continue(int lineno, int col_offset, PyArena *arena); +#define Seq(a0, a1, a2, a3) _Py_Seq(a0, a1, a2, a3) +stmt_ty _Py_Seq(asdl_seq * body, int lineno, int col_offset, PyArena *arena); #define BoolOp(a0, a1, a2, a3, a4) _Py_BoolOp(a0, a1, a2, a3, a4) expr_ty _Py_BoolOp(boolop_ty op, asdl_seq * values, int lineno, int col_offset, PyArena *arena); Index: Parser/Python.asdl =================================================================== --- Parser/Python.asdl (revision 67048) +++ Parser/Python.asdl (working copy) @@ -44,7 +44,7 @@ | Global(identifier* names) | Expr(expr value) | Pass | Break | Continue - + | Seq(stmt* body) -- XXX Jython will be different -- col_offset is the byte offset in the utf8 string the parser uses attributes (int lineno, int col_offset) Index: Lib/test/test_multiassign.py =================================================================== --- Lib/test/test_multiassign.py (revision 0) +++ Lib/test/test_multiassign.py (revision 0) @@ -0,0 +1,62 @@ +#!/usr/bin/env python +""" +Test script for tuple assignment +Original by David Turner +""" + +import unittest +from test import test_support +from test_peepholer import disassemble +import sys + +def ma0(): + a, b = c, d = 1, 2 + +def ma0_opt(): + a = 1 + c = 1 + b = 2 + d = 2 + +def ma1(): + a, (b, c) = d, e = 1, (2, 3) + +def ma1_opt(): + a = 1 + d = 1 + b, c = e = 2, 3 + +def ma2(): + a = 5 + a, (b, c) = d, e = 1, (2, a) + return e + +def ma2_opt_bogus(): + a = 5 + a = 1 + d = 1 + b, c = e = 2, a + return e + +class OptimizeMultiassignTestCase(unittest.TestCase): + def test_multiassign(self): + if not sys.flags.optimize: + print "Warning: can't test optimization unless running under -O" + return + + self.assertEqual(ma0(), ma0_opt()) + self.assertEqual(ma1(), ma1_opt()) + self.assertEqual(ma0.func_code.co_code, ma0_opt.func_code.co_code) + self.assertEqual(ma1.func_code.co_code, ma1_opt.func_code.co_code) + self.assertNotEqual(ma2.func_code.co_code, ma2_opt_bogus.func_code.co_code) + self.assertNotEqual(ma2(), ma2_opt_bogus()) + + +def test_main(): + test_support.run_unittest( + OptimizeMultiassignTestCase + ) + + +if __name__ == "__main__": + test_main()