summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYoctopuce dev <dev@yoctopuce.com>2025-01-28 00:26:08 +0100
committerDamien George <damien@micropython.org>2025-08-01 13:35:44 +1000
commit69ead7d98ef30df3b6bd4485633490e80fca1718 (patch)
treee00552cc696fad38a03de442c99c289fb1f8a2e9
parentf67a3703118be7d97629130d99630996ff3cb255 (diff)
py/parse: Add support for math module constants and float folding.
Add a new MICROPY_COMP_CONST_FLOAT feature, enabled by in mpy-cross and when compiling with MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES. The new feature leverages the code of MICROPY_COMP_CONST_FOLDING to support folding of floating point constants. If MICROPY_COMP_MODULE_CONST is defined as well, math module constants are made available at compile time. For example: _DEG_TO_GRADIANT = const(math.pi / 180) _INVALID_VALUE = const(math.nan) A few corner cases had to be handled: - The float const folding code should not fold expressions resulting into complex results, as the mpy parser for complex immediates has limitations. - The constant generation code must distinguish between -0.0 and 0.0, which are different even if C consider them as ==. This change removes previous limitations on the use of `const()` expressions that would result in floating point number, so the test cases of micropython/const_error have to be updated. Additional test cases have been added to cover the new repr() code (from a previous commit). A few other simple test cases have been added to handle the use of floats in `const()` expressions, but the float folding code itself is also tested when running general float test cases, as float expressions often get resolved at compile-time (with this change). Signed-off-by: Yoctopuce dev <dev@yoctopuce.com>
-rw-r--r--mpy-cross/mpconfigport.h4
-rw-r--r--py/builtin.h1
-rw-r--r--py/emitcommon.c17
-rw-r--r--py/mpconfig.h7
-rw-r--r--py/parse.c89
-rw-r--r--tests/float/float_parse_doubleprec.py6
-rw-r--r--tests/micropython/const_error.py2
-rw-r--r--tests/micropython/const_error.py.exp2
-rw-r--r--tests/micropython/const_float.py23
-rw-r--r--tests/micropython/const_float.py.exp4
-rw-r--r--tests/micropython/const_math.py18
-rw-r--r--tests/micropython/const_math.py.exp1
12 files changed, 143 insertions, 31 deletions
diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h
index 94a598c99..81cbfc2ee 100644
--- a/mpy-cross/mpconfigport.h
+++ b/mpy-cross/mpconfigport.h
@@ -55,6 +55,7 @@
#define MICROPY_COMP_CONST_FOLDING (1)
#define MICROPY_COMP_MODULE_CONST (1)
#define MICROPY_COMP_CONST (1)
+#define MICROPY_COMP_CONST_FLOAT (1)
#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (1)
#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (1)
#define MICROPY_COMP_RETURN_IF_EXPR (1)
@@ -88,7 +89,8 @@
#define MICROPY_PY_ARRAY (0)
#define MICROPY_PY_ATTRTUPLE (0)
#define MICROPY_PY_COLLECTIONS (0)
-#define MICROPY_PY_MATH (0)
+#define MICROPY_PY_MATH (MICROPY_COMP_CONST_FLOAT)
+#define MICROPY_PY_MATH_CONSTANTS (MICROPY_COMP_CONST_FLOAT)
#define MICROPY_PY_CMATH (0)
#define MICROPY_PY_GC (0)
#define MICROPY_PY_IO (0)
diff --git a/py/builtin.h b/py/builtin.h
index 6efe3e8fa..388bc8470 100644
--- a/py/builtin.h
+++ b/py/builtin.h
@@ -138,6 +138,7 @@ extern const mp_obj_module_t mp_module_sys;
extern const mp_obj_module_t mp_module_errno;
extern const mp_obj_module_t mp_module_uctypes;
extern const mp_obj_module_t mp_module_machine;
+extern const mp_obj_module_t mp_module_math;
extern const char MICROPY_PY_BUILTINS_HELP_TEXT[];
diff --git a/py/emitcommon.c b/py/emitcommon.c
index a9eb6e202..1f701db80 100644
--- a/py/emitcommon.c
+++ b/py/emitcommon.c
@@ -25,6 +25,7 @@
*/
#include <assert.h>
+#include <math.h>
#include "py/emit.h"
#include "py/nativeglue.h"
@@ -72,7 +73,21 @@ static bool strictly_equal(mp_obj_t a, mp_obj_t b) {
}
return true;
} else {
- return mp_obj_equal(a, b);
+ if (!mp_obj_equal(a, b)) {
+ return false;
+ }
+ #if MICROPY_PY_BUILTINS_FLOAT && MICROPY_COMP_CONST_FLOAT
+ if (a_type == &mp_type_float) {
+ mp_float_t a_val = mp_obj_float_get(a);
+ if (a_val == (mp_float_t)0.0) {
+ // Although 0.0 == -0.0, they are not strictly_equal and
+ // must be stored as two different constants in .mpy files
+ mp_float_t b_val = mp_obj_float_get(b);
+ return signbit(a_val) == signbit(b_val);
+ }
+ }
+ #endif
+ return true;
}
}
diff --git a/py/mpconfig.h b/py/mpconfig.h
index caa63fef3..ed5ccccb6 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -490,6 +490,13 @@
#define MICROPY_COMP_CONST (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES)
#endif
+// Whether to enable float constant folding like 1.2+3.4 (when MICROPY_COMP_CONST_FOLDING is also enabled)
+// and constant optimisation like id = const(1.2) (when MICROPY_COMP_CONST is also enabled)
+// and constant lookup like math.inf (when MICROPY_COMP_MODULE_CONST is also enabled)
+#ifndef MICROPY_COMP_CONST_FLOAT
+#define MICROPY_COMP_CONST_FLOAT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES)
+#endif
+
// Whether to enable optimisation of: a, b = c, d
// Costs 124 bytes (Thumb2)
#ifndef MICROPY_COMP_DOUBLE_TUPLE_ASSIGN
diff --git a/py/parse.c b/py/parse.c
index db89fb584..91eea3e36 100644
--- a/py/parse.c
+++ b/py/parse.c
@@ -336,18 +336,34 @@ static uint8_t peek_rule(parser_t *parser, size_t n) {
}
#endif
-bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o) {
+#if MICROPY_COMP_CONST_FOLDING || MICROPY_EMIT_INLINE_ASM
+static bool mp_parse_node_get_number_maybe(mp_parse_node_t pn, mp_obj_t *o) {
if (MP_PARSE_NODE_IS_SMALL_INT(pn)) {
*o = MP_OBJ_NEW_SMALL_INT(MP_PARSE_NODE_LEAF_SMALL_INT(pn));
return true;
} else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, RULE_const_object)) {
mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn;
*o = mp_parse_node_extract_const_object(pns);
- return mp_obj_is_int(*o);
+ return mp_obj_is_int(*o)
+ #if MICROPY_COMP_CONST_FLOAT
+ || mp_obj_is_float(*o)
+ #endif
+ ;
} else {
return false;
}
}
+#endif
+
+#if MICROPY_EMIT_INLINE_ASM
+bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o) {
+ return mp_parse_node_get_number_maybe(pn, o)
+ #if MICROPY_COMP_CONST_FLOAT
+ && mp_obj_is_int(*o)
+ #endif
+ ;
+}
+#endif
#if MICROPY_COMP_CONST_TUPLE || MICROPY_COMP_CONST
static bool mp_parse_node_is_const(mp_parse_node_t pn) {
@@ -642,12 +658,32 @@ static const mp_rom_map_elem_t mp_constants_table[] = {
#if MICROPY_PY_UCTYPES
{ MP_ROM_QSTR(MP_QSTR_uctypes), MP_ROM_PTR(&mp_module_uctypes) },
#endif
+ #if MICROPY_PY_BUILTINS_FLOAT && MICROPY_PY_MATH && MICROPY_COMP_CONST_FLOAT
+ { MP_ROM_QSTR(MP_QSTR_math), MP_ROM_PTR(&mp_module_math) },
+ #endif
// Extra constants as defined by a port
MICROPY_PORT_CONSTANTS
};
static MP_DEFINE_CONST_MAP(mp_constants_map, mp_constants_table);
#endif
+static bool binary_op_maybe(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs, mp_obj_t *res) {
+ nlr_buf_t nlr;
+ if (nlr_push(&nlr) == 0) {
+ mp_obj_t tmp = mp_binary_op(op, lhs, rhs);
+ #if MICROPY_PY_BUILTINS_COMPLEX
+ if (mp_obj_is_type(tmp, &mp_type_complex)) {
+ return false;
+ }
+ #endif
+ *res = tmp;
+ nlr_pop();
+ return true;
+ } else {
+ return false;
+ }
+}
+
static bool fold_logical_constants(parser_t *parser, uint8_t rule_id, size_t *num_args) {
if (rule_id == RULE_or_test
|| rule_id == RULE_and_test) {
@@ -706,7 +742,7 @@ static bool fold_logical_constants(parser_t *parser, uint8_t rule_id, size_t *nu
}
static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
- // this code does folding of arbitrary integer expressions, eg 1 + 2 * 3 + 4
+ // this code does folding of arbitrary numeric expressions, eg 1 + 2 * 3 + 4
// it does not do partial folding, eg 1 + 2 + x -> 3 + x
mp_obj_t arg0;
@@ -716,7 +752,7 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
|| rule_id == RULE_power) {
// folding for binary ops: | ^ & **
mp_parse_node_t pn = peek_result(parser, num_args - 1);
- if (!mp_parse_node_get_int_maybe(pn, &arg0)) {
+ if (!mp_parse_node_get_number_maybe(pn, &arg0)) {
return false;
}
mp_binary_op_t op;
@@ -732,58 +768,61 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
for (ssize_t i = num_args - 2; i >= 0; --i) {
pn = peek_result(parser, i);
mp_obj_t arg1;
- if (!mp_parse_node_get_int_maybe(pn, &arg1)) {
+ if (!mp_parse_node_get_number_maybe(pn, &arg1)) {
return false;
}
+ #if !MICROPY_COMP_CONST_FLOAT
if (op == MP_BINARY_OP_POWER && mp_obj_int_sign(arg1) < 0) {
// ** can't have negative rhs
return false;
}
- arg0 = mp_binary_op(op, arg0, arg1);
+ #endif
+ if (!binary_op_maybe(op, arg0, arg1, &arg0)) {
+ return false;
+ }
}
} else if (rule_id == RULE_shift_expr
|| rule_id == RULE_arith_expr
|| rule_id == RULE_term) {
// folding for binary ops: << >> + - * @ / % //
mp_parse_node_t pn = peek_result(parser, num_args - 1);
- if (!mp_parse_node_get_int_maybe(pn, &arg0)) {
+ if (!mp_parse_node_get_number_maybe(pn, &arg0)) {
return false;
}
for (ssize_t i = num_args - 2; i >= 1; i -= 2) {
pn = peek_result(parser, i - 1);
mp_obj_t arg1;
- if (!mp_parse_node_get_int_maybe(pn, &arg1)) {
+ if (!mp_parse_node_get_number_maybe(pn, &arg1)) {
return false;
}
mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(peek_result(parser, i));
- if (tok == MP_TOKEN_OP_AT || tok == MP_TOKEN_OP_SLASH) {
- // Can't fold @ or /
+ if (tok == MP_TOKEN_OP_AT) {
+ // Can't fold @
+ return false;
+ }
+ #if !MICROPY_COMP_CONST_FLOAT
+ if (tok == MP_TOKEN_OP_SLASH) {
+ // Can't fold /
return false;
}
+ #endif
mp_binary_op_t op = MP_BINARY_OP_LSHIFT + (tok - MP_TOKEN_OP_DBL_LESS);
- int rhs_sign = mp_obj_int_sign(arg1);
- if (op <= MP_BINARY_OP_RSHIFT) {
- // << and >> can't have negative rhs
- if (rhs_sign < 0) {
- return false;
- }
- } else if (op >= MP_BINARY_OP_FLOOR_DIVIDE) {
- // % and // can't have zero rhs
- if (rhs_sign == 0) {
- return false;
- }
+ if (!binary_op_maybe(op, arg0, arg1, &arg0)) {
+ return false;
}
- arg0 = mp_binary_op(op, arg0, arg1);
}
} else if (rule_id == RULE_factor_2) {
// folding for unary ops: + - ~
mp_parse_node_t pn = peek_result(parser, 0);
- if (!mp_parse_node_get_int_maybe(pn, &arg0)) {
+ if (!mp_parse_node_get_number_maybe(pn, &arg0)) {
return false;
}
mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(peek_result(parser, 1));
mp_unary_op_t op;
if (tok == MP_TOKEN_OP_TILDE) {
+ if (!mp_obj_is_int(arg0)) {
+ return false;
+ }
op = MP_UNARY_OP_INVERT;
} else {
assert(tok == MP_TOKEN_OP_PLUS || tok == MP_TOKEN_OP_MINUS); // should be
@@ -855,7 +894,7 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
return false;
}
// id1.id2
- // look it up in constant table, see if it can be replaced with an integer
+ // look it up in constant table, see if it can be replaced with an integer or a float
mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pn1;
assert(MP_PARSE_NODE_IS_ID(pns1->nodes[0]));
qstr q_base = MP_PARSE_NODE_LEAF_ARG(pn0);
@@ -866,7 +905,7 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
}
mp_obj_t dest[2];
mp_load_method_maybe(elem->value, q_attr, dest);
- if (!(dest[0] != MP_OBJ_NULL && mp_obj_is_int(dest[0]) && dest[1] == MP_OBJ_NULL)) {
+ if (!(dest[0] != MP_OBJ_NULL && (mp_obj_is_int(dest[0]) || mp_obj_is_float(dest[0])) && dest[1] == MP_OBJ_NULL)) {
return false;
}
arg0 = dest[0];
diff --git a/tests/float/float_parse_doubleprec.py b/tests/float/float_parse_doubleprec.py
index 81fcadcee..c1b0b4823 100644
--- a/tests/float/float_parse_doubleprec.py
+++ b/tests/float/float_parse_doubleprec.py
@@ -19,3 +19,9 @@ print("%.14e" % float("." + "0" * 400 + "9e400"))
print(float("1.00000000000000000000e-307"))
print(float("10.0000000000000000000e-308"))
print(float("100.000000000000000000e-309"))
+
+# ensure repr() adds an extra digit when needed for accurate parsing
+print(float(repr(float("2.0") ** 100)) == float("2.0") ** 100)
+
+# ensure repr does not add meaningless extra digits (1.234999999999)
+print(repr(1.2345))
diff --git a/tests/micropython/const_error.py b/tests/micropython/const_error.py
index d35be530a..950360e4d 100644
--- a/tests/micropython/const_error.py
+++ b/tests/micropython/const_error.py
@@ -18,8 +18,6 @@ test_syntax("A = const(1); A = const(2)")
# these operations are not supported within const
test_syntax("A = const(1 @ 2)")
-test_syntax("A = const(1 / 2)")
-test_syntax("A = const(1 ** -2)")
test_syntax("A = const(1 << -2)")
test_syntax("A = const(1 >> -2)")
test_syntax("A = const(1 % 0)")
diff --git a/tests/micropython/const_error.py.exp b/tests/micropython/const_error.py.exp
index 3edc3efe9..bef69eb32 100644
--- a/tests/micropython/const_error.py.exp
+++ b/tests/micropython/const_error.py.exp
@@ -5,5 +5,3 @@ SyntaxError
SyntaxError
SyntaxError
SyntaxError
-SyntaxError
-SyntaxError
diff --git a/tests/micropython/const_float.py b/tests/micropython/const_float.py
new file mode 100644
index 000000000..c3a0df027
--- /dev/null
+++ b/tests/micropython/const_float.py
@@ -0,0 +1,23 @@
+# test constant optimisation, with consts that are floats
+
+from micropython import const
+
+# check we can make consts from floats
+F1 = const(2.5)
+F2 = const(-0.3)
+print(type(F1), F1)
+print(type(F2), F2)
+
+# check arithmetic with floats
+F3 = const(F1 + F2)
+F4 = const(F1**2)
+print(F3, F4)
+
+# check int operations with float results
+F5 = const(1 / 2)
+F6 = const(2**-2)
+print(F5, F6)
+
+# note: we also test float expression folding when
+# we're compiling test cases in tests/float, as
+# many expressions are resolved at compile time.
diff --git a/tests/micropython/const_float.py.exp b/tests/micropython/const_float.py.exp
new file mode 100644
index 000000000..17a86a6d9
--- /dev/null
+++ b/tests/micropython/const_float.py.exp
@@ -0,0 +1,4 @@
+<class 'float'> 2.5
+<class 'float'> -0.3
+2.2 6.25
+0.5 0.25
diff --git a/tests/micropython/const_math.py b/tests/micropython/const_math.py
new file mode 100644
index 000000000..7ee5edc6d
--- /dev/null
+++ b/tests/micropython/const_math.py
@@ -0,0 +1,18 @@
+# Test expressions based on math module constants
+try:
+ import math
+except ImportError:
+ print("SKIP")
+ raise SystemExit
+
+from micropython import const
+
+# check that we can make consts from math constants
+# (skip if the target has MICROPY_COMP_MODULE_CONST disabled)
+try:
+ exec("two_pi = const(2.0 * math.pi)")
+except SyntaxError:
+ print("SKIP")
+ raise SystemExit
+
+print(math.cos(two_pi))
diff --git a/tests/micropython/const_math.py.exp b/tests/micropython/const_math.py.exp
new file mode 100644
index 000000000..d3827e75a
--- /dev/null
+++ b/tests/micropython/const_math.py.exp
@@ -0,0 +1 @@
+1.0