From 9251732f7487398305df3ba050e69be8b350ba76 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 28 Aug 2025 16:44:39 -0500 Subject: [PATCH] extmod: Add modmpycross. .. with supporting changes across the core. Signed-off-by: Jeff Epler --- extmod/extmod.mk | 1 + extmod/modmpycross.c | 251 ++++++++++++++++++ ports/unix/alloc.c | 9 +- ports/unix/main.c | 29 ++ .../unix/variants/coverage/mpconfigvariant.h | 17 ++ py/asmrv32.h | 4 +- py/asmxtensa.c | 2 +- py/mkrules.mk | 2 +- py/mpconfig.h | 4 + tests/ports/unix/extra_coverage.py.exp | 9 +- 10 files changed, 316 insertions(+), 12 deletions(-) create mode 100644 extmod/modmpycross.c diff --git a/extmod/extmod.mk b/extmod/extmod.mk index 37151ad120..5cd2db9ac1 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -31,6 +31,7 @@ SRC_EXTMOD_C += \ extmod/modlwip.c \ extmod/modmachine.c \ extmod/modmarshal.c \ + extmod/modmpycross.c \ extmod/modnetwork.c \ extmod/modonewire.c \ extmod/modopenamp.c \ diff --git a/extmod/modmpycross.c b/extmod/modmpycross.c new file mode 100644 index 0000000000..619ef7aa7b --- /dev/null +++ b/extmod/modmpycross.c @@ -0,0 +1,251 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Jeff Epler + * + * 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. + */ + +#include "py/runtime.h" + +#if MICROPY_PY_MPYCROSS + +#include "py/builtin.h" +#include "py/compile.h" +#include "py/persistentcode.h" + +static bool parse_arch(qstr arch) { + #if MICROPY_EMIT_X86 + if (arch == MP_QSTR_x86) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_X86; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_X86; + return true; + } + #endif + #if MICROPY_EMIT_X64 + if (arch == MP_QSTR_x64) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_X64; + mp_dynamic_compiler.nlr_buf_num_regs = MAX(MICROPY_NLR_NUM_REGS_X64, MICROPY_NLR_NUM_REGS_X64_WIN); + return true; + } + #endif + #if MICROPY_EMIT_INLINE_THUMB_FLOAT + if (arch == MP_QSTR_armv6) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_ARMV6; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_ARM_THUMB_FP; + return true; + } + if (arch == MP_QSTR_armv6m) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_ARMV6M; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_ARM_THUMB_FP; // need to be conservative so this code can run on armv7emdp + return true; + } + #endif + #if MICROPY_EMIT_THUMB_ARMV7M + if (arch == MP_QSTR_armv7m) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_ARMV7M; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_ARM_THUMB_FP; + return true; + } + if (arch == MP_QSTR_armv7em) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_ARMV7EM; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_ARM_THUMB_FP; + return true; + } + if (arch == MP_QSTR_armv7emsp) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_ARMV7EMSP; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_ARM_THUMB_FP; + return true; + } + if (arch == MP_QSTR_armv7emdp) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_ARMV7EMDP; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_ARM_THUMB_FP; + return true; + } + #endif + #if MICROPY_EMIT_XTENSA + if (arch == MP_QSTR_xtensa) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_XTENSA; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_XTENSA; + return true; + } + #endif + #if MICROPY_EMIT_XTENSAWIN + if (arch == MP_QSTR_xtensawin) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_XTENSAWIN; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_XTENSAWIN; + return true; + } + #endif + #if MICROPY_EMIT_RV32 + if (arch == MP_QSTR_rv32imc) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_RV32IMC; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_RV32I; + return true; + } + #endif + #if MICROPY_EMIT_NATIVE_DEBUG + if (arch == MP_QSTR_debug) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_DEBUG; + mp_dynamic_compiler.nlr_buf_num_regs = 0; + return true; + } + #endif + if (arch == MP_QSTR_host) { + #if defined(__i386__) || defined(_M_IX86) + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_X86; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_X86; + return true; + #elif defined(__x86_64__) || defined(_M_X64) + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_X64; + mp_dynamic_compiler.nlr_buf_num_regs = MAX(MICROPY_NLR_NUM_REGS_X64, MICROPY_NLR_NUM_REGS_X64_WIN); + return true; + #elif defined(__arm__) && !defined(__thumb2__) + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_ARMV6; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_ARM_THUMB_FP; + return true; + #else + mp_raise_ValueError(MP_ERROR_TEXT("unable to determine host architecture for emit_arch=\"host\"\n")); + #endif + } + return false; +} + +static mp_obj_t mpy_compile(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_source, ARG_filename, + ARG_emit_arch, ARG_small_int_bits, ARG_nlr_buf_num_regs, + #if MICROPY_EMIT_NATIVE + ARG_emit_opt, + #endif + }; + mp_arg_t allowed_args[] = { + {MP_QSTR_source, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE}}, + {MP_QSTR_filename, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE}}, + {MP_QSTR_emit_arch, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE}}, + {MP_QSTR_small_int_bits, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 31}}, + {MP_QSTR_nlr_buf_num_regs, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1}}, + #if MICROPY_EMIT_NATIVE + {MP_QSTR_emit_opt, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = MP_EMIT_OPT_NONE}}, + #endif + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + struct mp_dynamic_compiler_t restore_compiler = mp_dynamic_compiler; + #if MICROPY_EMIT_NATIVE + uint restore_emit_opt = MP_STATE_VM(default_emit_opt); + #endif + + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_dynamic_compiler.small_int_bits = args[ARG_small_int_bits].u_int; + + if (args[ARG_emit_arch].u_obj == MP_ROM_NONE) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_NONE; + } else { + + if (!parse_arch(mp_obj_str_get_qstr(args[ARG_emit_arch].u_obj))) { + mp_raise_ValueError(MP_ERROR_TEXT("Invalid emit_arch")); + } + } + + if (args[ARG_nlr_buf_num_regs].u_int >= 0) { + mp_dynamic_compiler.nlr_buf_num_regs = args[ARG_nlr_buf_num_regs].u_int; + } + + #if MICROPY_EMIT_NATIVE + // Set default emitter options + if (args[ARG_emit_opt].u_int < 0 || args[ARG_emit_opt].u_int > MP_EMIT_OPT_ASM) { + mp_raise_ValueError(MP_ERROR_TEXT("Invalid emit_opt")); + } + MP_STATE_VM(default_emit_opt) = args[ARG_emit_opt].u_int; + + if ((MP_STATE_VM(default_emit_opt) == MP_EMIT_OPT_NATIVE_PYTHON + || MP_STATE_VM(default_emit_opt) == MP_EMIT_OPT_VIPER) + && mp_dynamic_compiler.native_arch == MP_NATIVE_ARCH_NONE) { + mp_raise_ValueError(MP_ERROR_TEXT("arch not specified\n")); + } + #endif + + size_t str_len; + const char *str = mp_obj_str_get_data(args[ARG_source].u_obj, &str_len); + + // get the filename + qstr filename = mp_obj_str_get_qstr(args[ARG_filename].u_obj); + + // create the lexer + mp_lexer_t *lex = mp_lexer_new_from_str_len(filename, str, str_len, 0); + + mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_module_context_t ctx; + ctx.module.globals = NULL; + mp_compiled_module_t cm; + cm.context = &ctx; + mp_compile_to_raw_code(&parse_tree, lex->source_name, false, &cm); + + vstr_t vstr; + vstr_init(&vstr, 64); + mp_print_t print = {&vstr, (mp_print_strn_t)vstr_add_strn}; + mp_raw_code_save(&cm, &print); + + mp_obj_t result = mp_obj_new_bytes_from_vstr(&vstr); + + nlr_pop(); + mp_dynamic_compiler = restore_compiler; + #if MICROPY_EMIT_NATIVE + MP_STATE_VM(default_emit_opt) = restore_emit_opt; + #endif + + return result; + } else { + mp_dynamic_compiler = restore_compiler; + #if MICROPY_EMIT_NATIVE + MP_STATE_VM(default_emit_opt) = restore_emit_opt; + #endif + + nlr_raise(MP_OBJ_FROM_PTR(nlr.ret_val)); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(mpy_compile_obj, 1, mpy_compile); + +static const mp_rom_map_elem_t mp_module_mpycross_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_mpycross) }, + { MP_ROM_QSTR(MP_QSTR_compile), MP_ROM_PTR(&mpy_compile_obj) }, + { MP_ROM_QSTR(MP_QSTR_EMIT_OPT_NONE), MP_ROM_INT(MP_EMIT_OPT_NONE) }, + { MP_ROM_QSTR(MP_QSTR_EMIT_OPT_BYTECODE), MP_ROM_INT(MP_EMIT_OPT_BYTECODE) }, + #if MICROPY_EMIT_NATIVE + { MP_ROM_QSTR(MP_QSTR_EMIT_OPT_NATIVE_PYTHON), MP_ROM_INT(MP_EMIT_OPT_NATIVE_PYTHON) }, + { MP_ROM_QSTR(MP_QSTR_EMIT_OPT_VIPER), MP_ROM_INT(MP_EMIT_OPT_VIPER) }, + { MP_ROM_QSTR(MP_QSTR_EMIT_OPT_ASM), MP_ROM_INT(MP_EMIT_OPT_ASM) }, + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_mpycross_globals, mp_module_mpycross_globals_table); +const mp_obj_module_t mp_module_mpycross = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_mpycross_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_mpycross, mp_module_mpycross); +#endif diff --git a/ports/unix/alloc.c b/ports/unix/alloc.c index 9ab2ca04eb..39d9c6d12c 100644 --- a/ports/unix/alloc.c +++ b/ports/unix/alloc.c @@ -50,26 +50,27 @@ typedef struct _mmap_region_t { void mp_unix_alloc_exec(size_t min_size, void **ptr, size_t *size) { // size needs to be a multiple of the page size - *size = (min_size + 0xfff) & (~0xfff); - *ptr = mmap(NULL, *size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + size_t rounded_size = (min_size + 0xfff) & (~0xfff); + *ptr = mmap(NULL, rounded_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (*ptr == MAP_FAILED) { *ptr = NULL; } + *size = min_size; // add new link to the list of mmap'd regions mmap_region_t *rg = m_new_obj(mmap_region_t); rg->ptr = *ptr; - rg->len = min_size; + rg->len = rounded_size; rg->next = MP_STATE_VM(mmap_region_head); MP_STATE_VM(mmap_region_head) = rg; } void mp_unix_free_exec(void *ptr, size_t size) { - munmap(ptr, size); // unlink the mmap'd region from the list for (mmap_region_t **rg = (mmap_region_t **)&MP_STATE_VM(mmap_region_head); *rg != NULL; *rg = (*rg)->next) { if ((*rg)->ptr == ptr) { + munmap(ptr, (*rg)->len); mmap_region_t *next = (*rg)->next; m_del_obj(mmap_region_t, *rg); *rg = next; diff --git a/ports/unix/main.c b/ports/unix/main.c index 51d99ce5f1..176b78e8c6 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -56,6 +56,10 @@ #include "input.h" #include "stack_size.h" +#if MICROPY_DYNAMIC_COMPILER +#include "py/persistentcode.h" +#endif + // Command line options, with their defaults static bool compile_only = false; static uint emit_opt = MP_EMIT_OPT_NONE; @@ -506,6 +510,31 @@ MP_NOINLINE int main_(int argc, char **argv) { signal(SIGPIPE, SIG_IGN); #endif + #if MICROPY_EMIT_NATIVE + // Set default emitter options + MP_STATE_VM(default_emit_opt) = emit_opt; + #endif + + #if MICROPY_DYNAMIC_COMPILER + // set default compiler configuration + mp_dynamic_compiler.small_int_bits = 8 * sizeof(mp_int_t) - 1; + // don't support native emitter unless -march is specified + + #if defined(__i386__) || defined(_M_IX86) + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_X86; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_X86; + #elif defined(__x86_64__) || defined(_M_X64) + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_X64; + mp_dynamic_compiler.nlr_buf_num_regs = MAX(MICROPY_NLR_NUM_REGS_X64, MICROPY_NLR_NUM_REGS_X64_WIN); + #elif defined(__arm__) && !defined(__thumb2__) + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_ARMV6; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_ARM_THUMB_FP; + #else + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_NONE; + mp_dynamic_compiler.nlr_buf_num_regs = 0; + #endif + #endif + pre_process_options(argc, argv); #if MICROPY_ENABLE_GC diff --git a/ports/unix/variants/coverage/mpconfigvariant.h b/ports/unix/variants/coverage/mpconfigvariant.h index 2f5d9683b3..0f74f72f7f 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.h +++ b/ports/unix/variants/coverage/mpconfigvariant.h @@ -53,3 +53,20 @@ #define MICROPY_HW_MCU_NAME MICROPY_PY_SYS_PLATFORM // Keep the standard banner message #define MICROPY_BANNER_MACHINE MICROPY_PY_SYS_PLATFORM " [" MICROPY_PLATFORM_COMPILER "] version" + +#define MICROPY_PY_MPYCROSS (1) + +#define MICROPY_EMIT_X64 (1) +#define MICROPY_EMIT_X86 (1) +#define MICROPY_EMIT_THUMB (1) +#define MICROPY_EMIT_INLINE_THUMB (1) +#define MICROPY_EMIT_ARM (1) +#define MICROPY_EMIT_XTENSA (1) +#define MICROPY_EMIT_INLINE_XTENSA (1) +#define MICROPY_EMIT_XTENSAWIN (1) +#define MICROPY_EMIT_RV32 (1) +#define MICROPY_EMIT_INLINE_RV32 (1) +#define MICROPY_EMIT_NATIVE_DEBUG (1) +#define MICROPY_EMIT_NATIVE_DEBUG_PRINTER (&mp_plat_print) + +#define MICROPY_DYNAMIC_COMPILER (1) diff --git a/py/asmrv32.h b/py/asmrv32.h index dac9c028b0..757e999937 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -703,8 +703,6 @@ void asm_rv32_meta_comparison_le(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2 void asm_rv32_emit_optimised_load_immediate(asm_rv32_t *state, mp_uint_t rd, mp_int_t immediate); -#ifdef GENERIC_ASM_API - void asm_rv32_emit_call_ind(asm_rv32_t *state, mp_uint_t index); void asm_rv32_emit_jump(asm_rv32_t *state, mp_uint_t label); void asm_rv32_emit_jump_if_reg_eq(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t label); @@ -717,6 +715,8 @@ void asm_rv32_emit_mov_reg_pcrel(asm_rv32_t *state, mp_uint_t rd, mp_uint_t labe void asm_rv32_emit_optimised_xor(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs); void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_uint_t base, int32_t offset, mp_uint_t operation_size); +#ifdef GENERIC_ASM_API + #define ASM_T asm_rv32_t #define ASM_ENTRY(state, labels, name) asm_rv32_entry(state, labels) #define ASM_EXIT(state) asm_rv32_exit(state) diff --git a/py/asmxtensa.c b/py/asmxtensa.c index bc3e717d9f..d2fdac7f60 100644 --- a/py/asmxtensa.c +++ b/py/asmxtensa.c @@ -287,7 +287,7 @@ void asm_xtensa_mov_reg_pcrel(asm_xtensa_t *as, uint reg_dest, uint label) { asm_xtensa_op_add_n(as, reg_dest, reg_dest, ASM_XTENSA_REG_A0); } -void asm_xtensa_l32i_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint word_offset) { +static void asm_xtensa_l32i_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint word_offset) { if (word_offset < 16) { asm_xtensa_op_l32i_n(as, reg_dest, reg_base, word_offset); } else if (word_offset < 256) { diff --git a/py/mkrules.mk b/py/mkrules.mk index 6993a13a4f..a4fa7b9852 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -74,7 +74,7 @@ $(BUILD)/%.o: %.s define compile_c $(ECHO) "CC $<" -$(Q)$(CC) $(CFLAGS) -c -MD -MF $(@:.o=.d) -o $@ $< || (echo -e $(HELP_BUILD_ERROR); false) +$(Q)$(CC) $(CFLAGS) -c -MD -MF $(@:.o=.d) -MT "$@" -o $@ $< || (echo -e $(HELP_BUILD_ERROR); false) @# The following fixes the dependency file. @# See http://make.paulandlesley.org/autodep.html for details. @# Regex adjusted from the above to play better with Windows paths, etc. diff --git a/py/mpconfig.h b/py/mpconfig.h index 303eb08f90..cf0765db0b 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1831,6 +1831,10 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_OS_STATVFS (MICROPY_PY_OS) #endif +#ifndef MICROPY_PY_MPYCROSS +#define MICROPY_PY_MPYCROSS (0) +#endif + #ifndef MICROPY_PY_RE #define MICROPY_PY_RE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index 85e0e18cbf..48dfa91211 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -72,13 +72,14 @@ cppexample cryptolib deflate errno example_package ffi framebuf gc hashlib heapq io json machine marshal math -os platform random re -select socket struct sys -termios time tls uctypes -vfs websocket +mpycross os platform random +re select socket struct +sys termios time tls +uctypes vfs websocket me micropython machine marshal math +mpycross argv atexit byteorder exc_info executable exit getsizeof implementation