diff --git a/lib/micropython-lib b/lib/micropython-lib index f345a0db99b9d..5b496e944ec04 160000 --- a/lib/micropython-lib +++ b/lib/micropython-lib @@ -1 +1 @@ -Subproject commit f345a0db99b9dc454ffcbfb13bfb68978467018d +Subproject commit 5b496e944ec045177afa1620920a168410b7f60b diff --git a/lib/nxp_driver b/lib/nxp_driver index fa5a554c7944d..72fa6068dbfb6 160000 --- a/lib/nxp_driver +++ b/lib/nxp_driver @@ -1 +1 @@ -Subproject commit fa5a554c7944d2a196626f8d3631e44943f9abcc +Subproject commit 72fa6068dbfb6080272227f589352f6da6627fdb diff --git a/lib/protobuf-c b/lib/protobuf-c index abc67a11c6db2..4719fdd776062 160000 --- a/lib/protobuf-c +++ b/lib/protobuf-c @@ -1 +1 @@ -Subproject commit abc67a11c6db271bedbb9f58be85d6f4e2ea8389 +Subproject commit 4719fdd7760624388c2c5b9d6759eb6a47490626 diff --git a/lib/wiznet5k b/lib/wiznet5k index 0803fc519ad72..ce4a7b6d07541 160000 --- a/lib/wiznet5k +++ b/lib/wiznet5k @@ -1 +1 @@ -Subproject commit 0803fc519ad7227e841287fb3638d6c8b2f111a1 +Subproject commit ce4a7b6d07541bf0ba9f91e369276b38faa619bd diff --git a/ports/unix/variants/standard/mpconfigvariant.h b/ports/unix/variants/standard/mpconfigvariant.h index 447832a7656b6..024b08fec7245 100644 --- a/ports/unix/variants/standard/mpconfigvariant.h +++ b/ports/unix/variants/standard/mpconfigvariant.h @@ -27,7 +27,9 @@ // Set base feature level. #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) -#define MICROPY_PY_SYS_SETTRACE (1) +// #define MICROPY_PY_SYS_SETTRACE (1) +// #define MICROPY_PY_SYS_SETTRACE_SAVE_NAMES (1) // Save local variable names for debugging + // Enable extra Unix features. #include "../mpconfigvariant_common.h" diff --git a/py/compile.c b/py/compile.c index 7a1151bcd66f0..219fcf57eecab 100644 --- a/py/compile.c +++ b/py/compile.c @@ -38,6 +38,9 @@ #include "py/nativeglue.h" #include "py/persistentcode.h" #include "py/smallint.h" +#if MICROPY_PY_SYS_SETTRACE_SAVE_NAMES +#include "py/localnames.h" +#endif #if MICROPY_ENABLE_COMPILER @@ -3435,6 +3438,21 @@ static void scope_compute_things(scope_t *scope) { } } + #if MICROPY_PY_SYS_SETTRACE_SAVE_NAMES + // Save the local variable names in the raw_code for debugging + if (SCOPE_IS_FUNC_LIKE(scope->kind) && scope->num_locals > 0) { + // Initialize local_names structure with variable names + for (int i = 0; i < scope->id_info_len; i++) { + id_info_t *id = &scope->id_info[i]; + if ((id->kind == ID_INFO_KIND_LOCAL || id->kind == ID_INFO_KIND_CELL) && + id->local_num < scope->num_locals && + id->local_num < MICROPY_PY_SYS_SETTRACE_NAMES_MAX) { + mp_local_names_add(&scope->raw_code->local_names, id->local_num, id->qst); + } + } + } + #endif + // compute the index of free vars // make sure they are in the order of the parent scope if (scope->parent != NULL) { diff --git a/py/emitglue.c b/py/emitglue.c index 27cbb349ef602..a1ec52f11a74f 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -57,6 +57,9 @@ mp_raw_code_t *mp_emit_glue_new_raw_code(void) { #if MICROPY_PY_SYS_SETTRACE rc->line_of_definition = 0; #endif + #if MICROPY_PY_SYS_SETTRACE_SAVE_NAMES + mp_local_names_init(&rc->local_names); + #endif return rc; } diff --git a/py/emitglue.h b/py/emitglue.h index 126462671b003..1c2fffd23f7eb 100644 --- a/py/emitglue.h +++ b/py/emitglue.h @@ -28,6 +28,9 @@ #include "py/obj.h" #include "py/bc.h" +#if MICROPY_PY_SYS_SETTRACE_SAVE_NAMES +#include "py/localnames.h" +#endif // These variables and functions glue the code emitters to the runtime. @@ -96,6 +99,9 @@ typedef struct _mp_raw_code_t { uint32_t asm_n_pos_args : 8; uint32_t asm_type_sig : 24; // compressed as 2-bit types; ret is MSB, then arg0, arg1, etc #endif + #if MICROPY_PY_SYS_SETTRACE_SAVE_NAMES + mp_local_names_t local_names; // Maps local variable indices to names + #endif } mp_raw_code_t; // Version of mp_raw_code_t but without the asm_n_pos_args/asm_type_sig entries, which are diff --git a/py/localnames.c b/py/localnames.c new file mode 100644 index 0000000000000..90bbe01adf4e8 --- /dev/null +++ b/py/localnames.c @@ -0,0 +1,112 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Jos Verlinde + * + * Permission is hereby granted, free + * 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" +#include "py/localnames.h" + +#if MICROPY_PY_SYS_SETTRACE_SAVE_NAMES + +// Initialize the local names structure +void mp_local_names_init(mp_local_names_t *local_names) { + if (local_names == NULL) { + return; + } + + local_names->num_locals = 0; + local_names->order_count = 0; + + // Initialize all entries with null qstrs and invalid indices + for (uint16_t i = 0; i < MICROPY_PY_SYS_SETTRACE_NAMES_MAX; i++) { + local_names->local_names[i] = MP_QSTRnull; + local_names->local_nums[i] = UINT16_MAX; // Invalid index marker + } +} + +// Get the name of a local variable by its index +qstr mp_local_names_get_name(const mp_local_names_t *local_names, uint16_t local_num) { + if (local_names == NULL || local_num >= MICROPY_PY_SYS_SETTRACE_NAMES_MAX) { + return MP_QSTRnull; + } + + // Direct array access + return local_names->local_names[local_num]; +} + +// Look up the original local_num by order index (source code order) +uint16_t mp_local_names_get_local_num(const mp_local_names_t *local_names, uint16_t order_idx) { + if (local_names == NULL || order_idx >= local_names->order_count) { + return UINT16_MAX; // Invalid index + } + + return local_names->local_nums[order_idx]; +} + +// Add or update a name mapping for a local variable +void mp_local_names_add(mp_local_names_t *local_names, uint16_t local_num, qstr qstr_name) { + if (local_names == NULL || local_num >= MICROPY_PY_SYS_SETTRACE_NAMES_MAX) { + return; + } + + // Store name directly using local_num as index + local_names->local_names[local_num] = qstr_name; + + // Update number of locals if needed + if (local_num >= local_names->num_locals) { + local_names->num_locals = local_num + 1; + } + + // Also store in order of definition for correct runtime mapping + if (local_names->order_count < MICROPY_PY_SYS_SETTRACE_NAMES_MAX) { + uint16_t idx = local_names->order_count; + local_names->local_nums[idx] = local_num; + local_names->order_count++; + } + + // Refine runtime slot mapping logic + // Test the hypothesis that variables are assigned from highest slots down + uint16_t runtime_slot = local_num; // Default to direct mapping + + if (local_names->order_count > 0) { + // Find position in order array + for (uint16_t i = 0; i < local_names->order_count; ++i) { + if (local_names->local_nums[i] == local_num) { + runtime_slot = i; + break; + } + } + } + local_names->runtime_slots[local_num] = runtime_slot; +} + +// Get the runtime slot for a local variable by its index +uint16_t mp_local_names_get_runtime_slot(const mp_local_names_t *local_names, uint16_t local_num) { + if (local_names == NULL || local_num >= MICROPY_PY_SYS_SETTRACE_NAMES_MAX) { + return UINT16_MAX; // Invalid slot + } + return local_names->runtime_slots[local_num]; +} + +#endif // MICROPY_PY_SYS_SETTRACE_SAVE_NAMES diff --git a/py/localnames.h b/py/localnames.h new file mode 100644 index 0000000000000..fc968c56b6ab0 --- /dev/null +++ b/py/localnames.h @@ -0,0 +1,63 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Contributors to the MicroPython project + * + * 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. + */ + +#ifndef MICROPY_INCLUDED_PY_LOCALNAMES_H +#define MICROPY_INCLUDED_PY_LOCALNAMES_H + +#include "py/obj.h" +#include "py/qstr.h" + +#if MICROPY_PY_SYS_SETTRACE_SAVE_NAMES + +#define MICROPY_PY_SYS_SETTRACE_NAMES_MAX 32 // Maximum number of local variables to store names for + +// Structure to hold variable name mappings for a function scope +typedef struct _mp_local_names_t { + uint16_t num_locals; // Total number of local variables with names + qstr local_names[MICROPY_PY_SYS_SETTRACE_NAMES_MAX]; // Array of variable names, indexed by local_num + uint16_t local_nums[MICROPY_PY_SYS_SETTRACE_NAMES_MAX]; // Reverse mapping: name index -> local_num (for correct state array mapping) + uint16_t order_count; // Number of variables stored in order they were defined + uint16_t runtime_slots[MICROPY_PY_SYS_SETTRACE_NAMES_MAX]; // Mapping of local_num to runtime slots +} mp_local_names_t; + +// Initialize the local names structure +void mp_local_names_init(mp_local_names_t *local_names); + +// Function to look up a variable name by its index +qstr mp_local_names_get_name(const mp_local_names_t *local_names, uint16_t local_num); + +// Function to look up the original local_num by order index (source code order) +uint16_t mp_local_names_get_local_num(const mp_local_names_t *local_names, uint16_t order_idx); + +// Function to add or update a name mapping for a local variable +void mp_local_names_add(mp_local_names_t *local_names, uint16_t local_num, qstr qstr_name); + +// Function to get the runtime slot of a local variable by its index +uint16_t mp_local_names_get_runtime_slot(const mp_local_names_t *local_names, uint16_t local_num); + +#endif // MICROPY_PY_SYS_SETTRACE_SAVE_NAMES + +#endif // MICROPY_INCLUDED_PY_LOCALNAMES_H diff --git a/py/modsys.c b/py/modsys.c index 3ce14bf5d17ad..dfdc5a76170ce 100644 --- a/py/modsys.c +++ b/py/modsys.c @@ -239,7 +239,7 @@ static mp_obj_t mp_sys_settrace(mp_obj_t obj) { } MP_DEFINE_CONST_FUN_OBJ_1(mp_sys_settrace_obj, mp_sys_settrace); -static mp_obj_t mp_sys_gettrace() { +static mp_obj_t mp_sys_gettrace(void) { return mp_prof_gettrace(); } MP_DEFINE_CONST_FUN_OBJ_0(mp_sys_gettrace_obj, mp_sys_gettrace); diff --git a/py/mpconfig.h b/py/mpconfig.h index 01712bd5b4d90..d0e7db64410d5 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -238,6 +238,11 @@ #define MICROPY_ALLOC_PARSE_RESULT_INC (16) #endif +// If not explicitly enabled, disable local variable name saving +#ifndef MICROPY_PY_SYS_SETTRACE_SAVE_NAMES +#define MICROPY_PY_SYS_SETTRACE_SAVE_NAMES (0) +#endif + // Strings this length or less will be interned by the parser #ifndef MICROPY_ALLOC_PARSE_INTERN_STRING_LEN #define MICROPY_ALLOC_PARSE_INTERN_STRING_LEN (10) diff --git a/py/objfun.c b/py/objfun.c index f71f7c9ee32bb..088c013c57ae6 100644 --- a/py/objfun.c +++ b/py/objfun.c @@ -36,6 +36,9 @@ #include "py/bc.h" #include "py/stackctrl.h" #include "py/profile.h" +#if MICROPY_PY_SYS_SETTRACE_SAVE_NAMES +#include "py/localnames.h" +#endif #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_PRINT (1) diff --git a/py/profile.c b/py/profile.c index 4b813bb0d7b06..c2f966e90dcb2 100644 --- a/py/profile.c +++ b/py/profile.c @@ -28,6 +28,9 @@ #include "py/bc0.h" #include "py/gc.h" #include "py/objfun.h" +#if MICROPY_PY_SYS_SETTRACE_SAVE_NAMES +#include "py/localnames.h" +#endif #if MICROPY_PY_SYS_SETTRACE @@ -85,6 +88,8 @@ static void frame_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t ); } +static mp_obj_t frame_f_locals(mp_obj_t self_in); // Forward declaration + static void frame_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { mp_obj_frame_t *o = MP_OBJ_TO_PTR(self_in); @@ -125,11 +130,125 @@ static void frame_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { dest[0] = o->f_trace; break; case MP_QSTR_f_locals: - dest[0] = mp_obj_new_dict(0); + dest[0] = frame_f_locals(self_in); break; } } +static mp_obj_t frame_f_locals(mp_obj_t self_in) { + // This function returns a dictionary of local variables in the current frame. + if (gc_is_locked()) { + return MP_OBJ_NULL; // Cannot create locals dict when GC is locked + } + mp_obj_frame_t *frame = MP_OBJ_TO_PTR(self_in); + mp_obj_dict_t *locals_dict = mp_obj_new_dict(frame->code_state->n_state); + + const mp_code_state_t *code_state = frame->code_state; + + if (code_state == NULL) { + // Return empty dictionary if state is invalid + return MP_OBJ_FROM_PTR(locals_dict); + } + + #if MICROPY_PY_SYS_SETTRACE_SAVE_NAMES + const mp_raw_code_t *raw_code = code_state->fun_bc->rc; + + // First, handle function parameters (these should have fixed positions) + uint16_t n_pos_args = raw_code->prelude.n_pos_args; + uint16_t n_kwonly_args = raw_code->prelude.n_kwonly_args; + uint16_t param_count = n_pos_args + n_kwonly_args; + + // Add parameters first (they have fixed slot assignments) + for (uint16_t i = 0; i < param_count && i < code_state->n_state; i++) { + if (code_state->state[i] == NULL) { + continue; + } + + qstr var_name_qstr = MP_QSTRnull; + if (i < MICROPY_PY_SYS_SETTRACE_NAMES_MAX) { + var_name_qstr = mp_local_names_get_name(&raw_code->local_names, i); + } + + if (var_name_qstr == MP_QSTRnull) { + vstr_t vstr; + vstr_init(&vstr, 16); // Initialize with enough space + vstr_printf(&vstr, "arg_%d", (int)(i + 1)); + var_name_qstr = qstr_from_str(vstr_str(&vstr)); + vstr_clear(&vstr); + + if (var_name_qstr == MP_QSTR_NULL) { + continue; + } + } + } + + // Handle local variables with REVERSE SLOT ASSIGNMENT + bool used_slots[MICROPY_PY_SYS_SETTRACE_NAMES_MAX] = {false}; + + // Mark parameter slots as used + for (uint16_t i = 0; i < param_count; i++) { + if (i < MICROPY_PY_SYS_SETTRACE_NAMES_MAX) { + used_slots[i] = true; + } + } + + // Process variables using their source order but REVERSE slot assignment + for (uint16_t order_idx = 0; order_idx < raw_code->local_names.order_count; order_idx++) { + uint16_t local_num = mp_local_names_get_local_num(&raw_code->local_names, order_idx); + + // Skip parameters and invalid entries + if (local_num == UINT16_MAX || local_num < param_count) { + continue; + } + + qstr var_name_qstr = mp_local_names_get_name(&raw_code->local_names, local_num); + if (var_name_qstr == MP_QSTRnull) { + continue; + } + + // REVERSE SLOT ASSIGNMENT: Variables assigned from highest available slot down + uint16_t total_locals = code_state->n_state; + uint16_t reverse_slot = total_locals - 1 - order_idx; + // Validate and assign + if (reverse_slot >= param_count && reverse_slot < total_locals && + code_state->state[reverse_slot] != NULL && !used_slots[reverse_slot]) { + mp_obj_dict_store(locals_dict, MP_OBJ_NEW_QSTR(var_name_qstr), code_state->state[reverse_slot]); + used_slots[reverse_slot] = true; + } else { + // Fallback: try runtime slot or direct mapping + uint16_t runtime_slot = mp_local_names_get_runtime_slot(&raw_code->local_names, local_num); + if (runtime_slot != UINT16_MAX && runtime_slot < total_locals && + code_state->state[runtime_slot] != NULL && !used_slots[runtime_slot]) { + mp_obj_dict_store(locals_dict, MP_OBJ_NEW_QSTR(var_name_qstr), code_state->state[runtime_slot]); + used_slots[runtime_slot] = true; + } + } + } + + #else + // Fallback when variable names aren't saved + // Use reverse slot assignment: local variables are numbered from highest slot down + uint16_t total_locals = code_state->n_state; + for (uint16_t order_idx = 0; order_idx < total_locals; ++order_idx) { + uint16_t reverse_slot = total_locals - 1 - order_idx; + if (code_state->state[reverse_slot] == NULL) { + continue; + } + vstr_t vstr; + qstr var_name_qstr; + vstr_init(&vstr, 16); // Initialize with enough space + vstr_printf(&vstr, "local_%d", (int)(order_idx + 1)); + var_name_qstr = qstr_from_str(vstr_str(&vstr)); + vstr_clear(&vstr); + if (var_name_qstr == MP_QSTR_NULL) { + continue; + } + mp_obj_dict_store(locals_dict, MP_OBJ_NEW_QSTR(var_name_qstr), code_state->state[reverse_slot]); + } + #endif + return MP_OBJ_FROM_PTR(locals_dict); +} + MP_DEFINE_CONST_OBJ_TYPE( mp_type_frame, MP_QSTR_frame, diff --git a/py/py.cmake b/py/py.cmake index 1c81ed4c58f32..0f8e784fba6fd 100644 --- a/py/py.cmake +++ b/py/py.cmake @@ -39,6 +39,7 @@ set(MICROPY_SOURCE_PY ${MICROPY_PY_DIR}/frozenmod.c ${MICROPY_PY_DIR}/gc.c ${MICROPY_PY_DIR}/lexer.c + ${MICROPY_PY_DIR}/localnames.c ${MICROPY_PY_DIR}/malloc.c ${MICROPY_PY_DIR}/map.c ${MICROPY_PY_DIR}/modarray.c diff --git a/py/py.mk b/py/py.mk index e352d89792bfd..5a79113c26659 100644 --- a/py/py.mk +++ b/py/py.mk @@ -141,6 +141,7 @@ PY_CORE_O_BASENAME = $(addprefix py/,\ argcheck.o \ warning.o \ profile.o \ + localnames.o \ map.o \ obj.o \ objarray.o \