diff --git a/CMakeLists.txt b/CMakeLists.txt index 21578a9d..ed026786 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,11 @@ if(PK_ENABLE_OS) add_definitions(-DPK_ENABLE_OS=1) endif() +option(PK_ENABLE_PROFILER "" OFF) +if(PK_ENABLE_PROFILER) + add_definitions(-DPK_ENABLE_PROFILER=1) +endif() + option(PK_NO_EXPORT_C_API "" OFF) if(PK_NO_EXPORT_C_API) add_definitions(-DPK_NO_EXPORT_C_API) diff --git a/docs/features/debugging.md b/docs/features/debugging.md index aaae3279..526d3e25 100644 --- a/docs/features/debugging.md +++ b/docs/features/debugging.md @@ -4,7 +4,7 @@ title: Debugging --- !!! -This feature is available in `v1.4.5` or higher. +This feature is available in `v1.4.5` or higher. Set `PK_ENABLE_PROFILER` to `1` to enable this feature. !!! You can invoke `breakpoint()` in your python code to start a PDB-like session. diff --git a/docs/modules/line_profiler.md b/docs/modules/line_profiler.md index 302456a1..d0d1ea91 100644 --- a/docs/modules/line_profiler.md +++ b/docs/modules/line_profiler.md @@ -3,7 +3,9 @@ icon: package label: line_profiler --- -Line-by-line profiler for Python. +!!! +This module is optional. Set `PK_ENABLE_PROFILER` to `1` to enable it. +!!! ## Example diff --git a/include/pocketpy/config.h b/include/pocketpy/config.h index a702288c..c20b1262 100644 --- a/include/pocketpy/config.h +++ b/include/pocketpy/config.h @@ -13,6 +13,11 @@ #define PK_ENABLE_THREAD 0 #endif +// Enable `line_profiler` module and `breakpoint()` function +#ifndef PK_ENABLE_PROFILER // can be overridden by cmake +#define PK_ENABLE_PROFILER 0 +#endif + // GC min threshold #ifndef PK_GC_MIN_THRESHOLD // can be overridden by cmake #define PK_GC_MIN_THRESHOLD 32768 diff --git a/include/pocketpy/vm.h b/include/pocketpy/vm.h index ab04baa1..b6520a5c 100644 --- a/include/pocketpy/vm.h +++ b/include/pocketpy/vm.h @@ -139,8 +139,10 @@ public: void (*_ceval_on_step)(VM*, Frame*, Bytecode bc) = nullptr; +#if PK_ENABLE_PROFILER LineProfiler* _profiler = nullptr; NextBreakpoint _next_breakpoint; +#endif PrintFunc _stdout; PrintFunc _stderr; @@ -173,10 +175,12 @@ public: void _pop_frame(){ s_data.reset(callstack.top()._sp_base); callstack.pop(); - + +#if PK_ENABLE_PROFILER if(!_next_breakpoint.empty() && callstack.size()<_next_breakpoint.callstack_size){ _next_breakpoint = NextBreakpoint(); } +#endif } PyObject* py_str(PyObject* obj); diff --git a/run_tests.sh b/run_tests.sh index 6cba9243..924a3562 100644 --- a/run_tests.sh +++ b/run_tests.sh @@ -1,6 +1,6 @@ python prebuild.py SRC=$(find src/ -name "*.cpp") -clang++ -std=c++17 --coverage -O1 -stdlib=libc++ -frtti -Wfatal-errors -o main src2/main.cpp $SRC -Iinclude -DPK_ENABLE_OS=1 -DPK_DEBUG_PRECOMPILED_EXEC=1 +clang++ -std=c++17 --coverage -O1 -stdlib=libc++ -frtti -Wfatal-errors -o main src2/main.cpp $SRC -Iinclude -DPK_ENABLE_OS=1 -DPK_DEBUG_PRECOMPILED_EXEC=1 -DPK_ENABLE_PROFILER=1 python scripts/run_tests.py diff --git a/src/ceval.cpp b/src/ceval.cpp index 8eb82e00..5239197f 100644 --- a/src/ceval.cpp +++ b/src/ceval.cpp @@ -72,12 +72,17 @@ PyObject* VM::_run_top_frame(){ */ { + +#if PK_ENABLE_PROFILER #define CEVAL_STEP_CALLBACK() \ if(_ceval_on_step) _ceval_on_step(this, frame, byte); \ if(_profiler) _profiler->_step(callstack.size(), frame); \ if(!_next_breakpoint.empty()) { _next_breakpoint._step(this); } +#else +#define CEVAL_STEP_CALLBACK() \ + if(_ceval_on_step) _ceval_on_step(this, frame, byte); +#endif -#define DISPATCH_OP_CALL() { frame = top_frame(); goto __NEXT_FRAME; } __NEXT_FRAME: // cache const CodeObject* co = frame->co; @@ -639,7 +644,10 @@ __NEXT_STEP:; (byte.arg>>8) & 0xFF, // KWARGC true ); - if(_0 == PY_OP_CALL) DISPATCH_OP_CALL(); + if(_0 == PY_OP_CALL){ + frame = top_frame(); + goto __NEXT_FRAME; + } PUSH(_0); } DISPATCH(); TARGET(CALL_TP){ @@ -671,7 +679,10 @@ __NEXT_STEP:; true ); } - if(_0 == PY_OP_CALL) DISPATCH_OP_CALL(); + if(_0 == PY_OP_CALL){ + frame = top_frame(); + goto __NEXT_FRAME; + } PUSH(_0); } DISPATCH(); TARGET(RETURN_VALUE){ @@ -931,13 +942,7 @@ __NEXT_STEP:; } DISPATCH(); /*****************************************/ } - } - -#undef DISPATCH -#undef TARGET -#undef DISPATCH_OP_CALL -#undef CEVAL_STEP_CALLBACK /**********************************************************************/ PK_UNREACHABLE() }catch(HandledException){ @@ -970,6 +975,6 @@ __NEXT_STEP:; #undef DISPATCH #undef TARGET -#undef DISPATCH_OP_CALL +#undef CEVAL_STEP_CALLBACK } // namespace pkpy diff --git a/src/modules.cpp b/src/modules.cpp index 0d81b584..d329c1ab 100644 --- a/src/modules.cpp +++ b/src/modules.cpp @@ -250,6 +250,43 @@ void add_module_gc(VM* vm){ vm->bind_func<0>(mod, "collect", PK_LAMBDA(VAR(vm->heap.collect()))); } +void add_module_enum(VM* vm){ + PyObject* mod = vm->new_module("enum"); + CodeObject_ code = vm->compile(kPythonLibs__enum, "enum.py", EXEC_MODE); + vm->_exec(code, mod); + PyObject* Enum = mod->attr("Enum"); + vm->_all_types[PK_OBJ_GET(Type, Enum).index].on_end_subclass = \ + [](VM* vm, PyTypeInfo* new_ti){ + new_ti->subclass_enabled = false; // Enum class cannot be subclassed twice + NameDict& attr = new_ti->obj->attr(); + for(auto [k, v]: attr.items()){ + // wrap every attribute + std::string_view k_sv = k.sv(); + if(k_sv.empty() || k_sv[0] == '_') continue; + attr.set(k, vm->call(new_ti->obj, VAR(k_sv), v)); + } + }; +} + +void add_module___builtins(VM* vm){ + PyObject* mod = vm->new_module("__builtins"); + + vm->bind_func<1>(mod, "next", [](VM* vm, ArgsView args){ + return vm->py_next(args[0]); + }); + + vm->bind_func<1>(mod, "_enable_instance_dict", [](VM* vm, ArgsView args){ + PyObject* self = args[0]; + if(is_tagged(self)) vm->TypeError("object: tagged object cannot enable instance dict"); + if(self->is_attr_valid()) vm->RuntimeError("object: instance dict is already enabled"); + self->_enable_instance_dict(); + return vm->None; + }); +} + + +/************************************************/ +#if PK_ENABLE_PROFILER struct LineProfilerW; struct _LpGuard{ PK_ALWAYS_PASS_BY_POINTER(_LpGuard) @@ -317,40 +354,10 @@ void add_module_line_profiler(VM *vm){ PyObject* mod = vm->new_module("line_profiler"); LineProfilerW::register_class(vm, mod); } - - -void add_module_enum(VM* vm){ - PyObject* mod = vm->new_module("enum"); - CodeObject_ code = vm->compile(kPythonLibs__enum, "enum.py", EXEC_MODE); - vm->_exec(code, mod); - PyObject* Enum = mod->attr("Enum"); - vm->_all_types[PK_OBJ_GET(Type, Enum).index].on_end_subclass = \ - [](VM* vm, PyTypeInfo* new_ti){ - new_ti->subclass_enabled = false; // Enum class cannot be subclassed twice - NameDict& attr = new_ti->obj->attr(); - for(auto [k, v]: attr.items()){ - // wrap every attribute - std::string_view k_sv = k.sv(); - if(k_sv.empty() || k_sv[0] == '_') continue; - attr.set(k, vm->call(new_ti->obj, VAR(k_sv), v)); - } - }; -} - -void add_module___builtins(VM* vm){ - PyObject* mod = vm->new_module("__builtins"); - - vm->bind_func<1>(mod, "next", [](VM* vm, ArgsView args){ - return vm->py_next(args[0]); - }); - - vm->bind_func<1>(mod, "_enable_instance_dict", [](VM* vm, ArgsView args){ - PyObject* self = args[0]; - if(is_tagged(self)) vm->TypeError("object: tagged object cannot enable instance dict"); - if(self->is_attr_valid()) vm->RuntimeError("object: instance dict is already enabled"); - self->_enable_instance_dict(); - return vm->None; - }); +#else +void add_module_line_profiler(VM* vm){ + (void)vm; } +#endif } // namespace pkpy \ No newline at end of file diff --git a/src/pocketpy.cpp b/src/pocketpy.cpp index fb2846e5..bc53e99e 100644 --- a/src/pocketpy.cpp +++ b/src/pocketpy.cpp @@ -73,7 +73,9 @@ void init_builtins(VM* _vm) { // builtin functions _vm->bind_func<0>(_vm->builtins, "breakpoint", [](VM* vm, ArgsView args) { +#if PK_ENABLE_PROFILER vm->_next_breakpoint = NextBreakpoint(vm->callstack.size(), vm->top_frame()->curr_lineno(), false); +#endif return vm->None; }); diff --git a/src/vm.cpp b/src/vm.cpp index 325b9107..fb592d06 100644 --- a/src/vm.cpp +++ b/src/vm.cpp @@ -1437,6 +1437,7 @@ void NextBreakpoint::_step(VM* vm){ } void VM::_breakpoint(){ +#if PK_ENABLE_PROFILER _next_breakpoint = NextBreakpoint(); bool show_where = false; @@ -1569,6 +1570,7 @@ void VM::_breakpoint(){ continue; } } +#endif } } // namespace pkpy \ No newline at end of file diff --git a/tests/84_line_profiler.py b/tests/84_line_profiler.py index 01401c2a..fc82da72 100644 --- a/tests/84_line_profiler.py +++ b/tests/84_line_profiler.py @@ -1,4 +1,8 @@ -from line_profiler import LineProfiler +try: + from line_profiler import LineProfiler + print('[INFO] line_profiler is used') +except ImportError: + exit(0) def f2(x): a = 0