Skip to content

Commit 860cc5c

Browse files
committed
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 == To avoid loosing precision when parsing floats from .mpy file, the repr() string version of floats has been improved to include an extra digit, only when actually needed. This new version of float repr() actually appears to better mimic CPython. The new repr() code is however only enabled when MICROPY_COMP_MODULE_CONST is set, to avoid increasing code footprint. 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. 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. Signed-off-by: Yoctopuce dev <dev@yoctopuce.com>
1 parent f5d10c3 commit 860cc5c

File tree

13 files changed

+154
-31
lines changed

13 files changed

+154
-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
@@ -485,6 +485,13 @@
485485
#define MICROPY_COMP_CONST (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES)
486486
#endif
487487

488+
// Whether to enable float constant folding like 1.2+3.4 (when MICROPY_COMP_CONST_FOLDING is also enabled)
489+
// and constant optimisation like id = const(1.2) (when MICROPY_COMP_CONST is also enabled)
490+
// and constant lookup like math.inf (when MICROPY_COMP_MODULE_CONST is also enabled)
491+
#ifndef MICROPY_COMP_CONST_FLOAT
492+
#define MICROPY_COMP_CONST_FLOAT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES)
493+
#endif
494+
488495
// Whether to enable optimisation of: a, b = c, d
489496
// Costs 124 bytes (Thumb2)
490497
#ifndef MICROPY_COMP_DOUBLE_TUPLE_ASSIGN

py/objfloat.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,20 @@ static void float_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t
124124
const int precision = 16;
125125
#endif
126126
mp_format_float(o_val, buf, sizeof(buf), 'g', precision, '\0');
127+
#if MICROPY_COMP_CONST_FLOAT
128+
if (kind == PRINT_REPR) {
129+
// See if we can add an extra (partial) digit for precision.
130+
// This is useful in particular for saving floats to .mpy files,
131+
// but also for general `repr` support.
132+
// If the length increases by more than one digit, it means we are
133+
// digging too far (eg. 1.234499999999), so we keep the short form
134+
char ebuf[32];
135+
mp_format_float(o_val, ebuf, sizeof(ebuf), 'g', precision + 1, '\0');
136+
if (strlen(ebuf) == strlen(buf) + 1) {
137+
memcpy(buf, ebuf, sizeof(buf));
138+
}
139+
}
140+
#endif
127141
mp_print_str(print, buf);
128142
if (strchr(buf, '.') == NULL && strchr(buf, 'e') == NULL && strchr(buf, 'n') == NULL) {
129143
// Python floats always have decimal point (unless inf or nan)

py/parse.c

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -336,19 +336,33 @@ 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+
static bool mp_parse_node_get_number_maybe(mp_parse_node_t pn, mp_obj_t *o) {
340340
if (MP_PARSE_NODE_IS_SMALL_INT(pn)) {
341341
*o = MP_OBJ_NEW_SMALL_INT(MP_PARSE_NODE_LEAF_SMALL_INT(pn));
342342
return true;
343343
} else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, RULE_const_object)) {
344344
mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn;
345345
*o = mp_parse_node_extract_const_object(pns);
346-
return mp_obj_is_int(*o);
346+
return mp_obj_is_int(*o)
347+
#if MICROPY_COMP_CONST_FLOAT
348+
|| mp_obj_is_float(*o)
349+
#endif
350+
;
347351
} else {
348352
return false;
349353
}
350354
}
351355

