Skip to content

Commit cde2ff7

Browse files
committed
py/parse: Add support for math module constants and float folding.
Add a new MICROPY_COMP_FLOAT_CONST feature, enabled by in mpy-cross and when compiling with MICROPY_CONFIG_ROM_LEVEL_FULL_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. Signed-off-by: Yoctopuce dev <dev@yoctopuce.com>
1 parent 6db2997 commit cde2ff7

File tree

9 files changed

+127
-29
lines changed

9 files changed

+127
-29
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_FLOAT_CONST (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_FLOAT_CONST)
93+
#define MICROPY_PY_MATH_CONSTANTS (MICROPY_COMP_FLOAT_CONST)
9294
#define MICROPY_PY_CMATH (0)
9395
#define MICROPY_PY_GC (0)
9496
#define MICROPY_PY_IO (0)

py/builtin.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ 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+
#if MICROPY_PY_MATH
142+
extern const mp_obj_module_t mp_module_math;
143+
#endif
141144

142145
extern const char MICROPY_PY_BUILTINS_HELP_TEXT[];
143146

py/emitcommon.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131

3232
#if MICROPY_ENABLE_COMPILER
3333

34+
#if MICROPY_PY_BUILTINS_FLOAT && MICROPY_COMP_FLOAT_CONST
35+
#include <math.h>
36+
#endif
37+
3438
#if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE
3539
qstr_short_t mp_emit_common_use_qstr(mp_emit_common_t *emit, qstr qst) {
3640
mp_map_elem_t *elem = mp_map_lookup(&emit->qstr_map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
@@ -72,7 +76,22 @@ static bool strictly_equal(mp_obj_t a, mp_obj_t b) {
7276
}
7377
return true;
7478
} else {
75-
return mp_obj_equal(a, b);
79+
if (!mp_obj_equal(a, b)) {
80+
return false;
81+
}
82+
#if MICROPY_PY_BUILTINS_FLOAT && MICROPY_COMP_FLOAT_CONST
83+
if (a_type == &mp_type_float) {
84+
mp_float_t a_val = mp_obj_float_get(a);
85+
if (a_val == 0.0) {
86+
// Although 0.0 == -0.0, they are not strictly_equal and
87+
// must be stored as two different constants in .mpy files
88+
mp_float_t a_sign = MICROPY_FLOAT_C_FUN(copysign)(1.0, a_val);
89+
mp_float_t b_sign = MICROPY_FLOAT_C_FUN(copysign)(1.0, mp_obj_float_get(b));
90+
return a_sign == b_sign;
91+
}
92+
}
93+
#endif
94+
return true;
7695
}
7796
}
7897

py/mpconfig.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,11 @@
475475
#define MICROPY_COMP_CONST (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES)
476476
#endif
477477

478+
// Whether to enable float module constants lookup and folding; eg const(-math.inf)
479+
#ifndef MICROPY_COMP_FLOAT_CONST
480+
#define MICROPY_COMP_FLOAT_CONST (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_FULL_FEATURES)
481+
#endif
482+
478483
// Whether to enable optimisation of: a, b = c, d
479484
// Costs 124 bytes (Thumb2)
480485
#ifndef MICROPY_COMP_DOUBLE_TUPLE_ASSIGN

