From a96d3d0840357e6015119c427e7052fd73d9be3a Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Mon, 31 Mar 2014 01:10:10 +0300 Subject: [PATCH 1/6] objexcept: No more magic messages in exceptions, only exception arguments. One of the reason for separate "message" (besides still unfulfilled desire to optimize memory usage) was apparent special handling of exception with messages by CPython. Well, the message is still just an exception argument, it just printed specially. Implement that with PRINT_EXC printing format. --- py/obj.c | 2 +- py/obj.h | 4 +++- py/objexcept.c | 56 ++++++++++++++++++++++---------------------------- 3 files changed, 29 insertions(+), 33 deletions(-) diff --git a/py/obj.c b/py/obj.c index f80d1a9772..95052d16d2 100644 --- a/py/obj.c +++ b/py/obj.c @@ -62,7 +62,7 @@ void mp_obj_print_exception(mp_obj_t exc) { } } } - mp_obj_print(exc, PRINT_REPR); + mp_obj_print(exc, PRINT_EXC); printf("\n"); } diff --git a/py/obj.h b/py/obj.h index a34d5407af..952187e464 100644 --- a/py/obj.h +++ b/py/obj.h @@ -141,7 +141,9 @@ typedef mp_obj_t (*mp_fun_var_t)(uint n, const mp_obj_t *); typedef mp_obj_t (*mp_fun_kw_t)(uint n, const mp_obj_t *, mp_map_t *); typedef enum { - PRINT_STR, PRINT_REPR + PRINT_STR, + PRINT_REPR, + PRINT_EXC, // Special format for printing exception in unhandled exception message } mp_print_kind_t; typedef void (*mp_print_fun_t)(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o, mp_print_kind_t kind); diff --git a/py/objexcept.c b/py/objexcept.c index 7dd5c7ac39..0efc21d360 100644 --- a/py/objexcept.c +++ b/py/objexcept.c @@ -11,42 +11,34 @@ #include "runtime.h" #include "runtime0.h" -// This is unified class for C-level and Python-level exceptions -// Python-level exceptions have empty ->msg and all arguments are in -// args tuple. C-level exceptions likely have ->msg set, and args is empty. typedef struct _mp_obj_exception_t { mp_obj_base_t base; mp_obj_t traceback; // a list object, holding (file,line,block) as numbers (not Python objects); a hack for now - vstr_t *msg; mp_obj_tuple_t args; } mp_obj_exception_t; // Instance of GeneratorExit exception - needed by generator.close() // This would belong to objgenerator.c, but to keep mp_obj_exception_t // definition module-private so far, have it here. -const mp_obj_exception_t mp_const_GeneratorExit_obj = {{&mp_type_GeneratorExit}, MP_OBJ_NULL, NULL, {{&mp_type_tuple}, 0}}; +const mp_obj_exception_t mp_const_GeneratorExit_obj = {{&mp_type_GeneratorExit}, MP_OBJ_NULL, {{&mp_type_tuple}, 0}}; STATIC void mp_obj_exception_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) { mp_obj_exception_t *o = o_in; - if (o->msg != NULL) { - print(env, "%s: %s", qstr_str(o->base.type->name), vstr_str(o->msg)); - } else { - // Yes, that's how CPython has it - // TODO now that exceptions are classes and instances, I think this needs to be changed to match CPython - if (kind == PRINT_REPR) { - print(env, "%s", qstr_str(o->base.type->name)); - } - if (kind == PRINT_STR) { - if (o->args.len == 0) { - print(env, ""); - return; - } else if (o->args.len == 1) { - mp_obj_print_helper(print, env, o->args.items[0], PRINT_STR); - return; - } - } - tuple_print(print, env, &o->args, kind); + if (kind == PRINT_REPR) { + print(env, "%s", qstr_str(o->base.type->name)); + } else if (kind == PRINT_EXC) { + print(env, "%s: ", qstr_str(o->base.type->name)); } + if (kind == PRINT_STR || kind == PRINT_EXC) { + if (o->args.len == 0) { + print(env, ""); + return; + } else if (o->args.len == 1) { + mp_obj_print_helper(print, env, o->args.items[0], PRINT_STR); + return; + } + } + tuple_print(print, env, &o->args, kind); } STATIC mp_obj_t mp_obj_exception_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) { @@ -59,7 +51,6 @@ STATIC mp_obj_t mp_obj_exception_make_new(mp_obj_t type_in, uint n_args, uint n_ mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t, n_args); o->base.type = type; o->traceback = MP_OBJ_NULL; - o->msg = NULL; o->args.base.type = &mp_type_tuple; o->args.len = n_args; memcpy(o->args.items, args, n_args * sizeof(mp_obj_t)); @@ -185,7 +176,7 @@ MP_DEFINE_EXCEPTION(Exception, BaseException) */ mp_obj_t mp_obj_new_exception(const mp_obj_type_t *exc_type) { - return mp_obj_new_exception_msg_varg(exc_type, NULL); + return mp_obj_new_exception_args(exc_type, 0, NULL); } mp_obj_t mp_obj_new_exception_args(const mp_obj_type_t *exc_type, uint n_args, const mp_obj_t *args) { @@ -202,22 +193,25 @@ mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char assert(exc_type->make_new == mp_obj_exception_make_new); // make exception object - mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t, 0); + mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t, 1); o->base.type = exc_type; o->traceback = MP_OBJ_NULL; o->args.base.type = &mp_type_tuple; - o->args.len = 0; + o->args.len = 1; if (fmt == NULL) { // no message - o->msg = NULL; + assert(0); } else { - // render exception message - o->msg = vstr_new(); + // render exception message and store as .args[0] + // TODO: optimize bufferbloat + vstr_t *vstr = vstr_new(); va_list ap; va_start(ap, fmt); - vstr_vprintf(o->msg, fmt, ap); + vstr_vprintf(vstr, fmt, ap); va_end(ap); + o->args.items[0] = mp_obj_new_str((byte*)vstr->buf, vstr->len, false); + vstr_free(vstr); } return o; From a8e60c1fdee248bda55e9e6e718e05d78c93e115 Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Mon, 31 Mar 2014 01:38:25 +0300 Subject: [PATCH 2/6] objfloat: Missing default: caused incorrect results for unimplemented ops. --- py/objfloat.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/py/objfloat.c b/py/objfloat.c index babc0c479b..5e4d05f172 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -119,7 +119,8 @@ mp_obj_t mp_obj_float_binary_op(int op, mp_float_t lhs_val, mp_obj_t rhs_in) { case MP_BINARY_OP_LESS_EQUAL: return MP_BOOL(lhs_val <= rhs_val); case MP_BINARY_OP_MORE_EQUAL: return MP_BOOL(lhs_val >= rhs_val); - return NULL; // op not supported + default: + return NULL; // op not supported } return mp_obj_new_float(lhs_val); } From 864038dab78afb01d7736faae0de58510d40e7c1 Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Mon, 31 Mar 2014 02:12:40 +0300 Subject: [PATCH 3/6] objfloat: Make sure that floats always have dot (for C "double" type case). This matches CPython behavior and hopefully can be treated as general Python semantics. --- py/objfloat.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/py/objfloat.c b/py/objfloat.c index 5e4d05f172..d8ac96c0b4 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -1,4 +1,6 @@ #include +#include +#include #include #include @@ -23,7 +25,13 @@ STATIC void float_print(void (*print)(void *env, const char *fmt, ...), void *en format_float(o->value, buf, sizeof(buf), 'g', 6, '\0'); print(env, "%s", buf); #else - print(env, "%.8g", (double) o->value); + char buf[32]; + sprintf(buf, "%.8g", (double) o->value); + print(env, buf); + if (strchr(buf, '.') == NULL) { + // Python floats always have decimal point + print(env, ".0"); + } #endif } From 96eec4f8a6721bc909415fd3c813757cc50eab7f Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Mon, 31 Mar 2014 02:16:25 +0300 Subject: [PATCH 4/6] compile: Don't try to constant-fold division by zero. The way it is, just crashes app. And optimizing to "raise ZeroDivisionError" is probably too much. --- py/compile.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/py/compile.c b/py/compile.c index c6c6752d16..89ca380aab 100644 --- a/py/compile.c +++ b/py/compile.c @@ -145,7 +145,9 @@ mp_parse_node_t fold_constants(mp_parse_node_t pn) { } else if (MP_PARSE_NODE_IS_TOKEN_KIND(pns->nodes[1], MP_TOKEN_OP_PERCENT)) { pn = mp_parse_node_new_leaf(MP_PARSE_NODE_SMALL_INT, python_modulo(arg0, arg1)); } else if (MP_PARSE_NODE_IS_TOKEN_KIND(pns->nodes[1], MP_TOKEN_OP_DBL_SLASH)) { - pn = mp_parse_node_new_leaf(MP_PARSE_NODE_SMALL_INT, python_floor_divide(arg0, arg1)); + if (arg1 != 0) { + pn = mp_parse_node_new_leaf(MP_PARSE_NODE_SMALL_INT, python_floor_divide(arg0, arg1)); + } } else { // shouldn't happen assert(0); From 96ed213320cb627c1df7ea8550b86b0bba5cb559 Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Mon, 31 Mar 2014 02:18:09 +0300 Subject: [PATCH 5/6] objfloat: Quick&dirty implementation of float floor division. TODO: Likely doesn't match Python semantics for negative numbers. --- py/objfloat.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/py/objfloat.c b/py/objfloat.c index d8ac96c0b4..3916c340b3 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -111,13 +111,15 @@ mp_obj_t mp_obj_float_binary_op(int op, mp_float_t lhs_val, mp_obj_t rhs_in) { case MP_BINARY_OP_INPLACE_SUBTRACT: lhs_val -= rhs_val; break; case MP_BINARY_OP_MULTIPLY: case MP_BINARY_OP_INPLACE_MULTIPLY: lhs_val *= rhs_val; break; - /* TODO floor(?) the value + // TODO: verify that C floor matches Python semantics case MP_BINARY_OP_FLOOR_DIVIDE: - case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: val = lhs_val / rhs_val; break; - */ + case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: + lhs_val = MICROPY_FLOAT_C_FUN(floor)(lhs_val / rhs_val); + goto check_zero_division; case MP_BINARY_OP_TRUE_DIVIDE: case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: lhs_val /= rhs_val; +check_zero_division: if (isinf(lhs_val)){ // check for division by zero nlr_jump(mp_obj_new_exception_msg(&mp_type_ZeroDivisionError, "float division by zero")); } From 6ded55a61f6bbf007d518fc4531287de08fe51c4 Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Mon, 31 Mar 2014 02:20:00 +0300 Subject: [PATCH 6/6] py: Properly implement divide-by-zero handling. "1/0" is sacred idiom, the shortest way to break program execution (sys.exit() is too long). --- py/runtime.c | 13 ++++++++++--- tests/basics/float1.py | 13 +++++++++++++ tests/basics/int-divzero.py | 9 +++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 tests/basics/int-divzero.py diff --git a/py/runtime.c b/py/runtime.c index 4898864962..1eca454806 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -348,13 +348,20 @@ mp_obj_t mp_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs) { } case MP_BINARY_OP_FLOOR_DIVIDE: case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: - { + if (rhs_val == 0) { + goto zero_division; + } lhs_val = python_floor_divide(lhs_val, rhs_val); break; - } + #if MICROPY_ENABLE_FLOAT case MP_BINARY_OP_TRUE_DIVIDE: - case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: return mp_obj_new_float((mp_float_t)lhs_val / (mp_float_t)rhs_val); + case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: + if (rhs_val == 0) { +zero_division: + nlr_jump(mp_obj_new_exception_msg(&mp_type_ZeroDivisionError, "division by zero")); + } + return mp_obj_new_float((mp_float_t)lhs_val / (mp_float_t)rhs_val); #endif case MP_BINARY_OP_MODULO: diff --git a/tests/basics/float1.py b/tests/basics/float1.py index 200d955856..bf1305c3d5 100644 --- a/tests/basics/float1.py +++ b/tests/basics/float1.py @@ -1,3 +1,16 @@ # basic float x = 1 / 2 print(x) + +print(1.0 // 2) +print(2.0 // 2) + +try: + 1.0 / 0 +except ZeroDivisionError: + print("ZeroDivisionError") + +try: + 1.0 // 0 +except ZeroDivisionError: + print("ZeroDivisionError") diff --git a/tests/basics/int-divzero.py b/tests/basics/int-divzero.py new file mode 100644 index 0000000000..d1fc579321 --- /dev/null +++ b/tests/basics/int-divzero.py @@ -0,0 +1,9 @@ +try: + 1 / 0 +except ZeroDivisionError: + print("ZeroDivisionError") + +try: + 1 // 0 +except ZeroDivisionError: + print("ZeroDivisionError")