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:
parent
f67a370311
commit
69ead7d98e
12 changed files with 144 additions and 32 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
91
py/parse.c
91
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;
|
||||
}
|
||||
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];
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
|
|
|
|||
|
|
@ -5,5 +5,3 @@ SyntaxError
|
|||
SyntaxError
|
||||
SyntaxError
|
||||
SyntaxError
|
||||
SyntaxError
|
||||
SyntaxError
|
||||
|
|
|
|||
23
tests/micropython/const_float.py
Normal file
23
tests/micropython/const_float.py
Normal 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.
|
||||
4
tests/micropython/const_float.py.exp
Normal file
4
tests/micropython/const_float.py.exp
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<class 'float'> 2.5
|
||||
<class 'float'> -0.3
|
||||
2.2 6.25
|
||||
0.5 0.25
|
||||
18
tests/micropython/const_math.py
Normal file
18
tests/micropython/const_math.py
Normal 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))
|
||||
1
tests/micropython/const_math.py.exp
Normal file
1
tests/micropython/const_math.py.exp
Normal file
|
|
@ -0,0 +1 @@
|
|||
1.0
|
||||
Loading…
Reference in a new issue