py/objfloat.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,18 @@ 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_FLOAT_CONST
128+
if (kind == PRINT_REPR) {
129+
// For .mpy files, try to add an extra (partial) digit for precision.
130+
// If the length increases by more than one digit, it means we are
131+
// digging too far (eg. 1.234499999999), so we keep the short form
132+
char ebuf[32];
133+
mp_format_float(o_val, ebuf, sizeof(ebuf), 'g', precision + 1, '\0');
134+
if (strlen(ebuf) - strlen(buf) == 1) {
135+
memcpy(buf, ebuf, sizeof(buf));
136+
}
137+
}
138+
#endif
127139
mp_print_str(print, buf);
128140
if (strchr(buf, '.') == NULL && strchr(buf, 'e') == NULL && strchr(buf, 'n') == NULL) {
129141
// Python floats always have decimal point (unless inf or nan)

py/parse.c

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

339+
#if MICROPY_COMP_FLOAT_CONST
340+
static bool mp_parse_node_get_number_maybe(mp_parse_node_t pn, mp_obj_t *o) {
341+
if (MP_PARSE_NODE_IS_SMALL_INT(pn)) {
342+
*o = MP_OBJ_NEW_SMALL_INT(MP_PARSE_NODE_LEAF_SMALL_INT(pn));
343+
return true;
344+
} else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, RULE_const_object)) {
345+
mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn;
346+
*o = mp_parse_node_extract_const_object(pns);
347+
return mp_obj_is_int(*o) || mp_obj_is_float(*o);
348+
} else {
349+
return false;
350+
}
351+
}
352+
bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o) {
353+
if (!mp_parse_node_get_number_maybe(pn, o)) {
354+
return false;
355+
}
356+
return mp_obj_is_int(*o);
357+
}
358+
#else
339359
bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o) {
340360
if (MP_PARSE_NODE_IS_SMALL_INT(pn)) {
341361
*o = MP_OBJ_NEW_SMALL_INT(MP_PARSE_NODE_LEAF_SMALL_INT(pn));
@@ -348,6 +368,8 @@ bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o) {
348368
return false;
349369
}
350370
}
371+
#define mp_parse_node_get_number_maybe mp_parse_node_get_int_maybe
372+
#endif
351373

