Skip to content

Commit 69ead7d

Browse files
yoctopucedpgeorge
authored andcommitted
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 <[email protected]>
1 parent f67a370 commit 69ead7d

File tree

12 files changed

+143
-31
lines changed

12 files changed

+143
-31
lines changed

mpy-cross/mpconfigport.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#define MICROPY_COMP_CONST_FOLDING (1)
5656
#define MICROPY_COMP_MODULE_CONST (1)
5757
#define MICROPY_COMP_CONST (1)
58+
#define MICROPY_COMP_CONST_FLOAT (1)
5859
#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (1)
5960
#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (1)
6061
#define MICROPY_COMP_RETURN_IF_EXPR (1)
@@ -88,7 +89,8 @@
8889
#define MICROPY_PY_ARRAY (0)
8990
#define MICROPY_PY_ATTRTUPLE (0)
9091
#define MICROPY_PY_COLLECTIONS (0)
91-
#define MICROPY_PY_MATH (0)
92+
#define MICROPY_PY_MATH (MICROPY_COMP_CONST_FLOAT)
93+
#define MICROPY_PY_MATH_CONSTANTS (MICROPY_COMP_CONST_FLOAT)
9294
#define MICROPY_PY_CMATH (0)
9395
#define MICROPY_PY_GC (0)
9496
#define MICROPY_PY_IO (0)

py/builtin.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ extern const mp_obj_module_t mp_module_sys;
138138
extern const mp_obj_module_t mp_module_errno;
139139
extern const mp_obj_module_t mp_module_uctypes;
140140
extern const mp_obj_module_t mp_module_machine;
141+
extern const mp_obj_module_t mp_module_math;
141142

142143
extern const char MICROPY_PY_BUILTINS_HELP_TEXT[];
143144

py/emitcommon.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
*/
2626

2727
#include <assert.h>
28+
#include <math.h>
2829

2930
#include "py/emit.h"
3031
#include "py/nativeglue.h"
@@ -72,7 +73,21 @@ static bool strictly_equal(mp_obj_t a, mp_obj_t b) {
7273
}
7374
return true;
7475
} else {
75-
return mp_obj_equal(a, b);
76+
if (!mp_obj_equal(a, b)) {
77+
return false;
78+
}
79+
#if MICROPY_PY_BUILTINS_FLOAT && MICROPY_COMP_CONST_FLOAT
80+
if (a_type == &mp_type_float) {
81+
mp_float_t a_val = mp_obj_float_get(a);
82+
if (a_val == (mp_float_t)0.0) {
83+
// Although 0.0 == -0.0, they are not strictly_equal and
84+
// must be stored as two different constants in .mpy files
85+
mp_float_t b_val = mp_obj_float_get(b);
86+
return signbit(a_val) == signbit(b_val);
87+
}
88+
}
89+
#endif
90+
return true;
7691
}
7792
}
7893

py/mpconfig.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,13 @@
490490
#define MICROPY_COMP_CONST (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES)
491491
#endif
492492

493+
// Whether to enable float constant folding like 1.2+3.4 (when MICROPY_COMP_CONST_FOLDING is also enabled)
494+
// and constant optimisation like id = const(1.2) (when MICROPY_COMP_CONST is also enabled)
495+
// and constant lookup like math.inf (when MICROPY_COMP_MODULE_CONST is also enabled)
496+
#ifndef MICROPY_COMP_CONST_FLOAT
497+
#define MICROPY_COMP_CONST_FLOAT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES)
498+
#endif
499+
493500
// Whether to enable optimisation of: a, b = c, d
494501
// Costs 124 bytes (Thumb2)
495502
#ifndef MICROPY_COMP_DOUBLE_TUPLE_ASSIGN

py/parse.c

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -336,18 +336,34 @@ static uint8_t peek_rule(parser_t *parser, size_t n) {
336336
}
337337
#endif
338338

339-
bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o) {
339+
#if MICROPY_COMP_CONST_FOLDING || MICROPY_EMIT_INLINE_ASM
340+
static bool mp_parse_node_get_number_maybe(mp_parse_node_t pn, mp_obj_t *o) {
340341
if (MP_PARSE_NODE_IS_SMALL_INT(pn)) {
341342
*o = MP_OBJ_NEW_SMALL_INT(MP_PARSE_NODE_LEAF_SMALL_INT(pn));
342343
return true;
343344
} else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, RULE_const_object)) {
344345
mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn;
345346
*o = mp_parse_node_extract_const_object(pns);
346-
return mp_obj_is_int(*o);
347+
return mp_obj_is_int(*o)
348+
#if MICROPY_COMP_CONST_FLOAT
349+
|| mp_obj_is_float(*o)
350+
#endif
351+
;
347352
} else {
348353
return false;
349354
}
350355
}
356+
#endif
357+
358+
#if MICROPY_EMIT_INLINE_ASM
359+
bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o) {
360+
return mp_parse_node_get_number_maybe(pn, o)
361+
#if MICROPY_COMP_CONST_FLOAT
362+
&& mp_obj_is_int(*o)
363+
#endif
364+
;
365+
}
366+
#endif
351367

