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>
This commit is contained in:
Yoctopuce dev 2025-01-28 00:26:08 +01:00 committed by Damien George
parent f67a370311
commit 69ead7d98e
12 changed files with 144 additions and 32 deletions

View file

@ -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)

View file

@ -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[];

View file

@ -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;
}
}

View file

@ -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

View file

@ -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;
}
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 !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);
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];

View file

@ -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))

View file

@ -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)")

View file

@ -5,5 +5,3 @@ SyntaxError
SyntaxError
SyntaxError
SyntaxError
SyntaxError
SyntaxError

View file

@ -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.

View file

@ -0,0 +1,4 @@
<class 'float'> 2.5
<class 'float'> -0.3
2.2 6.25
0.5 0.25

View file

@ -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))

View file

@ -0,0 +1 @@
1.0