356+
#if MICROPY_EMIT_INLINE_ASM
357+
bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o) {
358+
return mp_parse_node_get_number_maybe(pn, o)
359+
#if MICROPY_COMP_CONST_FLOAT
360+
&& mp_obj_is_int(*o)
361+
#endif
362+
;
363+
}
364+
#endif
365+
352366
#if MICROPY_COMP_CONST_TUPLE || MICROPY_COMP_CONST
353367
static bool mp_parse_node_is_const(mp_parse_node_t pn) {
354368
if (MP_PARSE_NODE_IS_SMALL_INT(pn)) {
@@ -642,12 +656,32 @@ static const mp_rom_map_elem_t mp_constants_table[] = {
642656
#if MICROPY_PY_UCTYPES
643657
{ MP_ROM_QSTR(MP_QSTR_uctypes), MP_ROM_PTR(&mp_module_uctypes) },
644658
#endif
659+
#if MICROPY_PY_BUILTINS_FLOAT && MICROPY_PY_MATH && MICROPY_COMP_CONST_FLOAT
660+
{ MP_ROM_QSTR(MP_QSTR_math), MP_ROM_PTR(&mp_module_math) },
661+
#endif
645662
// Extra constants as defined by a port
646663
MICROPY_PORT_CONSTANTS
647664
};
648665
static MP_DEFINE_CONST_MAP(mp_constants_map, mp_constants_table);
649666
#endif
650667

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

708742
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
743+
// this code does folding of arbitrary numeric expressions, eg 1 + 2 * 3 + 4
710744
// it does not do partial folding, eg 1 + 2 + x -> 3 + x
711745

712746
mp_obj_t arg0;
@@ -716,7 +750,7 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
716750
|| rule_id == RULE_power) {
717751
// folding for binary ops: | ^ & **
718752
mp_parse_node_t pn = peek_result(parser, num_args - 1);
719-
if (!mp_parse_node_get_int_maybe(pn, &arg0)) {
753+
if (!mp_parse_node_get_number_maybe(pn, &arg0)) {
720754
return false;
721755
}
722756
mp_binary_op_t op;
@@ -732,58 +766,61 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
732766
for (ssize_t i = num_args - 2; i >= 0; --i) {
733767
pn = peek_result(parser, i);
734768
mp_obj_t arg1;
735-
if (!mp_parse_node_get_int_maybe(pn, &arg1)) {
769+
if (!mp_parse_node_get_number_maybe(pn, &arg1)) {
736770
return false;
737771
}
772+
#if !MICROPY_COMP_CONST_FLOAT
738773
if (op == MP_BINARY_OP_POWER && mp_obj_int_sign(arg1) < 0) {
739774
// ** can't have negative rhs
740775
return false;
741776
}
742-
arg0 = mp_binary_op(op, arg0, arg1);
777+
#endif
778+
if (!binary_op_maybe(op, arg0, arg1, &arg0)) {
779+
return false;
780+
}
743781
}
744782
} else if (rule_id == RULE_shift_expr
745783
|| rule_id == RULE_arith_expr
746784
|| rule_id == RULE_term) {
747785
// folding for binary ops: << >> + - * @ / % //
748786
mp_parse_node_t pn = peek_result(parser, num_args - 1);
749-
if (!mp_parse_node_get_int_maybe(pn, &arg0)) {
787+
if (!mp_parse_node_get_number_maybe(pn, &arg0)) {
750788
return false;
751789
}
752790
for (ssize_t i = num_args - 2; i >= 1; i -= 2) {
753791
pn = peek_result(parser, i - 1);
754792
mp_obj_t arg1;
755-
if (!mp_parse_node_get_int_maybe(pn, &arg1)) {
793+
if (!mp_parse_node_get_number_maybe(pn, &arg1)) {
756794
return false;
757795
}
758796
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 /
797+
if (tok == MP_TOKEN_OP_AT) {
798+
// Can't fold @
799+
return false;
800+
}
801+
#if !MICROPY_COMP_CONST_FLOAT
802+
if (tok == MP_TOKEN_OP_SLASH) {
803+
// Can't fold /
761804
return false;
762805
}
806+
#endif
763807
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-
}
808+
if (!binary_op_maybe(op, arg0, arg1, &arg0)) {
809+
return false;
775810
}
776-
arg0 = mp_binary_op(op, arg0, arg1);
777811
}
778812
} else if (rule_id == RULE_factor_2) {
779813
// folding for unary ops: + - ~
780814
mp_parse_node_t pn = peek_result(parser, 0);
781-
if (!mp_parse_node_get_int_maybe(pn, &arg0)) {
815+
if (!mp_parse_node_get_number_maybe(pn, &arg0)) {
782816
return false;
783817
}
784818
mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(peek_result(parser, 1));
785819
mp_unary_op_t op;
786820
if (tok == MP_TOKEN_OP_TILDE) {
821+
if (!mp_obj_is_int(arg0)) {
822+
return false;
823+
}
787824
op = MP_UNARY_OP_INVERT;
788825
} else {
789826
assert(tok == MP_TOKEN_OP_PLUS || tok == MP_TOKEN_OP_MINUS); // should be
@@ -855,7 +892,7 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
855892
return false;
856893
}
857894
// id1.id2
858-
// look it up in constant table, see if it can be replaced with an integer
895+
// look it up in constant table, see if it can be replaced with an integer or a float
859896
mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pn1;
860897
assert(MP_PARSE_NODE_IS_ID(pns1->nodes[0]));
861898
qstr q_base = MP_PARSE_NODE_LEAF_ARG(pn0);
@@ -866,7 +903,7 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
866903
}
867904
mp_obj_t dest[2];
868905
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)) {
906+
if (!(dest[0] != MP_OBJ_NULL && (mp_obj_is_int(dest[0]) || mp_obj_is_float(dest[0])) && dest[1] == MP_OBJ_NULL)) {
870907
return false;
871908
}
872909
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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# test constant optimisation, with consts that are floats
2+
try:
3+
float("3.14")
4+
except NameError:
5+
print("SKIP")
6+
raise SystemExit
7+
8+
from micropython import const
9+
10+
# check we can make consts from floats
11+
F1 = const(2.5)
12+
F2 = const(-0.3)
13+
print(type(F1), F1)
14+
print(type(F2), F2)
15+
16+
# check arithmetic with floats
17+
F3 = const(F1 + F2)
18+
F4 = const(F1**2)
19+
print(F3, F4)
20+
21+
# check int operations with float results
22+
F5 = const(1 / 2)
23+
F6 = const(2**-2)
24+
print(F5, F6)
25+
26+
# note: we also test float expression folding when
27+
# we're compiling test cases in tests/float, as
28+
# many expressions are resolved at compile time.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy