From b3bbe3be58647d13914ec4130803a06bf74ca5c9 Mon Sep 17 00:00:00 2001 From: lightovernight <119399319+lightovernight@users.noreply.github.com> Date: Tue, 19 Aug 2025 20:59:39 +0800 Subject: [PATCH 1/8] implement `evaluate` request (#389) * implement `evaluate` request * Update main.c * Update main.c * change port to 6110 * Update main.c * simplify the workdir process and minor optimizations * implement exit event * fix bp not hit because of path format * implement output event * add `ctype.h` to `core.c` --------- Co-authored-by: blueloveTH --- src/debugger/core.c | 22 ++++++++- src/debugger/dap.c | 107 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 104 insertions(+), 25 deletions(-) diff --git a/src/debugger/core.c b/src/debugger/core.c index 021c9a07..2b512978 100644 --- a/src/debugger/core.c +++ b/src/debugger/core.c @@ -1,5 +1,6 @@ #include "pocketpy/interpreter/frame.h" #include "pocketpy/pocketpy.h" +#include #include "pocketpy/debugger/core.h" @@ -109,6 +110,7 @@ void c11_debugger_set_step_mode(C11_STEP_MODE mode) { debugger.keep_suspend = false; } + int c11_debugger_setbreakpoint(const char* filename, int lineno) { c11_debugger_breakpoint breakpoint = {.sourcename = c11_strdup(filename), .lineno = lineno}; c11_vector__push(c11_debugger_breakpoint, &debugger.breakpoints, breakpoint); @@ -134,6 +136,21 @@ int c11_debugger_reset_breakpoints_by_source(const char* sourcesname) { return debugger.breakpoints.length; } +bool c11_debugger_path_equal(const char* path1, const char* path2) { + if (path1 == NULL || path2 == NULL) return false; + while (*path1 && *path2) { + char c1 = (*path1 == '\\') ? '/' : *path1; + char c2 = (*path2 == '\\') ? '/' : *path2; + c1 = (char)tolower((unsigned char)c1); + c2 = (char)tolower((unsigned char)c2); + if (c1 != c2) return false; + path1++; + path2++; + } + return *path1 == *path2; +} + + int c11_debugger_should_pause() { if(debugger.current_event == TRACE_EVENT_POP) return false; bool should_pause = false; @@ -153,7 +170,7 @@ int c11_debugger_should_pause() { } if(debugger.step_mode == C11_STEP_CONTINUE) { c11__foreach(c11_debugger_breakpoint, &debugger.breakpoints, bp) { - if(strcmp(debugger.current_filename, bp->sourcename) == 0 && + if(c11_debugger_path_equal(debugger.current_filename, bp->sourcename) && debugger.current_line == bp->lineno) { should_pause = true; break; @@ -166,6 +183,7 @@ int c11_debugger_should_pause() { int c11_debugger_should_keep_pause(void) { return debugger.keep_suspend; } + inline static c11_sv sv_from_cstr(const char* str) { c11_sv sv = {.data = str, .size = strlen(str)}; return sv; @@ -295,7 +313,7 @@ bool c11_debugger_unfold_var(int var_id, c11_sbuf* buffer) { // 5. dump & write if(!py_json_dumps(dap_obj, 0)) { - printf("dap_obj: %s\n", py_tpname(py_typeof(dap_obj))); + // printf("dap_obj: %s\n", py_tpname(py_typeof(dap_obj))); py_printexc(); return false; } diff --git a/src/debugger/dap.c b/src/debugger/dap.c index c337de59..a09b6c5a 100644 --- a/src/debugger/dap.c +++ b/src/debugger/dap.c @@ -20,7 +20,8 @@ X(variables) \ X(threads) \ X(configurationDone) \ - X(ready) + X(ready) \ + X(evaluate) #define DECLARE_HANDLE_FN(name) void c11_dap_handle_##name(py_Ref arguments, c11_sbuf*); DAP_COMMAND_LIST(DECLARE_HANDLE_FN) @@ -125,13 +126,48 @@ void c11_dap_handle_stackTrace(py_Ref arguments, c11_sbuf* buffer) { c11_sbuf__write_char(buffer, ','); } +void c11_dap_handle_evaluate(py_Ref arguments, c11_sbuf* buffer) { + int res = py_dict_getitem_by_str(arguments, "expression"); + if(res <= 0) { + py_printexc(); + c11__abort("[DEBUGGER ERROR] no expression found in evaluate request"); + } + + // [eval, nil, expression, globals, locals] + // vectorcall would pop the above 5 items + // so we don't need to pop them manually + py_Ref py_eval = py_pushtmp(); + py_pushnil(); + py_Ref expression = py_pushtmp(); + py_assign(expression, py_retval()); + py_assign(py_eval, py_getbuiltin(py_name("eval"))); + py_newglobals(py_pushtmp()); + py_newlocals(py_pushtmp()); + bool ok = py_vectorcall(3, 0); + + char* result = NULL; + c11_sbuf__write_cstr(buffer, "\"body\":"); + if(!ok) { + result = py_formatexc(); + } else { + py_str(py_retval()); + result = c11_strdup(py_tostr(py_retval())); + } + + c11_sv result_sv = {.data = result, .size = strlen(result)}; + pk_sprintf(buffer, "{\"result\":%Q,\"variablesReference\":0}", result_sv); + PK_FREE((void*)result); + c11_sbuf__write_char(buffer, ','); +} + void c11_dap_handle_scopes(py_Ref arguments, c11_sbuf* buffer) { int res = py_dict_getitem_by_str(arguments, "frameId"); if(res <= 0) { if(res == 0) { - printf("[DEBUGGER ERROR] no frameID found\n"); + c11__abort("[DEBUGGER ERROR] no frameID found in scopes request"); } else { py_printexc(); + c11__abort("[DEBUGGER ERROR] an error occurred while parsing request frameId"); } return; } @@ -170,9 +206,9 @@ const char* c11_dap_handle_request(const char* message) { int res = py_dict_getitem_by_str(py_request, "command"); if(res == -1) { py_printexc(); - return NULL; + c11__abort("[DEBUGGER ERROR] an error occurred while parsing request"); } else if(res == 0) { - return "cannot find attribute command"; + c11__abort("[DEBUGGER ERROR] no command found in request"); } py_assign(py_command, py_retval()); const char* command = py_tostr(py_command); @@ -180,14 +216,14 @@ const char* c11_dap_handle_request(const char* message) { res = py_dict_getitem_by_str(py_request, "arguments"); if(res == -1) { py_printexc(); - return NULL; + c11__abort("[DEBUGGER ERROR] an error occurred while parsing request arguments"); } py_assign(py_arguments, py_retval()); res = py_dict_getitem_by_str(py_request, "seq"); if(res == -1) { py_printexc(); - return NULL; + c11__abort("[DEBUGGER ERROR] an error occurred while parsing request sequence number"); } int request_seq = (res == 1) ? py_toint(py_retval()) : 0; @@ -218,25 +254,47 @@ const char* c11_dap_handle_request(const char* message) { } void c11_dap_send_event(const char* event_name, const char* body_json) { - char json[256]; - int json_len = snprintf(json, - sizeof(json), - "{\"seq\":%d,\"type\":\"event\",\"event\":\"%s\",\"body\":%s}", - server.dap_next_seq++, - event_name, - body_json); - + c11_sbuf buffer; char header[64]; + c11_sbuf__ctor(&buffer); + pk_sprintf(&buffer, + "{\"seq\":%d,\"type\":\"event\",\"event\":\"%s\",\"body\":%s}", + server.dap_next_seq++, + event_name, + body_json); + c11_string* json = c11_sbuf__submit(&buffer); + int json_len = json->size; int header_len = snprintf(header, sizeof(header), "Content-Length: %d\r\n\r\n", json_len); - // printf("[DEBUGGER INFO] send event %s\n", json); c11_socket_send(server.toclient, header, header_len); - c11_socket_send(server.toclient, json, json_len); + c11_socket_send(server.toclient, json->data, json_len); + c11_string__delete(json); +} + +void c11_dap_send_output_event(const char* category, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + c11_sbuf output; + c11_sbuf__ctor(&output); + pk_vsprintf(&output, fmt, args); + va_end(args); + + c11_sbuf buffer; + c11_string* output_json = c11_sbuf__submit(&output); + c11_sv sv_output = {.data = output_json->data, .size = output_json->size}; + c11_sbuf__ctor(&buffer); + pk_sprintf(&buffer, "{\"category\":\"%s\",\"output\":%Q}", category, sv_output); + c11_string* body_json = c11_sbuf__submit(&buffer); + c11_dap_send_event("output", body_json->data); + c11_string__delete(body_json); + c11_string__delete(output_json); } void c11_dap_send_stop_event() { c11_dap_send_event("stopped", "{\"threadId\":1,\"allThreadsStopped\":true}"); } + + void c11_dap_send_exited_event(int exitCode) { char body[64]; snprintf(body, sizeof(body), "{\"exitCode\":%d}", exitCode); @@ -329,21 +387,24 @@ void c11_dap_init_server(const char* hostname, unsigned short port) { server.isclientready = false; c11_socket_bind(server.server, hostname, port); c11_socket_listen(server.server, 0); + // c11_dap_send_output_event("console", "[DEBUGGER INFO] : listen on %s:%hu\n",hostname,port); printf("[DEBUGGER INFO] : listen on %s:%hu\n", hostname, port); } void c11_dap_waitforclient(const char* hostname, unsigned short port) { server.toclient = c11_socket_accept(server.server, NULL, NULL); - printf("[DEBUGGER INFO] : connected a client\n"); } inline static void c11_dap_handle_message() { const char* message = c11_dap_read_message(); if(message == NULL) { return; } - // printf("[DEBUGGER INFO] read request %s\n", message); + // c11_dap_send_output_event("console", "[DEBUGGER LOG] : read request %s\n", message); const char* response_content = c11_dap_handle_request(message); - // if(response_content != NULL) { printf("[DEBUGGER INFO] send response %s\n", - // response_content); } + if(response_content != NULL) { + // c11_dap_send_output_event("console", + // "[DEBUGGER LOG] : send response %s\n", + // response_content); + } c11_sbuf buffer; c11_sbuf__ctor(&buffer); pk_sprintf(&buffer, "Content-Length: %d\r\n\r\n%s", strlen(response_content), response_content); @@ -365,15 +426,15 @@ void c11_dap_configure_debugger() { return; } } - printf("[DEBUGGER INFO] : configure done\n"); + // c11_dap_send_output_event("console", "[DEBUGGER INFO] : client configure done\n"); } void c11_dap_tracefunc(py_Frame* frame, enum py_TraceEvent event) { py_sys_settrace(NULL, false); C11_DEBUGGER_STATUS result = c11_debugger_on_trace(frame, event); if(result == C11_DEBUGGER_EXIT) { + // c11_dap_send_output_event("console", "[DEBUGGER INFO] : program exit\n"); c11_dap_send_exited_event(0); - printf("[DEBUGGER INFO] : program exit\n"); exit(0); } if(result != C11_DEBUGGER_SUCCESS) { @@ -409,7 +470,7 @@ void py_debugger_waitforattach(const char* hostname, unsigned short port) { c11_dap_configure_debugger(); if(!server.isconfiguredone) { c11_socket_close(server.toclient); - printf("[DEBUGGER INFO] : An clinet is ready\n"); + // c11_dap_send_output_event("console", "[DEBUGGER INFO] : An clinet is ready\n"); } } c11_socket_set_block(server.toclient, 0); From 8f7e9f5f6c6d9a016d806310510500432f7200bd Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Thu, 21 Aug 2025 17:56:09 +0800 Subject: [PATCH 2/8] Update random.c --- src/modules/random.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/random.c b/src/modules/random.c index bb9da9b8..cc6f2010 100644 --- a/src/modules/random.c +++ b/src/modules/random.c @@ -270,9 +270,9 @@ static bool Random_choices(int argc, py_Ref argv) { } py_f64 total = cum_weights[length - 1]; - if(total <= 0) { + if(total <= 1e-6) { PK_FREE(cum_weights); - return ValueError("total of weights must be greater than zero"); + return ValueError("total of weights must be greater than 1e-6"); } py_newlistn(py_retval(), k); From 5dba2cd8e63ba1da3e97dc1a72b2455b0b770563 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Fri, 22 Aug 2025 16:51:05 +0800 Subject: [PATCH 3/8] remove `is_virtual` --- include/pocketpy/objects/codeobject.h | 2 +- src/compiler/compiler.c | 11 +++-------- src/interpreter/ceval.c | 27 +++++++++++++++++---------- src/modules/dis.c | 2 +- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/include/pocketpy/objects/codeobject.h b/include/pocketpy/objects/codeobject.h index 607d1f0c..88af8a55 100644 --- a/include/pocketpy/objects/codeobject.h +++ b/include/pocketpy/objects/codeobject.h @@ -12,6 +12,7 @@ #define BC_NOARG 0 #define BC_KEEPLINE -1 +#define BC_RETURN_VIRTUAL 5 typedef enum FuncType { FuncType_UNSET, @@ -62,7 +63,6 @@ typedef struct CodeBlock { typedef struct BytecodeEx { int lineno; // line number for each bytecode - bool is_virtual; // whether this bytecode is virtual (not in source code) int iblock; // block index } BytecodeEx; diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index c9fc95ea..a1e8eba2 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -76,7 +76,6 @@ static int Ctx__prepare_loop_divert(Ctx* self, int line, bool is_break); static int Ctx__enter_block(Ctx* self, CodeBlockType type); static void Ctx__exit_block(Ctx* self); static int Ctx__emit_(Ctx* self, Opcode opcode, uint16_t arg, int line); -static int Ctx__emit_virtual(Ctx* self, Opcode opcode, uint16_t arg, int line, bool virtual); // static void Ctx__revert_last_emit_(Ctx* self); static int Ctx__emit_int(Ctx* self, int64_t value, int line); static void Ctx__patch_jump(Ctx* self, int index); @@ -1177,9 +1176,9 @@ static void Ctx__s_emit_decorators(Ctx* self, int count) { } } -static int Ctx__emit_virtual(Ctx* self, Opcode opcode, uint16_t arg, int line, bool is_virtual) { +static int Ctx__emit_(Ctx* self, Opcode opcode, uint16_t arg, int line) { Bytecode bc = {(uint8_t)opcode, arg}; - BytecodeEx bcx = {line, is_virtual, self->curr_iblock}; + BytecodeEx bcx = {line, self->curr_iblock}; c11_vector__push(Bytecode, &self->co->codes, bc); c11_vector__push(BytecodeEx, &self->co->codes_ex, bcx); int i = self->co->codes.length - 1; @@ -1188,10 +1187,6 @@ static int Ctx__emit_virtual(Ctx* self, Opcode opcode, uint16_t arg, int line, b return i; } -static int Ctx__emit_(Ctx* self, Opcode opcode, uint16_t arg, int line) { - return Ctx__emit_virtual(self, opcode, arg, line, false); -} - // static void Ctx__revert_last_emit_(Ctx* self) { // c11_vector__pop(&self->co->codes); // c11_vector__pop(&self->co->codes_ex); @@ -1512,7 +1507,7 @@ static Error* pop_context(Compiler* self) { // previously, we only do this if the last opcode is not a return // however, this is buggy...since there may be a jump to the end (out of bound) even if the last // opcode is a return - Ctx__emit_virtual(ctx(), OP_RETURN_VALUE, 1, BC_KEEPLINE, true); + Ctx__emit_(ctx(), OP_RETURN_VALUE, BC_RETURN_VIRTUAL, BC_KEEPLINE); CodeObject* co = ctx()->co; // find the last valid token diff --git a/src/interpreter/ceval.c b/src/interpreter/ceval.c index 23ce786a..27ddf526 100644 --- a/src/interpreter/ceval.c +++ b/src/interpreter/ceval.c @@ -105,13 +105,16 @@ __NEXT_STEP: byte = co_codes[frame->ip]; if(self->trace_info.func) { - SourceLocation loc = Frame__source_location(frame); - SourceLocation prev_loc = self->trace_info.prev_loc; - if(loc.lineno != prev_loc.lineno || loc.src != prev_loc.src) { - if(prev_loc.src) PK_DECREF(prev_loc.src); - PK_INCREF(loc.src); - self->trace_info.prev_loc = loc; - self->trace_info.func(frame, TRACE_EVENT_LINE); + bool is_virtual = byte.op == OP_RETURN_VALUE && byte.arg == BC_RETURN_VIRTUAL; + if(!is_virtual) { + SourceLocation loc = Frame__source_location(frame); + SourceLocation prev_loc = self->trace_info.prev_loc; + if(loc.lineno != prev_loc.lineno || loc.src != prev_loc.src) { + if(prev_loc.src) PK_DECREF(prev_loc.src); + PK_INCREF(loc.src); + self->trace_info.prev_loc = loc; + self->trace_info.func(frame, TRACE_EVENT_LINE); + } } } @@ -1219,14 +1222,15 @@ __NEXT_STEP: c11__unreachable(); __ERROR: - py_BaseException__stpush(frame, &self->curr_exception, + py_BaseException__stpush(frame, + &self->curr_exception, frame->co->src, Frame__lineno(frame), !frame->is_locals_special ? frame->co->name->data : NULL); __ERROR_RE_RAISE: do { } while(0); - + int target = Frame__prepare_jump_exception_handler(frame, &self->stack); if(target >= 0) { // 1. Exception can be handled inside the current frame @@ -1306,7 +1310,10 @@ bool pk_stack_binaryop(VM* self, py_Name op, py_Name rop) { py_Type lhs_t = rop ? TOP()->type : SECOND()->type; py_Type rhs_t = rop ? SECOND()->type : TOP()->type; - return TypeError("unsupported operand type(s) for '%s': '%t' and '%t'", pk_op2str(op), lhs_t, rhs_t); + return TypeError("unsupported operand type(s) for '%s': '%t' and '%t'", + pk_op2str(op), + lhs_t, + rhs_t); } bool py_binaryop(py_Ref lhs, py_Ref rhs, py_Name op, py_Name rop) { diff --git a/src/modules/dis.c b/src/modules/dis.c index fbcffc92..213c4805 100644 --- a/src/modules/dis.c +++ b/src/modules/dis.c @@ -44,7 +44,7 @@ static bool disassemble(CodeObject* co) { c11_sbuf__write_cstr(&ss, buf); c11_sbuf__write_cstr(&ss, pk_opname(byte.op)); - c11_sbuf__write_char(&ss, ex.is_virtual ? '*' : ' '); + c11_sbuf__write_char(&ss, ' '); int padding = 24 - strlen(pk_opname(byte.op)); for(int j = 0; j < padding; j++) c11_sbuf__write_char(&ss, ' '); From a55f62c86091d32d7bef918306fd71fa80d92d17 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Fri, 22 Aug 2025 17:08:18 +0800 Subject: [PATCH 4/8] set `def_line` for function --- scripts/98_profiler.py | 10 ++++++++++ src/compiler/compiler.c | 7 ++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/scripts/98_profiler.py b/scripts/98_profiler.py index af7c1d4a..d0554db0 100644 --- a/scripts/98_profiler.py +++ b/scripts/98_profiler.py @@ -9,3 +9,13 @@ y = 2 costly_func(2) # 3s time.sleep(1) # 1s + +def new_func(a, b, c): + x = a + b + x = x + c + return a + +def new_func2(a, b, c): + x = a + b + x = x + c + return a diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index a1e8eba2..d1c3b9d0 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -2416,6 +2416,7 @@ static Error* consume_pep695_py312(Compiler* self) { static Error* compile_function(Compiler* self, int decorators) { Error* err; + int def_line = prev()->line; consume(TK_ID); c11_sv decl_name_sv = Token__sv(prev()); int decl_index; @@ -2445,7 +2446,7 @@ static Error* compile_function(Compiler* self, int decorators) { } } - Ctx__emit_(ctx(), OP_LOAD_FUNCTION, decl_index, prev()->line); + Ctx__emit_(ctx(), OP_LOAD_FUNCTION, decl_index, def_line); Ctx__s_emit_decorators(ctx(), decorators); py_Name decl_name = py_namev(decl_name_sv); @@ -2458,9 +2459,9 @@ static Error* compile_function(Compiler* self, int decorators) { } } - Ctx__emit_(ctx(), OP_STORE_CLASS_ATTR, Ctx__add_name(ctx(), decl_name), prev()->line); + Ctx__emit_(ctx(), OP_STORE_CLASS_ATTR, Ctx__add_name(ctx(), decl_name), def_line); } else { - NameExpr* e = NameExpr__new(prev()->line, decl_name, name_scope(self)); + NameExpr* e = NameExpr__new(def_line, decl_name, name_scope(self)); vtemit_store((Expr*)e, ctx()); vtdelete((Expr*)e); } From 0ce88fa70c26a98811999a51dc1510fafa80807c Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Mon, 25 Aug 2025 20:16:11 +0800 Subject: [PATCH 5/8] backup --- src/interpreter/ceval.c | 4 +++- src/interpreter/vm.c | 4 +++- tests/71_gc_bug.py | 9 +++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 tests/71_gc_bug.py diff --git a/src/interpreter/ceval.c b/src/interpreter/ceval.c index 27ddf526..4bcac41d 100644 --- a/src/interpreter/ceval.c +++ b/src/interpreter/ceval.c @@ -4,6 +4,7 @@ #include "pocketpy/interpreter/vm.h" #include "pocketpy/common/sstream.h" #include "pocketpy/objects/codeobject.h" +#include "pocketpy/objects/exception.h" #include "pocketpy/pocketpy.h" #include "pocketpy/objects/error.h" #include @@ -820,7 +821,8 @@ __NEXT_STEP: return RES_YIELD; } else { assert(self->last_retval.type == tp_StopIteration); - py_ObjectRef value = py_getslot(&self->last_retval, 0); + BaseException* ud = py_touserdata(py_retval()); + py_ObjectRef value = &ud->args; if(py_isnil(value)) value = py_None(); *TOP() = *value; // [iter] -> [retval] DISPATCH_JUMP((int16_t)byte.arg); diff --git a/src/interpreter/vm.c b/src/interpreter/vm.c index 192896df..1cf4ad4f 100644 --- a/src/interpreter/vm.c +++ b/src/interpreter/vm.c @@ -635,7 +635,9 @@ void ManagedHeap__mark(ManagedHeap* self) { assert(p_stack->length == 0); // mark value stack - for(py_TValue* p = vm->stack.begin; p != vm->stack.end; p++) { + for(py_TValue* p = vm->stack.begin; p < vm->stack.sp; p++) { + // assert(p->type != tp_nil); + if(py_isnil(p)) continue; pk__mark_value(p); } // mark modules diff --git a/tests/71_gc_bug.py b/tests/71_gc_bug.py new file mode 100644 index 00000000..c462fcde --- /dev/null +++ b/tests/71_gc_bug.py @@ -0,0 +1,9 @@ +a=[] +import gc +gc.collect() + +# a.append(a) +print(list(globals().items())) +del a +print(list(globals().items())) +gc.collect() From 9a23eb6c9e329b7e6153c7bab2c007738f6cda9b Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Tue, 26 Aug 2025 00:32:22 +0800 Subject: [PATCH 6/8] fix a bug --- src/interpreter/vm.c | 1 - src/objects/namedict.c | 22 +++++++++++--------- src/public/py_exception.c | 11 +++++++--- tests/08_dict.py | 15 +++++++++++++- tests/71_gc_bug.py | 42 +++++++++++++++++++++++++++++++-------- 5 files changed, 69 insertions(+), 22 deletions(-) diff --git a/src/interpreter/vm.c b/src/interpreter/vm.c index 1cf4ad4f..eb5109a6 100644 --- a/src/interpreter/vm.c +++ b/src/interpreter/vm.c @@ -637,7 +637,6 @@ void ManagedHeap__mark(ManagedHeap* self) { // mark value stack for(py_TValue* p = vm->stack.begin; p < vm->stack.sp; p++) { // assert(p->type != tp_nil); - if(py_isnil(p)) continue; pk__mark_value(p); } // mark modules diff --git a/src/objects/namedict.c b/src/objects/namedict.c index 86f006f6..7e89c6df 100644 --- a/src/objects/namedict.c +++ b/src/objects/namedict.c @@ -5,27 +5,29 @@ #include #include +#define HASH_KEY(__k) ((uintptr_t)(__k) >> 3U) + #define HASH_PROBE_0(__k, ok, i) \ ok = false; \ - i = (uintptr_t)(__k)&self->mask; \ + i = HASH_KEY(__k) & self->mask; \ do { \ if(self->items[i].key == (__k)) { \ ok = true; \ break; \ } \ if(self->items[i].key == NULL) break; \ - i = (5 * i + 1) & self->mask; \ + i = (i + 1) & self->mask; \ } while(true); #define HASH_PROBE_1(__k, ok, i) \ ok = false; \ - i = (uintptr_t)(__k)&self->mask; \ + i = HASH_KEY(__k) & self->mask; \ while(self->items[i].key != NULL) { \ if(self->items[i].key == (__k)) { \ ok = true; \ break; \ } \ - i = (5 * i + 1) & self->mask; \ + i = (i + 1) & self->mask; \ } static void NameDict__set_capacity_and_alloc_items(NameDict* self, int val) { @@ -111,12 +113,12 @@ bool NameDict__del(NameDict* self, py_Name key) { self->items[i].value = *py_NIL(); self->length--; /* tidy */ - uint32_t posToRemove = i; - uint32_t posToShift = posToRemove; + uintptr_t posToRemove = i; + uintptr_t posToShift = posToRemove; while(true) { - posToShift = (5 * posToShift + 1) & self->mask; + posToShift = (posToShift + 1) & self->mask; if(self->items[posToShift].key == NULL) break; - uintptr_t hash_z = (uintptr_t)self->items[posToShift].key; + uintptr_t hash_z = HASH_KEY(self->items[posToShift].key); uintptr_t insertPos = hash_z & self->mask; bool cond1 = insertPos <= posToRemove; bool cond2 = posToRemove <= posToShift; @@ -124,6 +126,7 @@ bool NameDict__del(NameDict* self, py_Name key) { // chain wrapped around capacity (posToShift < insertPos && (cond1 || cond2))) { NameDict_KV tmp = self->items[posToRemove]; + assert(tmp.key == NULL); self->items[posToRemove] = self->items[posToShift]; self->items[posToShift] = tmp; posToRemove = posToShift; @@ -141,4 +144,5 @@ void NameDict__clear(NameDict* self) { } #undef HASH_PROBE_0 -#undef HASH_PROBE_1 \ No newline at end of file +#undef HASH_PROBE_1 +#undef HASH_KEY \ No newline at end of file diff --git a/src/public/py_exception.c b/src/public/py_exception.c index 93238296..4fb0d117 100644 --- a/src/public/py_exception.c +++ b/src/public/py_exception.c @@ -20,9 +20,14 @@ void py_BaseException__stpush(py_Frame* frame, frame_dump->lineno = lineno; frame_dump->name = func_name ? c11_string__new(func_name) : NULL; - if(py_debugger_isattached() && frame != NULL) { - py_Frame_newlocals(frame, &frame_dump->locals); - py_Frame_newglobals(frame, &frame_dump->globals); + if(py_debugger_isattached()) { + if(frame != NULL) { + py_Frame_newlocals(frame, &frame_dump->locals); + py_Frame_newglobals(frame, &frame_dump->globals); + } else { + py_newdict(&frame_dump->locals); + py_newdict(&frame_dump->globals); + } } } diff --git a/tests/08_dict.py b/tests/08_dict.py index 992603f7..48833e7b 100644 --- a/tests/08_dict.py +++ b/tests/08_dict.py @@ -165,4 +165,17 @@ for i in range(len(data)): b.append(z) if i % 3 == 0: y = b.pop() - delattr(a, y) \ No newline at end of file + delattr(a, y) + +# bug test +d = { + '__name__': '__main__', + '__package__': '', + '__path__': '__main__', + 'a': [], + 'gc': 1, +} + +del d['a'] +assert 'a' not in d +assert d['gc'] == 1 \ No newline at end of file diff --git a/tests/71_gc_bug.py b/tests/71_gc_bug.py index c462fcde..d7656516 100644 --- a/tests/71_gc_bug.py +++ b/tests/71_gc_bug.py @@ -1,9 +1,35 @@ -a=[] -import gc -gc.collect() +# a=[] +# import gc +# gc.collect() -# a.append(a) -print(list(globals().items())) -del a -print(list(globals().items())) -gc.collect() +# # a.append(a) +# print(globals().items()) +# del a +# print(list(globals().items())) +# print(globals()['gc']) +# gc.collect() + +d = object() +d.__name__ = '__main__' +d.__package__ = '' +d.__path__ = '__main__' +d.a = [] +d.gc = 1 + +print('-' * 100) +assert d.gc == 1 +del d.a + +assert not hasattr(d, 'a') +assert d.gc == 1 + +# [0, 1, 6, 7, 4, 5, 2, 3] + +# 0 __name__ [0] +# 1 __package__ [1] +# 2 nil +# 3 nil +# 4 gc [4] +# 5 nil +# 6 __path__ [2] +# 7 a [3] \ No newline at end of file From 0c897df2c678cb09c23304728e48c00ae6c9abc0 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Tue, 26 Aug 2025 00:48:58 +0800 Subject: [PATCH 7/8] backup --- src/interpreter/vmx.c | 2 +- tests/71_gc_bug.py | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/interpreter/vmx.c b/src/interpreter/vmx.c index abb00a2b..5162165d 100644 --- a/src/interpreter/vmx.c +++ b/src/interpreter/vmx.c @@ -3,7 +3,7 @@ void pk_print_stack(VM* self, py_Frame* frame, Bytecode byte) { return; - if(frame == NULL || py_isnil(self->main)) return; + if(frame == NULL || !self->main || py_isnil(self->main)) return; py_TValue* sp = self->stack.sp; c11_sbuf buf; diff --git a/tests/71_gc_bug.py b/tests/71_gc_bug.py index d7656516..a5c244e2 100644 --- a/tests/71_gc_bug.py +++ b/tests/71_gc_bug.py @@ -16,7 +16,6 @@ d.__path__ = '__main__' d.a = [] d.gc = 1 -print('-' * 100) assert d.gc == 1 del d.a @@ -32,4 +31,16 @@ assert d.gc == 1 # 4 gc [4] # 5 nil # 6 __path__ [2] -# 7 a [3] \ No newline at end of file +# 7 a [3] + +import gc +gc.collect() + +a = [] +del a +assert gc.collect() == 1 + +# a = [] +# a.append(a) +# del a +# assert gc.collect() == 1 From 054fcba7e4037207aefcc3911222026384166908 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Tue, 26 Aug 2025 00:51:55 +0800 Subject: [PATCH 8/8] fix gc bug --- src/public/values.c | 5 ++++- tests/71_gc_bug.py | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/public/values.c b/src/public/values.c index f696f85d..0c35edb0 100644 --- a/src/public/values.c +++ b/src/public/values.c @@ -47,7 +47,10 @@ void py_newellipsis(py_OutRef out) { out->is_ptr = false; } -void py_newnil(py_OutRef out) { out->type = 0; } +void py_newnil(py_OutRef out) { + out->type = tp_nil; + out->is_ptr = false; +} void py_newnativefunc(py_OutRef out, py_CFunction f) { out->type = tp_nativefunc; diff --git a/tests/71_gc_bug.py b/tests/71_gc_bug.py index a5c244e2..24cd9002 100644 --- a/tests/71_gc_bug.py +++ b/tests/71_gc_bug.py @@ -40,7 +40,7 @@ a = [] del a assert gc.collect() == 1 -# a = [] -# a.append(a) -# del a -# assert gc.collect() == 1 +a = [] +a.append(a) +del a +assert gc.collect() == 1