352368
#if MICROPY_COMP_CONST_TUPLE || MICROPY_COMP_CONST
353369
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[] = {
642658
#if MICROPY_PY_UCTYPES
643659
{ MP_ROM_QSTR(MP_QSTR_uctypes), MP_ROM_PTR(&mp_module_uctypes) },
644660
#endif
661+
#if MICROPY_PY_BUILTINS_FLOAT && MICROPY_PY_MATH && MICROPY_COMP_CONST_FLOAT
662+
{ MP_ROM_QSTR(MP_QSTR_math), MP_ROM_PTR(&mp_module_math) },
663+
#endif
645664
// Extra constants as defined by a port
646665
MICROPY_PORT_CONSTANTS
647666
};
648667
static MP_DEFINE_CONST_MAP(mp_constants_map, mp_constants_table);
649668
#endif
650669

670+
static bool binary_op_maybe(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs, mp_obj_t *res) {
671+
nlr_buf_t nlr;
672+
if (nlr_push(&nlr) == 0) {
673+
mp_obj_t tmp = mp_binary_op(op, lhs, rhs);
674+
#if MICROPY_PY_BUILTINS_COMPLEX
675+
if (mp_obj_is_type(tmp, &mp_type_complex)) {
676+
return false;
677+
}
678+
#endif
679+
*res = tmp;
680+
nlr_pop();
681+
return true;
682+
} else {
683+
return false;
684+
}
685+
}
686+
651687
static bool fold_logical_constants(parser_t *parser, uint8_t rule_id, size_t *num_args) {
652688
if (rule_id == RULE_or_test
653689
|| rule_id == RULE_and_test) {
@@ -706,7 +742,7 @@ static bool fold_logical_constants(parser_t *parser, uint8_t rule_id, size_t *nu
706742
}
707743

708744
static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
709-
// this code does folding of arbitrary integer expressions, eg 1 + 2 * 3 + 4
745+
// this code does folding of arbitrary numeric expressions, eg 1 + 2 * 3 + 4
710746
// it does not do partial folding, eg 1 + 2 + x -> 3 + x
711747

712748
mp_obj_t arg0;
@@ -716,7 +752,7 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
716752
|| rule_id == RULE_power) {
717753
// folding for binary ops: | ^ & **
718754
mp_parse_node_t pn = peek_result(parser, num_args - 1);
719-
if (!mp_parse_node_get_int_maybe(pn, &arg0)) {
755+
if (!mp_parse_node_get_number_maybe(pn, &arg0)) {
720756
return false;
721757
}
722758
mp_binary_op_t op;
@@ -732,58 +768,61 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
732768
for (ssize_t i = num_args - 2; i >= 0; --i) {
733769
pn = peek_result(parser, i);
734770
mp_obj_t arg1;
735-
if (!mp_parse_node_get_int_maybe(pn, &arg1)) {
771+
if (!mp_parse_node_get_number_maybe(pn, &arg1)) {
736772
return false;
737773
}
774+
#if !MICROPY_COMP_CONST_FLOAT
738775
if (op == MP_BINARY_OP_POWER && mp_obj_int_sign(arg1) < 0) {
739776
// ** can't have negative rhs
740777
return false;
741778
}
742-
arg0 = mp_binary_op(op, arg0, arg1);
779+
#endif
780+
if (!binary_op_maybe(op, arg0, arg1, &arg0)) {
781+
return false;
782+
}
743783
}
744784
} else if (rule_id == RULE_shift_expr
745785
|| rule_id == RULE_arith_expr
746786
|| rule_id == RULE_term) {
747787
// folding for binary ops: << >> + - * @ / % //
748788
mp_parse_node_t pn = peek_result(parser, num_args - 1);
749-
if (!mp_parse_node_get_int_maybe(pn, &arg0)) {
789+
if (!mp_parse_node_get_number_maybe(pn, &arg0)) {
750790
return false;
751791
}
752792
for (ssize_t i = num_args - 2; i >= 1; i -= 2) {
753793
pn = peek_result(parser, i - 1);
754794
mp_obj_t arg1;
755-
if (!mp_parse_node_get_int_maybe(pn, &arg1)) {
795+
if (!mp_parse_node_get_number_maybe(pn, &arg1)) {
756796
return false;
757797
}
758798
mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(peek_result(parser, i));
759-
if (tok == MP_TOKEN_OP_AT || tok == MP_TOKEN_OP_SLASH) {
760-
// Can't fold @ or /
799+
if (tok == MP_TOKEN_OP_AT) {
800+
// Can't fold @
801+
return false;
802+
}
803+
#if !MICROPY_COMP_CONST_FLOAT
804+
if (tok == MP_TOKEN_OP_SLASH) {
805+
// Can't fold /
761806
return false;
762807
}
808+
#endif
763809
mp_binary_op_t op = MP_BINARY_OP_LSHIFT + (tok - MP_TOKEN_OP_DBL_LESS);
764-
int rhs_sign = mp_obj_int_sign(arg1);
765-
if (op <= MP_BINARY_OP_RSHIFT) {
766-
// << and >> can't have negative rhs
767-
if (rhs_sign < 0) {
768-
return false;
769-
}
770-
} else if (op >= MP_BINARY_OP_FLOOR_DIVIDE) {
771-
// % and // can't have zero rhs
772-
if (rhs_sign == 0) {
773-
return false;
774-
}
810+
if (!binary_op_maybe(op, arg0, arg1, &arg0)) {
811+
return false;
775812
}
776-
arg0 = mp_binary_op(op, arg0, arg1);
777813
}
778814
} else if (rule_id == RULE_factor_2) {
779815
// folding for unary ops: + - ~
780816
mp_parse_node_t pn = peek_result(parser, 0);
781-
if (!mp_parse_node_get_int_maybe(pn, &arg0)) {
817+
if (!mp_parse_node_get_number_maybe(pn, &arg0)) {
782818
return false;
783819
}
784820
mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(peek_result(parser, 1));
785821
mp_unary_op_t op;
786822
if (tok == MP_TOKEN_OP_TILDE) {
823+
if (!mp_obj_is_int(arg0)) {
824+
return false;
825+
}
787826
op = MP_UNARY_OP_INVERT;
788827
} else {
789828
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) {
855894
return false;
856895
}
857896
// id1.id2
858-
// look it up in constant table, see if it can be replaced with an integer
897+
// look it up in constant table, see if it can be replaced with an integer or a float
859898
mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pn1;
860899
assert(MP_PARSE_NODE_IS_ID(pns1->nodes[0]));
861900
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) {
866905
}
867906
mp_obj_t dest[2];
868907
mp_load_method_maybe(elem->value, q_attr, dest);
869-
if (!(dest[0] != MP_OBJ_NULL && mp_obj_is_int(dest[0]) && dest[1] == MP_OBJ_NULL)) {
908+
if (!(dest[0] != MP_OBJ_NULL && (mp_obj_is_int(dest[0]) || mp_obj_is_float(dest[0])) && dest[1] == MP_OBJ_NULL)) {
870909
return false;
871910
}
872911
arg0 = dest[0];

tests/float/float_parse_doubleprec.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@
1919
print(float("1.00000000000000000000e-307"))
2020
print(float("10.0000000000000000000e-308"))
2121
print(float("100.000000000000000000e-309"))
22+
23+
# ensure repr() adds an extra digit when needed for accurate parsing
24+
print(float(repr(float("2.0") ** 100)) == float("2.0") ** 100)
25+
26+
# ensure repr does not add meaningless extra digits (1.234999999999)
27+
print(repr(1.2345))

tests/micropython/const_error.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ def test_syntax(code):
1818

1919
# these operations are not supported within const
2020
test_syntax("A = const(1 @ 2)")
21-
test_syntax("A = const(1 / 2)")
22-
test_syntax("A = const(1 ** -2)")
2321
test_syntax("A = const(1 << -2)")
2422
test_syntax("A = const(1 >> -2)")
2523
test_syntax("A = const(1 % 0)")

tests/micropython/const_error.py.exp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,3 @@ SyntaxError
55
SyntaxError
66
SyntaxError
77
SyntaxError
8-
SyntaxError
9-
SyntaxError

tests/micropython/const_float.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# test constant optimisation, with consts that are floats
2+
3+
from micropython import const
4+
5+
# check we can make consts from floats
6+
F1 = const(2.5)
7+
F2 = const(-0.3)
8+
print(type(F1), F1)
9+
print(type(F2), F2)
10+
11+
# check arithmetic with floats
12+
F3 = const(F1 + F2)
13+
F4 = const(F1**2)
14+
print(F3, F4)
15+
16+
# check int operations with float results
17+
F5 = const(1 / 2)
18+
F6 = const(2**-2)
19+
print(F5, F6)
20+
21+
# note: we also test float expression folding when
22+
# we're compiling test cases in tests/float, as
23+
# many expressions are resolved at compile time.

tests/micropython/const_float.py.exp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<class 'float'> 2.5
2+
<class 'float'> -0.3
3+
2.2 6.25
4+
0.5 0.25

0 commit comments

Comments
 (0)