Skip to content

py/parse: Add support for math module constants and float folding #16666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion mpy-cross/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions py/builtin.h
Original file line number Diff line number Diff line change
Expand Up @@ -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[];

Expand Down
17 changes: 16 additions & 1 deletion py/emitcommon.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/

#include <assert.h>
#include <math.h>

#include "py/emit.h"
#include "py/nativeglue.h"
Expand Down Expand Up @@ -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;
}
}

Expand Down
7 changes: 7 additions & 0 deletions py/mpconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
89 changes: 64 additions & 25 deletions py/parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
}
#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);
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 (!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
Expand Down Expand Up @@ -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);
Expand All @@ -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];
Expand Down
6 changes: 6 additions & 0 deletions tests/float/float_parse_doubleprec.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@
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))
2 changes: 0 additions & 2 deletions tests/micropython/const_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ def test_syntax(code):

# 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)")
Expand Down
2 changes: 0 additions & 2 deletions tests/micropython/const_error.py.exp
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,3 @@ SyntaxError
SyntaxError
SyntaxError
SyntaxError
SyntaxError
SyntaxError
23 changes: 23 additions & 0 deletions tests/micropython/const_float.py
Original file line number Diff line number Diff line change
@@ -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 changes: 4 additions & 0 deletions tests/micropython/const_float.py.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<class 'float'> 2.5
<class 'float'> -0.3
2.2 6.25
0.5 0.25
18 changes: 18 additions & 0 deletions tests/micropython/const_math.py
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions tests/micropython/const_math.py.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0
Loading
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