diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index d16122b720b6c..83afee8f1889a 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -20,6 +20,6 @@ jobs: steps: - uses: actions/checkout@v5 - name: Build - run: make -C examples/embedding -f micropython_embed.mk && make -C examples/embedding + run: tools/ci.sh embedding_build - name: Run - run: ./examples/embedding/embed | grep "hello world" + run: tools/ci.sh embedding_run_tests diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index deee7b265fcb7..c54efc4fd62f6 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -160,6 +160,20 @@ jobs: if: failure() run: tests/run-tests.py --print-failures + repr_e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Install packages + run: tools/ci.sh unix_32bit_setup + - name: Build + run: tools/ci.sh unix_repr_e_build + - name: Run main test suite + run: tools/ci.sh unix_repr_e_run_tests + - name: Print failures + if: failure() + run: tests/run-tests.py --print-failures + gil_enabled: runs-on: ubuntu-latest steps: diff --git a/docs/develop/memorymgt.rst b/docs/develop/memorymgt.rst index 5b1690cc827b3..6dfbb7b9af9aa 100644 --- a/docs/develop/memorymgt.rst +++ b/docs/develop/memorymgt.rst @@ -61,6 +61,9 @@ See ``py/mpconfig.h`` for the specific details of the available representations. **Pointer tagging** +This section describes how objects are stored in ``OBJ_REPR_A``. Terser descriptions of other object +representations are in ``py/mpconfig.h``. + Because pointers are word-aligned, when they are stored in an ``mp_obj_t`` the lower bits of this object handle will be zero. For example on a 32-bit architecture the lower 2 bits will be zero: diff --git a/ports/cc3200/bootmgr/bootloader.mk b/ports/cc3200/bootmgr/bootloader.mk index 39a960ef4fb0a..e253be27e4510 100644 --- a/ports/cc3200/bootmgr/bootloader.mk +++ b/ports/cc3200/bootmgr/bootloader.mk @@ -123,6 +123,10 @@ $(BUILD)/bootloader.bin: $(BUILD)/bootmgr.bin $(ECHO) "Create $@" $(Q)$(SHELL) $(BOOT_GEN) $(BUILD) +# Create an empty "float_consts.h" needed by py/mkrules.mk +$(HEADER_BUILD)/float_consts.h: | $(HEADER_BUILD) + touch $@ + # Create an empty "qstrdefs.generated.h" needed by py/mkrules.mk $(HEADER_BUILD)/qstrdefs.generated.h: | $(HEADER_BUILD) touch $@ diff --git a/ports/embed/embed.mk b/ports/embed/embed.mk index bea5e5d65bbab..66641231f314e 100644 --- a/ports/embed/embed.mk +++ b/ports/embed/embed.mk @@ -25,6 +25,7 @@ CFLAGS += -Wall -Werror -std=c99 # Define the required generated header files. GENHDR_OUTPUT = $(addprefix $(BUILD)/genhdr/, \ + float_consts.h \ moduledefs.h \ mpversion.h \ qstrdefs.generated.h \ diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 81751bf0f7b2c..a67282c0fbb4f 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -241,7 +241,9 @@ endif CXXFLAGS += $(filter-out -Wmissing-prototypes -Wold-style-definition -std=gnu99,$(CFLAGS) $(CXXFLAGS_MOD)) ifeq ($(MICROPY_FORCE_32BIT),1) -RUN_TESTS_MPY_CROSS_FLAGS = --mpy-cross-flags='-march=x86' +$(info RUN_TESTS_MPY_CROSS_FLAGS=$(RUN_TESTS_MPY_CROSS_FLAGS)) +RUN_TESTS_MPY_CROSS_FLAGS ?= --mpy-cross-flags='-march=x86' +$(info RUN_TESTS_MPY_CROSS_FLAGS=$(RUN_TESTS_MPY_CROSS_FLAGS)) endif ifeq ($(CROSS_COMPILE),arm-linux-gnueabi-) diff --git a/ports/unix/main.c b/ports/unix/main.c index db50e12f59821..e2fa6add4ab71 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -60,6 +60,11 @@ static bool compile_only = false; static uint emit_opt = MP_EMIT_OPT_NONE; + +#if defined(MICROPY_UNIX_COVERAGE) && !defined(NO_QSTR) +#include "genhdr/float_consts.h" +#endif + #if MICROPY_ENABLE_GC // Heap size of GC heap (if enabled) // Make it larger on a 64 bit machine, because pointers are larger. @@ -613,6 +618,7 @@ MP_NOINLINE int main_(int argc, char **argv) { MP_DECLARE_CONST_FUN_OBJ_0(extra_cpp_coverage_obj); mp_store_global(MP_QSTR_extra_coverage, MP_OBJ_FROM_PTR(&extra_coverage_obj)); mp_store_global(MP_QSTR_extra_cpp_coverage, MP_OBJ_FROM_PTR(&extra_cpp_coverage_obj)); + mp_store_global(MP_QSTR_one_quarter, (mp_obj_t)MP_CONST_FLOAT_0__25); } #endif diff --git a/ports/unix/variants/repr_e/mpconfigvariant.h b/ports/unix/variants/repr_e/mpconfigvariant.h new file mode 100644 index 0000000000000..82fd84c633bd6 --- /dev/null +++ b/ports/unix/variants/repr_e/mpconfigvariant.h @@ -0,0 +1,43 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// This config exists to test that the MICROPY_OBJ_REPR_E variant +// builds and passes tests. REPR_E uses memory-efficient floating point +// objects encoded directly mp_obj_t (limited exponent range) plus boxed values +// for full float range & precision. Therefore this variant should be built +// using MICROPY_FORCE_32BIT=1 + +#define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_E) +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) + +// Set base feature level. +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) + +// Not compatible with uctypes +#define MICROPY_PY_UCTYPES (0) + +// Enable extra Unix features. +#include "../mpconfigvariant_common.h" diff --git a/ports/unix/variants/repr_e/mpconfigvariant.mk b/ports/unix/variants/repr_e/mpconfigvariant.mk new file mode 100644 index 0000000000000..5765e86bc0ec8 --- /dev/null +++ b/ports/unix/variants/repr_e/mpconfigvariant.mk @@ -0,0 +1,8 @@ +# build interpreter with "bigints" implemented as "longlong" + +# otherwise, small int is essentially 64-bit +MICROPY_FORCE_32BIT := 1 + +MICROPY_PY_FFI := 0 + +RUN_TESTS_MPY_CROSS_FLAGS = --mpy-cross-flags='-march=x86 -msmall-int-bits=30' diff --git a/ports/windows/msvc/genhdr.targets b/ports/windows/msvc/genhdr.targets index 57ec707ef91b4..e62fc5dee8c70 100644 --- a/ports/windows/msvc/genhdr.targets +++ b/ports/windows/msvc/genhdr.targets @@ -4,7 +4,7 @@ - + @@ -16,6 +16,7 @@ $(DestDir)qstrdefs.generated.h $(DestDir)/moduledefs.collected $(DestDir)/root_pointers.collected + $(DestDir)/float_consts.collected $(DestDir)/compressed.collected $(MICROPY_CPYTHON3) python @@ -48,7 +49,8 @@ using(var outFile = System.IO.File.CreateText(OutputFile)) { foreach(var inFile in InputFiles) foreach(var line in System.IO.File.ReadAllLines(inFile)) if((line.Contains(".c") && line.StartsWith("#line")) || line.Contains("MP_QSTR") || - line.Contains("MP_REGISTER") || line.Contains("MP_COMPRESSED_ROM_TEXT")) + line.Contains("MP_REGISTER") || line.Contains("MP_COMPRESSED_ROM_TEXT") || + line.Contains("MP_CONST_FLOAT_")) outFile.WriteLine( line ); } ]]> @@ -122,6 +124,20 @@ using(var outFile = System.IO.File.CreateText(OutputFile)) { + + + + + + + + $(DestDir)float_consts.h + $(DestFile).tmp + + + + + diff --git a/py/make_float_consts.py b/py/make_float_consts.py new file mode 100644 index 0000000000000..89c1c598614c0 --- /dev/null +++ b/py/make_float_consts.py @@ -0,0 +1,134 @@ +""" +This pre-processor parses a single file containing a list of +MP_CONST_FLOAT items. + +These are used to generate a header with the required entries. +""" + +import argparse +import io +import math +import re +import struct +import sys + +from textwrap import dedent + +PATTERN = re.compile(r"MP_CONST_FLOAT_[_0-9a-zA-Z]+") + + +def find_constant_floats(filename): + """Find any MP_CONST_FLOAT definitions in the provided file. + + :param str filename: path to file to check + :return: List[variable_declaration] + """ + with io.open(filename, encoding="utf-8") as c_file_obj: + content = c_file_obj.read() + return set(re.findall(PATTERN, content)) + + +def cast(from_, to, value): + return struct.unpack(to, struct.pack(from_, value))[0] + + +def removeprefix(s, pfx): # assumes s starts with pfx + return s[len(pfx) :] + + +repr_e_bias = 0x30080000 +repr_e_roll = 3 + + +def roll32_l(val, n): + return ((val << n) | (val >> (32 - n))) & 0xFFFFFFFF + + +def generate_constant_float_definition(constant_name): + expression = removeprefix(constant_name, "MP_CONST_FLOAT_") + if expression.startswith("__"): + expression = "-" + expression[2:] + expression = expression.replace("__", ".") + value = eval(expression, math.__dict__) + if expression == "inf": + c_value = "INFINITY" + elif expression == "-inf": + c_value = "-INFINITY" + elif expression == "nan": + c_value = "NAN" + elif expression == "-0": + c_value = "-0." + else: + c_value = float(value) + + print(f"// {constant_name} = {value}") + double_as_uint64 = cast("d", "Q", value) + repr_d_value = double_as_uint64 + 0x8004000000000000 + float_as_uint32 = cast("f", "I", value) + repr_c_value = ((((float_as_uint32) & ~3) | 2) + 0x80800000) & 0xFFFFFFFF + repr_e_value = roll32_l((float_as_uint32 + repr_e_bias) & 0xFFFFFFFF, repr_e_roll) + + print( + dedent(f"""\ + #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_B + extern const struct _mp_obj_float_t {constant_name}_obj; + #define {constant_name} MP_ROM_PTR(&{constant_name}_obj) + #if defined(MP_FLOAT_CONSTS_IMPL) + const mp_obj_float_t {constant_name}_obj = {{ {{&mp_type_float}}, (mp_float_t)({c_value}) }}; + #endif + #elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D + #define {constant_name} {{ ((mp_obj_t)((uint64_t){repr_d_value:#x})) }} + #elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C + #define {constant_name} ((mp_obj_t)((uint64_t){repr_c_value:#x})) + #else // REPR_E""") + ) + + if repr_e_value & 0x3 == 3: + print(f"#define {constant_name} ((mp_obj_t)((uint64_t){repr_e_value:#x}))") + else: + print( + dedent(f"""\ + extern const struct _mp_obj_float_t {constant_name}_obj; + #define {constant_name} MP_ROM_PTR(&{constant_name}_obj) + #if defined(MP_FLOAT_CONSTS_IMPL) + const mp_obj_float_t {constant_name}_obj = {{ {{&mp_type_float}}, (mp_float_t)({c_value}) }}; + #endif""") + ) + + print("""#endif""") + print() + + +def generate_constant_float_header(constant_floats): + """Generate header with root pointer entries. + + :param List[variable_declaration] constant_floats: root pointer declarations + :return: None + """ + + # Print header file for all external modules. + print( + dedent("""\ + // Automatically generated by make_constant_floats.py. + #ifndef MICROPY_INCLUDED_FLOAT_CONSTS_H + #define MICROPY_INCLUDED_FLOAT_CONSTS_H + """) + ) + + for item in constant_floats: + generate_constant_float_definition(item) + + print("#endif") + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("file", nargs=1, help="file with MP_CONST_FLOAT definitions") + args = parser.parse_args() + + constant_floats = find_constant_floats(args.file[0]) + generate_constant_float_header(sorted(constant_floats)) + + +if __name__ == "__main__": + main() diff --git a/py/makeqstrdefs.py b/py/makeqstrdefs.py index dd514c7033dd7..b1c6d0f4073d4 100644 --- a/py/makeqstrdefs.py +++ b/py/makeqstrdefs.py @@ -25,6 +25,9 @@ # Extract MP_REGISTER_ROOT_POINTER(...) macros. _MODE_ROOT_POINTER = "root_pointer" +# Extract MP_CONST_FLOAT_ macros. +_MODE_FLOAT_CONST = "float_const" + class PreprocessorError(Exception): pass @@ -103,6 +106,8 @@ def process_file(f): ) elif args.mode == _MODE_ROOT_POINTER: re_match = re.compile(r"MP_REGISTER_ROOT_POINTER\(.*?\);") + elif args.mode == _MODE_FLOAT_CONST: + re_match = re.compile(r"MP_CONST_FLOAT_[_a-zA-Z0-9]+") output = [] last_fname = None for line in f: @@ -122,7 +127,12 @@ def process_file(f): if args.mode == _MODE_QSTR: name = match.replace("MP_QSTR_", "") output.append("Q(" + name + ")") - elif args.mode in (_MODE_COMPRESS, _MODE_MODULE, _MODE_ROOT_POINTER): + elif args.mode in ( + _MODE_COMPRESS, + _MODE_MODULE, + _MODE_ROOT_POINTER, + _MODE_FLOAT_CONST, + ): output.append(match) if last_fname: @@ -220,7 +230,13 @@ class Args: args.output_dir = sys.argv[4] args.output_file = None if len(sys.argv) == 5 else sys.argv[5] # Unused for command=split - if args.mode not in (_MODE_QSTR, _MODE_COMPRESS, _MODE_MODULE, _MODE_ROOT_POINTER): + if args.mode not in ( + _MODE_QSTR, + _MODE_COMPRESS, + _MODE_MODULE, + _MODE_ROOT_POINTER, + _MODE_FLOAT_CONST, + ): print("error: mode %s unrecognised" % sys.argv[2]) sys.exit(2) diff --git a/py/mkrules.cmake b/py/mkrules.cmake index e3d769cc59b5c..8884ff464e9ce 100644 --- a/py/mkrules.cmake +++ b/py/mkrules.cmake @@ -14,6 +14,9 @@ set(MICROPY_MODULEDEFS "${MICROPY_GENHDR_DIR}/moduledefs.h") set(MICROPY_ROOT_POINTERS_SPLIT "${MICROPY_GENHDR_DIR}/root_pointers.split") set(MICROPY_ROOT_POINTERS_COLLECTED "${MICROPY_GENHDR_DIR}/root_pointers.collected") set(MICROPY_ROOT_POINTERS "${MICROPY_GENHDR_DIR}/root_pointers.h") +set(MICROPY_FLOAT_CONSTS_SPLIT "${MICROPY_GENHDR_DIR}/float_consts.split") +set(MICROPY_FLOAT_CONSTS_COLLECTED "${MICROPY_GENHDR_DIR}/float_consts.collected") +set(MICROPY_FLOAT_CONSTS "${MICROPY_GENHDR_DIR}/float_consts.h") set(MICROPY_COMPRESSED_SPLIT "${MICROPY_GENHDR_DIR}/compressed.split") set(MICROPY_COMPRESSED_COLLECTED "${MICROPY_GENHDR_DIR}/compressed.collected") set(MICROPY_COMPRESSED_DATA "${MICROPY_GENHDR_DIR}/compressed.data.h") @@ -91,6 +94,7 @@ target_sources(${MICROPY_TARGET} PRIVATE ${MICROPY_MPVERSION} ${MICROPY_QSTRDEFS_GENERATED} ${MICROPY_MODULEDEFS} + ${MICROPY_FLOAT_CONSTS} ${MICROPY_ROOT_POINTERS} ) @@ -219,6 +223,32 @@ add_custom_command( DEPENDS ${MICROPY_ROOT_POINTERS_COLLECTED} ${MICROPY_PY_DIR}/make_root_pointers.py ) +# Generate float_consts.h + +add_custom_command( + OUTPUT ${MICROPY_FLOAT_CONSTS_SPLIT} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makeqstrdefs.py split float_const ${MICROPY_GENHDR_DIR}/qstr.i.last ${MICROPY_GENHDR_DIR}/float_const _ + COMMAND touch ${MICROPY_FLOAT_CONSTS_SPLIT} + DEPENDS ${MICROPY_QSTRDEFS_LAST} + VERBATIM + COMMAND_EXPAND_LISTS +) + +add_custom_command( + OUTPUT ${MICROPY_FLOAT_CONSTS_COLLECTED} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makeqstrdefs.py cat float_const _ ${MICROPY_GENHDR_DIR}/float_const ${MICROPY_FLOAT_CONSTS_COLLECTED} + BYPRODUCTS "${MICROPY_FLOAT_CONSTS_COLLECTED}.hash" + DEPENDS ${MICROPY_FLOAT_CONSTS_SPLIT} + VERBATIM + COMMAND_EXPAND_LISTS +) + +add_custom_command( + OUTPUT ${MICROPY_FLOAT_CONSTS} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/make_float_consts.py ${MICROPY_FLOAT_CONSTS_COLLECTED} > ${MICROPY_FLOAT_CONSTS} + DEPENDS ${MICROPY_FLOAT_CONSTS_COLLECTED} ${MICROPY_PY_DIR}/make_float_consts.py +) + # Generate compressed.data.h add_custom_command( @@ -314,6 +344,7 @@ if(MICROPY_FROZEN_MANIFEST) DEPENDS ${MICROPY_QSTRDEFS_GENERATED} ${MICROPY_ROOT_POINTERS} + ${MICROPY_FLOAT_CONSTS} ${MICROPY_MPYCROSS_DEPENDENCY} VERBATIM ) diff --git a/py/mkrules.mk b/py/mkrules.mk index 4968ed58b03ea..3d0a4395030d7 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -18,7 +18,7 @@ HELP_MPY_LIB_SUBMODULE ?= "\033[1;31mError: micropython-lib submodule is not ini OBJ_EXTRA_ORDER_DEPS = # Generate header files. -OBJ_EXTRA_ORDER_DEPS += $(HEADER_BUILD)/moduledefs.h $(HEADER_BUILD)/root_pointers.h +OBJ_EXTRA_ORDER_DEPS += $(HEADER_BUILD)/moduledefs.h $(HEADER_BUILD)/root_pointers.h $(HEADER_BUILD)/float_consts.h ifeq ($(MICROPY_ROM_TEXT_COMPRESSION),1) # If compression is enabled, trigger the build of compressed.data.h... @@ -165,6 +165,16 @@ $(HEADER_BUILD)/root_pointers.collected: $(HEADER_BUILD)/root_pointers.split $(ECHO) "GEN $@" $(Q)$(PYTHON) $(PY_SRC)/makeqstrdefs.py cat root_pointer _ $(HEADER_BUILD)/root_pointer $@ +# Floating point constants via MP_FLOAT_ +$(HEADER_BUILD)/float_consts.split: $(HEADER_BUILD)/qstr.i.last + $(ECHO) "GEN $@" + $(Q)$(PYTHON) $(PY_SRC)/makeqstrdefs.py split float_const $< $(HEADER_BUILD)/float_const _ + $(Q)$(TOUCH) $@ + +$(HEADER_BUILD)/float_consts.collected: $(HEADER_BUILD)/float_consts.split + $(ECHO) "GEN $@" + $(Q)$(PYTHON) $(PY_SRC)/makeqstrdefs.py cat float_const _ $(HEADER_BUILD)/float_const $@ + # Compressed error strings. $(HEADER_BUILD)/compressed.split: $(HEADER_BUILD)/qstr.i.last $(ECHO) "GEN $@" diff --git a/py/modcmath.c b/py/modcmath.c index 33cb00cbe7e69..6a6d352b3f9b9 100644 --- a/py/modcmath.c +++ b/py/modcmath.c @@ -30,6 +30,10 @@ #include +#ifndef NO_QSTR +#include "genhdr/float_consts.h" +#endif + // phase(z): returns the phase of the number z in the range (-pi, +pi] static mp_obj_t mp_cmath_phase(mp_obj_t z_obj) { mp_float_t real, imag; @@ -114,8 +118,8 @@ static MP_DEFINE_CONST_FUN_OBJ_1(mp_cmath_sin_obj, mp_cmath_sin); static const mp_rom_map_elem_t mp_module_cmath_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cmath) }, - { MP_ROM_QSTR(MP_QSTR_e), mp_const_float_e }, - { MP_ROM_QSTR(MP_QSTR_pi), mp_const_float_pi }, + { MP_ROM_QSTR(MP_QSTR_e), MP_CONST_FLOAT_e }, + { MP_ROM_QSTR(MP_QSTR_pi), MP_CONST_FLOAT_pi }, { MP_ROM_QSTR(MP_QSTR_phase), MP_ROM_PTR(&mp_cmath_phase_obj) }, { MP_ROM_QSTR(MP_QSTR_polar), MP_ROM_PTR(&mp_cmath_polar_obj) }, { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&mp_cmath_rect_obj) }, diff --git a/py/modmath.c b/py/modmath.c index 045c842150e44..2acd9c376f5bf 100644 --- a/py/modmath.c +++ b/py/modmath.c @@ -27,6 +27,10 @@ #include "py/builtin.h" #include "py/runtime.h" +#ifndef NO_QSTR +#include "genhdr/float_consts.h" +#endif + #if MICROPY_PY_BUILTINS_FLOAT && MICROPY_PY_MATH #include @@ -388,12 +392,12 @@ static MP_DEFINE_CONST_FUN_OBJ_1(mp_math_factorial_obj, mp_math_factorial); static const mp_rom_map_elem_t mp_module_math_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_math) }, - { MP_ROM_QSTR(MP_QSTR_e), mp_const_float_e }, - { MP_ROM_QSTR(MP_QSTR_pi), mp_const_float_pi }, + { MP_ROM_QSTR(MP_QSTR_e), MP_CONST_FLOAT_e }, + { MP_ROM_QSTR(MP_QSTR_pi), MP_CONST_FLOAT_pi }, #if MICROPY_PY_MATH_CONSTANTS - { MP_ROM_QSTR(MP_QSTR_tau), mp_const_float_tau }, - { MP_ROM_QSTR(MP_QSTR_inf), mp_const_float_inf }, - { MP_ROM_QSTR(MP_QSTR_nan), mp_const_float_nan }, + { MP_ROM_QSTR(MP_QSTR_tau), MP_CONST_FLOAT_tau }, + { MP_ROM_QSTR(MP_QSTR_inf), MP_CONST_FLOAT_inf }, + { MP_ROM_QSTR(MP_QSTR_nan), MP_CONST_FLOAT_nan }, #endif { MP_ROM_QSTR(MP_QSTR_sqrt), MP_ROM_PTR(&mp_math_sqrt_obj) }, { MP_ROM_QSTR(MP_QSTR_pow), MP_ROM_PTR(&mp_math_pow_obj) }, diff --git a/py/mpconfig.h b/py/mpconfig.h index 97c40389b32f0..cdc841520e8db 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -159,6 +159,22 @@ // to the garbage collector. #define MICROPY_OBJ_REPR_D (3) +// A MicroPython object is a machine word having the following form (called R): +// - pppppppp pppppppp pppppppp pppppp00 ptr (4 byte alignment) +// - iiiiiiii iiiiiiii iiiiiiii iiiiii01 small int with 30-bit signed value +// - 00000000 00qqqqqq qqqqqqqq qqqqq010 str with 19-bit qstr value +// - 00000000 00000000 00000000 0ssss110 immediate object with 4-bit value +// - xxxxxxxx xxxxxxxx xxxxxxxx xxxxxx11 limited-exponent 32-bit fp. +// Float stored by: O = (R + 0x30800000 >>> 3), R = (O <<< 3) - 0x30800000 +// where <<< and >>> denote a 32-bit rotate operation. +// This scheme only works with 32-bit word size and float enabled. +// zeros, infinities, nan, and values with large/small exponents are "boxed" +// This representation is loosely inspired by the paper Float Self-Tagging +// by Olivier Melançon, Manuel Serrano, Marc Feeley +// (https://arxiv.org/abs/2411.16544) +#define MICROPY_OBJ_REPR_E (4) + + #ifndef MICROPY_OBJ_REPR #define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_A) #endif diff --git a/py/obj.c b/py/obj.c index 26a912fc6825e..047f129d62af7 100644 --- a/py/obj.c +++ b/py/obj.c @@ -94,7 +94,7 @@ const mp_obj_type_t *MICROPY_WRAP_MP_OBJ_GET_TYPE(mp_obj_get_type)(mp_const_obj_ } else if (mp_obj_is_qstr(o_in)) { return &mp_type_str; #if MICROPY_PY_BUILTINS_FLOAT && ( \ - MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D) + MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_E) } else if (mp_obj_is_float(o_in)) { return &mp_type_float; #endif diff --git a/py/obj.h b/py/obj.h index 9a5d273cdd297..407d8b8c445d1 100644 --- a/py/obj.h +++ b/py/obj.h @@ -102,21 +102,6 @@ static inline bool mp_obj_is_immediate_obj(mp_const_obj_t o) { #define MP_OBJ_NEW_IMMEDIATE_OBJ(val) ((mp_obj_t)(((val) << 3) | 6)) #if MICROPY_PY_BUILTINS_FLOAT -#define mp_const_float_e MP_ROM_PTR(&mp_const_float_e_obj) -#define mp_const_float_pi MP_ROM_PTR(&mp_const_float_pi_obj) -#if MICROPY_PY_MATH_CONSTANTS -#define mp_const_float_tau MP_ROM_PTR(&mp_const_float_tau_obj) -#define mp_const_float_inf MP_ROM_PTR(&mp_const_float_inf_obj) -#define mp_const_float_nan MP_ROM_PTR(&mp_const_float_nan_obj) -#endif -extern const struct _mp_obj_float_t mp_const_float_e_obj; -extern const struct _mp_obj_float_t mp_const_float_pi_obj; -#if MICROPY_PY_MATH_CONSTANTS -extern const struct _mp_obj_float_t mp_const_float_tau_obj; -extern const struct _mp_obj_float_t mp_const_float_inf_obj; -extern const struct _mp_obj_float_t mp_const_float_nan_obj; -#endif - #define mp_obj_is_float(o) mp_obj_is_type((o), &mp_type_float) mp_float_t mp_obj_float_get(mp_obj_t self_in); mp_obj_t mp_obj_new_float(mp_float_t value); @@ -147,21 +132,6 @@ static inline bool mp_obj_is_immediate_obj(mp_const_obj_t o) { #define MP_OBJ_NEW_IMMEDIATE_OBJ(val) ((mp_obj_t)(((val) << 3) | 7)) #if MICROPY_PY_BUILTINS_FLOAT -#define mp_const_float_e MP_ROM_PTR(&mp_const_float_e_obj) -#define mp_const_float_pi MP_ROM_PTR(&mp_const_float_pi_obj) -#if MICROPY_PY_MATH_CONSTANTS -#define mp_const_float_tau MP_ROM_PTR(&mp_const_float_tau_obj) -#define mp_const_float_inf MP_ROM_PTR(&mp_const_float_inf_obj) -#define mp_const_float_nan MP_ROM_PTR(&mp_const_float_nan_obj) -#endif -extern const struct _mp_obj_float_t mp_const_float_e_obj; -extern const struct _mp_obj_float_t mp_const_float_pi_obj; -#if MICROPY_PY_MATH_CONSTANTS -extern const struct _mp_obj_float_t mp_const_float_tau_obj; -extern const struct _mp_obj_float_t mp_const_float_inf_obj; -extern const struct _mp_obj_float_t mp_const_float_nan_obj; -#endif - #define mp_obj_is_float(o) mp_obj_is_type((o), &mp_type_float) mp_float_t mp_obj_float_get(mp_obj_t self_in); mp_obj_t mp_obj_new_float(mp_float_t value); @@ -185,16 +155,8 @@ static inline bool mp_obj_is_small_int(mp_const_obj_t o) { #if MICROPY_PY_BUILTINS_FLOAT #include -// note: MP_OBJ_NEW_CONST_FLOAT should be a MP_ROM_PTR but that macro isn't available yet #define MP_OBJ_NEW_CONST_FLOAT(f) ((mp_obj_t)((((((uint64_t)f) & ~3) | 2) + 0x80800000) & 0xffffffff)) -#define mp_const_float_e MP_OBJ_NEW_CONST_FLOAT(0x402df854) -#define mp_const_float_pi MP_OBJ_NEW_CONST_FLOAT(0x40490fdb) #define mp_const_float_nan MP_OBJ_NEW_CONST_FLOAT(0x7fc00000) -#if MICROPY_PY_MATH_CONSTANTS -#define mp_const_float_tau MP_OBJ_NEW_CONST_FLOAT(0x40c90fdb) -#define mp_const_float_inf MP_OBJ_NEW_CONST_FLOAT(0x7f800000) -#endif - static inline bool mp_obj_is_float(mp_const_obj_t o) { // Ensure that 32-bit arch can only use single precision. MP_STATIC_ASSERT(sizeof(mp_float_t) <= sizeof(mp_obj_t)); @@ -242,7 +204,8 @@ static inline bool mp_obj_is_obj(mp_const_obj_t o) { } #elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D - +#include +#define mp_const_float_nan {((mp_obj_t)((uint64_t)0x7ff8000000000000 + 0x8004000000000000))} static inline bool mp_obj_is_small_int(mp_const_obj_t o) { return (((uint64_t)(o)) & 0xffff000000000000) == 0x0001000000000000; } @@ -267,15 +230,6 @@ static inline bool mp_obj_is_immediate_obj(mp_const_obj_t o) { #error MICROPY_OBJ_REPR_D requires MICROPY_FLOAT_IMPL_DOUBLE #endif -#include -#define mp_const_float_e {((mp_obj_t)((uint64_t)0x4005bf0a8b145769 + 0x8004000000000000))} -#define mp_const_float_pi {((mp_obj_t)((uint64_t)0x400921fb54442d18 + 0x8004000000000000))} -#define mp_const_float_nan {((mp_obj_t)((uint64_t)0x7ff8000000000000 + 0x8004000000000000))} -#if MICROPY_PY_MATH_CONSTANTS -#define mp_const_float_tau {((mp_obj_t)((uint64_t)0x401921fb54442d18 + 0x8004000000000000))} -#define mp_const_float_inf {((mp_obj_t)((uint64_t)0x7ff0000000000000 + 0x8004000000000000))} -#endif - static inline bool mp_obj_is_float(mp_const_obj_t o) { return ((uint64_t)(o) & 0xfffc000000000000) != 0; } @@ -323,6 +277,38 @@ typedef union _mp_rom_obj_t { #define MP_ROM_PTR(p) {.u32 = {.lo = NULL, .hi = (p)}} #endif +#elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_E + +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_NONE +#error "MICROPY_OBJ_REPR_E requires float to be enabled." +#endif + +static inline bool mp_obj_is_small_int(mp_const_obj_t o) { + return (((mp_int_t)(o)) & 3) == 1; +} +#define MP_OBJ_SMALL_INT_VALUE(o) (((mp_int_t)(o)) >> 2) +#define MP_OBJ_NEW_SMALL_INT(small_int) ((mp_obj_t)((((mp_uint_t)(small_int)) << 2) | 1)) + +static inline bool mp_obj_is_qstr(mp_const_obj_t o) { + return (((mp_uint_t)(o)) & 0xff80000f) == 0x00000006; +} +#define MP_OBJ_QSTR_VALUE(o) (((mp_uint_t)(o)) >> 4) +#define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)((((mp_uint_t)(qst)) << 4) | 0x00000006)) + +static inline bool mp_obj_is_immediate_obj(mp_const_obj_t o) { + return (((mp_uint_t)(o)) & 0xff80000f) == 0x0000000e; +} +#define MP_OBJ_IMMEDIATE_OBJ_VALUE(o) (((mp_uint_t)(o)) >> 4) +#define MP_OBJ_NEW_IMMEDIATE_OBJ(val) ((mp_obj_t)(((val) << 4) | 0xe)) + +static inline bool mp_obj_is_obj(mp_const_obj_t o) { + return (((mp_int_t)(o)) & 3) == 0; +} + +#include +bool mp_obj_is_float(mp_const_obj_t o); +mp_float_t mp_obj_float_get(mp_const_obj_t o); +mp_obj_t mp_obj_new_float(mp_float_t f); #endif // Macros to convert between mp_obj_t and concrete object types. diff --git a/py/objfloat.c b/py/objfloat.c index b0ad70de4a3e8..599834aa50c9e 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -43,31 +43,15 @@ #include "py/formatfloat.h" #if MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_C && MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_D - -// M_E and M_PI are not part of the math.h standard and may not be defined -#ifndef M_E -#define M_E (2.7182818284590452354) -#endif -#ifndef M_PI -#define M_PI (3.14159265358979323846) -#endif - typedef struct _mp_obj_float_t { mp_obj_base_t base; mp_float_t value; } mp_obj_float_t; - -const mp_obj_float_t mp_const_float_e_obj = {{&mp_type_float}, (mp_float_t)M_E}; -const mp_obj_float_t mp_const_float_pi_obj = {{&mp_type_float}, (mp_float_t)M_PI}; -#if MICROPY_PY_MATH_CONSTANTS -#ifndef NAN -#error NAN macro is not defined -#endif -const mp_obj_float_t mp_const_float_tau_obj = {{&mp_type_float}, (mp_float_t)(2.0 * M_PI)}; -const mp_obj_float_t mp_const_float_inf_obj = {{&mp_type_float}, (mp_float_t)INFINITY}; -const mp_obj_float_t mp_const_float_nan_obj = {{&mp_type_float}, (mp_float_t)NAN}; #endif +#ifndef NO_QSTR +#define MP_FLOAT_CONSTS_IMPL +#include "genhdr/float_consts.h" #endif #define MICROPY_FLOAT_ZERO MICROPY_FLOAT_CONST(0.0) @@ -179,7 +163,71 @@ MP_DEFINE_CONST_OBJ_TYPE( binary_op, float_binary_op ); -#if MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_C && MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_D +#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_E +static mp_uint_t roll_l(mp_uint_t val, mp_uint_t n) { + return (val << n) | (val >> (32 - n)); +} +#define MICROPY_FLOAT_ROLL (3) +#define MICROPY_FLOAT_BIAS (0x30080000) +#define MICROPY_FLOAT_TO_O(f) (roll_l((f) + MICROPY_FLOAT_BIAS, MICROPY_FLOAT_ROLL)) +#define MICROPY_FLOAT_FROM_O(f) (roll_l((f), 32 - MICROPY_FLOAT_ROLL) - MICROPY_FLOAT_BIAS) +static const struct { mp_uint_t u; + mp_rom_obj_t o; +} float_constants[] = { + { 0, MP_CONST_FLOAT_0 }, + { 0x80000000, MP_CONST_FLOAT___0 }, + { 0x7f800000, MP_CONST_FLOAT_inf }, + { 0x8f800000, MP_CONST_FLOAT___inf }, +}; + +mp_obj_t mp_obj_new_float(mp_float_t value) { + union { + mp_float_t f; + mp_uint_t u; + } num = {.f = value }; + mp_uint_t u = MICROPY_FLOAT_TO_O(num.u); + if ((u & 0x3) == 3) { + return (mp_obj_t)u; + } + for (size_t i = 0; i < MP_ARRAY_SIZE(float_constants); i++) { + if (num.u == float_constants[i].u) { + return (mp_obj_t)float_constants[i].o; + } + } + if (isnan(value)) { + return (mp_obj_t)MP_CONST_FLOAT_nan; + } + // Don't use mp_obj_malloc here to avoid extra function call overhead. + mp_obj_float_t *o = m_new_obj(mp_obj_float_t); + o->base.type = &mp_type_float; + o->value = value; + return MP_OBJ_FROM_PTR(o); +} +mp_float_t mp_obj_float_get(mp_const_obj_t o) { + if (mp_obj_is_obj(o)) { + mp_obj_float_t *self = MP_OBJ_TO_PTR(o); + return self->value; + } + mp_uint_t u = (mp_uint_t)o; + u = (u << 3) | (u >> 29); + u = u - 0x30800000; + union { + mp_float_t f; + mp_uint_t u; + } num = {.u = MICROPY_FLOAT_FROM_O((mp_uint_t)o) }; + return num.f; +} +bool mp_obj_is_float(mp_const_obj_t o) { + // Ensure that 32-bit arch can only use single precision. + MP_STATIC_ASSERT(sizeof(mp_float_t) <= sizeof(mp_obj_t)); + + if (mp_obj_is_obj(o)) { + const mp_obj_base_t *self = MP_OBJ_TO_PTR(o); + return self->type == &mp_type_float; + } + return ((mp_uint_t)(o) & 3) == 3; +} +#elif MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_C && MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_D mp_obj_t mp_obj_new_float(mp_float_t value) { // Don't use mp_obj_malloc here to avoid extra function call overhead. diff --git a/py/objint.c b/py/objint.c index 901264aca6564..27604ee250835 100644 --- a/py/objint.c +++ b/py/objint.c @@ -117,7 +117,7 @@ static mp_fp_as_int_class_t mp_classify_fp_as_int(mp_float_t val) { } // 8 * sizeof(uintptr_t) counts the number of bits for a small int // TODO provide a way to configure this properly - #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_B + #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_B || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_E if (e <= ((8 * sizeof(uintptr_t) + MP_FLOAT_EXP_BIAS - 4) << MP_FLOAT_EXP_SHIFT_I32)) { return MP_FP_CLASS_FIT_SMALLINT; } diff --git a/py/py.mk b/py/py.mk index e7716a1adc9a6..2036dd5f23501 100644 --- a/py/py.mk +++ b/py/py.mk @@ -261,6 +261,11 @@ $(HEADER_BUILD)/root_pointers.h: $(HEADER_BUILD)/root_pointers.collected $(PY_SR @$(ECHO) "GEN $@" $(Q)$(PYTHON) $(PY_SRC)/make_root_pointers.py $< > $@ +# build a list of registered root pointers for py/mpstate.h. +$(HEADER_BUILD)/float_consts.h: $(HEADER_BUILD)/float_consts.collected $(PY_SRC)/make_float_consts.py + @$(ECHO) "GEN $@" + $(Q)$(PYTHON) $(PY_SRC)/make_float_consts.py $< > $@ + # Standard C functions like memset need to be compiled with special flags so # the compiler does not optimise these functions in terms of themselves. CFLAGS_BUILTIN ?= -ffreestanding -fno-builtin -fno-lto diff --git a/py/smallint.h b/py/smallint.h index ec5b0af3b2867..9457866f2229c 100644 --- a/py/smallint.h +++ b/py/smallint.h @@ -41,7 +41,7 @@ // Mask to truncate mp_int_t to positive value #define MP_SMALL_INT_POSITIVE_MASK ~(MP_OBJ_WORD_MSBIT_HIGH | (MP_OBJ_WORD_MSBIT_HIGH >> 1)) -#elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_B +#elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_B || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_E #define MP_SMALL_INT_MIN ((mp_int_t)(((mp_int_t)MP_OBJ_WORD_MSBIT_HIGH) >> 2)) #define MP_SMALL_INT_FITS(n) ((((n) & MP_SMALL_INT_MIN) == 0) || (((n) & MP_SMALL_INT_MIN) == MP_SMALL_INT_MIN)) diff --git a/tests/ports/unix/extra_coverage.py b/tests/ports/unix/extra_coverage.py index 2087e7ea2adc5..450a92bbe3177 100644 --- a/tests/ports/unix/extra_coverage.py +++ b/tests/ports/unix/extra_coverage.py @@ -149,3 +149,5 @@ foo_f() print(foo_f == example_package.foo.f) + +print(one_quarter) diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index d11e5ee6f4215..45308ceee49f6 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -270,3 +270,4 @@ example_package.foo.bar.f True example_package.foo.f True +0.25 diff --git a/tools/ci.sh b/tools/ci.sh index 42d231c458127..29607e9fbdd8d 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -937,6 +937,17 @@ function ci_unix_repr_b_run_tests { ci_unix_run_tests_helper "${CI_UNIX_OPTS_REPR_B[@]}" } +function ci_unix_repr_e_build { + ci_unix_build_helper VARIANT=repr_e +} + +function ci_unix_repr_e_run_tests { + # ci_unix_run_tests_full_no_native_helper is not used due to + # https://github.com/micropython/micropython/issues/18105 + ci_unix_run_tests_helper VARIANT=repr_e +} + + ######################################################################################## # ports/windows @@ -1023,6 +1034,18 @@ function ci_alif_ae3_build { make ${MAKEOPTS} -C ports/alif BOARD=ALIF_ENSEMBLE MCU_CORE=M55_DUAL } +######################################################################################## +# embedding +# +function ci_embedding_build { + make ${MAKEOPTS} -C examples/embedding -f micropython_embed.mk && make -C examples/embedding +} + +function ci_embedding_run_tests { + set -o pipefail + ./examples/embedding/embed | grep "hello world" +} + function _ci_help { # Note: these lines must be indented with tab characters (required by bash <<-EOF) cat <<-EOF