352374
#if MICROPY_COMP_CONST_TUPLE || MICROPY_COMP_CONST
353375
static bool mp_parse_node_is_const(mp_parse_node_t pn) {
@@ -642,12 +664,32 @@ static const mp_rom_map_elem_t mp_constants_table[] = {
642664
#if MICROPY_PY_UCTYPES
643665
{ MP_ROM_QSTR(MP_QSTR_uctypes), MP_ROM_PTR(&mp_module_uctypes) },
644666
#endif
667+
#if MICROPY_PY_MATH && MICROPY_COMP_FLOAT_CONST
668+
{ MP_ROM_QSTR(MP_QSTR_math), MP_ROM_PTR(&mp_module_math) },
669+
#endif
645670
// Extra constants as defined by a port
646671
MICROPY_PORT_CONSTANTS
647672
};
648673
static MP_DEFINE_CONST_MAP(mp_constants_map, mp_constants_table);
649674
#endif
650675

676+
static bool binary_op_maybe(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs, mp_obj_t *res) {
677+
nlr_buf_t nlr;
678+
if (nlr_push(&nlr) == 0) {
679+
mp_obj_t tmp = mp_binary_op(op, lhs, rhs);
680+
#if MICROPY_PY_BUILTINS_COMPLEX
681+
if (mp_obj_is_type(tmp, &mp_type_complex)) {
682+
return false;
683+
}
684+
#endif
685+
*res = tmp;
686+
nlr_pop();
687+
return true;
688+
} else {
689+
return false;
690+
}
691+
}
692+
651693
static bool fold_logical_constants(parser_t *parser, uint8_t rule_id, size_t *num_args) {
652694
if (rule_id == RULE_or_test
653695
|| rule_id == RULE_and_test) {
@@ -706,7 +748,7 @@ static bool fold_logical_constants(parser_t *parser, uint8_t rule_id, size_t *nu
706748
}
707749

708750
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
751+
// this code does folding of arbitrary numeric expressions, eg 1 + 2 * 3 + 4
710752
// it does not do partial folding, eg 1 + 2 + x -> 3 + x
711753

712754
mp_obj_t arg0;
@@ -716,7 +758,7 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
716758
|| rule_id == RULE_power) {
717759
// folding for binary ops: | ^ & **
718760
mp_parse_node_t pn = peek_result(parser, num_args - 1);
719-
if (!mp_parse_node_get_int_maybe(pn, &arg0)) {
761+
if (!mp_parse_node_get_number_maybe(pn, &arg0)) {
720762
return false;
721763
}
722764
mp_binary_op_t op;
@@ -732,60 +774,63 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) {
732774
for (ssize_t i = num_args - 2; i >= 0; --i) {
733775
pn = peek_result(parser, i);
734776
mp_obj_t arg1;
735-
if (!mp_parse_node_get_int_maybe(pn, &arg1)) {
777+
if (!mp_parse_node_get_number_maybe(pn, &arg1)) {
736778
return false;
737779
}
780+
#if !MICROPY_COMP_FLOAT_CONST
738781
if (op == MP_BINARY_OP_POWER && mp_obj_int_sign(arg1) < 0) {
739782
// ** can't have negative rhs
740783
return false;
741784
}
742-
arg0 = mp_binary_op(op, arg0, arg1);
785+
#endif
786+
if (!binary_op_maybe(op, arg0, arg1, &arg0)) {
787+
return false;
788+
}
743789
}
744790
} else if (rule_id == RULE_shift_expr
745791
|| rule_id == RULE_arith_expr
746792
|| rule_id == RULE_term) {
747793
// folding for binary ops: << >> + - * @ / % //
748794
mp_parse_node_t pn = peek_result(parser, num_args - 1);
749-
if (!mp_parse_node_get_int_maybe(pn, &arg0)) {
795+
if (!mp_parse_node_get_number_maybe(pn, &arg0)) {
750796
return false;
751797
}
752798
for (ssize_t i = num_args - 2; i >= 1; i -= 2) {
753799
pn = peek_result(parser, i - 1);
754800
mp_obj_t arg1;
755-
if (!mp_parse_node_get_int_maybe(pn, &arg1)) {
801+
if (!mp_parse_node_get_number_maybe(pn, &arg1)) {
756802
return false;
757803
}
758804
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 /
805+
if (tok == MP_TOKEN_OP_AT) {
806+
// Can't fold @
807+
return false;
808+
}
809+
#if !MICROPY_COMP_FLOAT_CONST
810+
if (tok == MP_TOKEN_OP_SLASH) {
811+
// Can't fold /
761812
return false;
762813
}
814+
#endif
763815
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-
}
816+
if (!binary_op_maybe(op, arg0, arg1, &arg0)) {
817+
return false;
775818
}
776-
arg0 = mp_binary_op(op, arg0, arg1);
777819
}
778820
} else if (rule_id == RULE_factor_2) {
779821
// folding for unary ops: + - ~
780822
mp_parse_node_t pn = peek_result(parser, 0);
781-
if (!mp_parse_node_get_int_maybe(pn, &arg0)) {
782-
return false;
783-
}
784823
mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(peek_result(parser, 1));
785824
mp_unary_op_t op;
786825
if (tok == MP_TOKEN_OP_TILDE) {
826+
if (!mp_parse_node_get_int_maybe(pn, &arg0)) {
827+
return false;
828+
}
787829
op = MP_UNARY_OP_INVERT;
788830
} else {
831+
if (!mp_parse_node_get_number_maybe(pn, &arg0)) {
832+
return false;
833+
}
789834
assert(tok == MP_TOKEN_OP_PLUS || tok == MP_TOKEN_OP_MINUS); // should be
790835
op = MP_UNARY_OP_POSITIVE + (tok - MP_TOKEN_OP_PLUS);
791836
}

tests/float/float_parse_doubleprec.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,13 @@
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+
if float(repr(2.0 ** 100)) != (2.0 ** 100):
25+
# this test is needed for coverage, but it will
26+
# only work if MICROPY_COMP_FLOAT_CONST == 1,
27+
# so we should not make the test case fail
28+
pass
29+
30+
# ensure repr does not add meaningless extra digits (1.234999999999)
31+
print(repr(1.2345))

tests/micropython/const_error.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ 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)")
2624
test_syntax("A = const(1 // 0)")
25+
26+
# Expressions below are supported if MICROPY_COMP_FLOAT_CONST is set.
27+
# They should not anymore be expected to always fail:
28+
#
29+
# test_syntax("A = const(1 / 2)")
30+
# test_syntax("A = const(1 ** -2)")

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

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