diff --git a/README.md b/README.md index baad208d..71aad9cc 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ Please see https://pocketpy.dev for details and try the following resources. + [Live Python Demo](https://pocketpy.dev/static/web/): Run Python code in your browser + [Live C Examples](https://pocketpy.github.io/examples/): Explore C-APIs in your browser + [Godot Extension](https://github.com/pocketpy/godot-pocketpy): Use pocketpy in Godot Engine ++ [VSCode Extension](https://marketplace.visualstudio.com/items?itemName=pocketpy.pocketpy): Debug and profile pocketpy scripts in VSCode ++ [Flutter Plugin](https://pub.dev/packages/pocketpy): Use pocketpy in Flutter apps ## Supported Platforms diff --git a/docs/features/debugging.md b/docs/features/debugging.md index f7383a7a..2bac8f62 100644 --- a/docs/features/debugging.md +++ b/docs/features/debugging.md @@ -1,24 +1,88 @@ --- icon: dot title: Debugging +order: 80 --- +## Install VSCode Extension + +To debug a pocketpy program, you need to install our VSCode extension first: + +https://marketplace.visualstudio.com/items?itemName=pocketpy.pocketpy + !!! -This feature is not available in `v2.0` yet. +The VSCode extension requires pocketpy version >= `2.1.1` !!! -You can invoke `breakpoint()` in your python code to start a PDB-like session. +## Create a `launch.json` file -The following commands are supported: +Navigate to the Debug view in VSCode, and click on "create a launch.json file" link. +In the dropdown menu, select "pocketpy". -+ `h, help`: show this help message -+ `q, quit`: exit the debugger -+ `n, next`: execute next line -+ `s, step`: step into -+ `w, where`: show current stack frame -+ `c, continue`: continue execution -+ `a, args`: show local variables -+ `l, list`: show lines around current line -+ `ll, longlist`: show all lines -+ `p, print `: evaluate expression -+ `!, execute statement`: execute statement +![launch_json](../static/debugger/launch_json.png) + +Then a default `launch.json` file will be created in the `.vscode` folder +with a sample pocketpy debug configuration. + +## How does it work? + +pocketpy provides a C-API `py_debugger_waitforattach`, +which starts a debug server and waits for the VSCode extension to attach. +When the debugger is attached, the program will continue to run. + ++ If you are using pocketpy's standalone executable `main.exe`, you can pass `--debug` flag to it. This will automatically call `py_debugger_waitforattach("localhost", 6110)` before running your program. ++ If you are embedding pocketpy as a library, you need to call `py_debugger_waitforattach` manually in your C/C++ code. + +## Configuration + ++ `type`: must be `pocketpy` ++ `request`: can be `attach` or `launch` ++ `name`: the name of this configuration ++ `port`: the port number of the debug server, must match the one in `py_debugger_waitforattach` ++ `host`: the host of the debug server, must match the one in `py_debugger_waitforattach` ++ `sourceFolder`: the root folder of your python source code, default to `${workspaceFolder}`. However, +sometimes you may run your program from a subfolder, in this case you need to set `sourceFolder` to the correct path. If this is not set correctly, breakpoints will not be hit. ++ `program`: (for launch mode only) the path to the executable file which calls `py_debugger_waitforattach`, e.g. the pocketpy standalone executable `main.exe`. ++ `args`: (for launch mode only) the arguments to pass to the executable file, e.g. `--debug` and the script path if you are using `main.exe`. ++ `cwd`: (for launch mode only) the working directory to launch the executable file, default to `${workspaceFolder}`. + +### For attach mode + +In this mode, you need to start your pocketpy program manually which must call `py_debugger_waitforattach` first. +After the program starts, you can let VSCode attach to the debug server. + +```json +{ + "type": "pocketpy", + "request": "attach", + "name": "Attach to pocketpy program", + "port": 6110, + "host": "localhost", + "sourceFolder": "${workspaceFolder}" +} +``` + +### For launch mode + +In this mode, VSCode will start your program with the specified `program`, `args` and `cwd`. +After the program starts, VSCode attempts to attach to the debug server automatically. + +```json +{ + "type": "pocketpy", + "request": "launch", + "name": "Launch pocketpy program", + "port": 6110, + "host": "localhost", + "sourceFolder": "${workspaceFolder}", + "program": "${workspaceFolder}/pocketpy/main.exe", + "args": [ + "--debug" + ], + "cwd": "${workspaceFolder}" +} +``` + +## Showcase + +![debugger_demo](../static/debugger/debugger_demo.png) diff --git a/docs/features/profiling.md b/docs/features/profiling.md new file mode 100644 index 00000000..4799129d --- /dev/null +++ b/docs/features/profiling.md @@ -0,0 +1,29 @@ +--- +icon: dot +title: Profiling +order: 79 +--- + +To profile your pocketpy program, you can run `main.exe` with `--profile` flag. + +For example, to profile `test/test_math.py`, run + +``` +main.exe --profile test/test_math.py +``` + +This will output a JSON report file named `profile_report.json` in the current directory, +which records the time spent for each line. To visualize the report, please install our VSCode extension. + +https://marketplace.visualstudio.com/items?itemName=pocketpy.pocketpy + +!!! +The VSCode extension requires pocketpy version >= `2.1.1` +!!! + +With pocketpy VSCode extension, press `F1` and type `pocketpy: Load Line Profiler Report`, +select **1. the `profile_report.json` file; 2. the source root of the program**. Then you will see a nice visualization of the profiling result. + +![profiler_report](../static/profiler_demo.png) + +Press `ESC` to exit the report view. diff --git a/docs/index.md b/docs/index.md index e112d063..5a342559 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,6 +13,7 @@ Developers are able to write Python bindings via C-API or pybind11 compatible in + [Live Python Demo](https://pocketpy.dev/static/web/): Run Python code in your browser + [Live C Examples](https://pocketpy.github.io/examples/): Explore C-APIs in your browser + [Godot Extension](https://github.com/pocketpy/godot-pocketpy): Use pocketpy in Godot Engine ++ [VSCode Extension](https://marketplace.visualstudio.com/items?itemName=pocketpy.pocketpy): Debug and profile pocketpy scripts in VSCode + [Flutter Plugin](https://pub.dev/packages/pocketpy): Use pocketpy in Flutter apps ## What it looks like diff --git a/docs/retype.yml b/docs/retype.yml index 89232f96..7d05eb7e 100644 --- a/docs/retype.yml +++ b/docs/retype.yml @@ -3,7 +3,7 @@ output: .retype url: https://pocketpy.dev branding: title: pocketpy - label: v2.1.0 + label: v2.1.1 logo: "./static/logo.png" favicon: "./static/logo.png" meta: diff --git a/docs/static/debugger/debugger_demo.png b/docs/static/debugger/debugger_demo.png new file mode 100644 index 00000000..e3d72827 Binary files /dev/null and b/docs/static/debugger/debugger_demo.png differ diff --git a/docs/static/debugger/launch_json.png b/docs/static/debugger/launch_json.png new file mode 100644 index 00000000..540aebbe Binary files /dev/null and b/docs/static/debugger/launch_json.png differ diff --git a/docs/static/profiler_demo.png b/docs/static/profiler_demo.png new file mode 100644 index 00000000..3bb742ad Binary files /dev/null and b/docs/static/profiler_demo.png differ diff --git a/include/pocketpy/common/smallmap.h b/include/pocketpy/common/smallmap.h index 74556202..3a007268 100644 --- a/include/pocketpy/common/smallmap.h +++ b/include/pocketpy/common/smallmap.h @@ -22,12 +22,11 @@ #define K c11_sv #define V int #define NAME c11_smallmap_v2d -#define less(a, b) (c11_sv__cmp((a), (b)) < 0) -#define equal(a, b) (c11_sv__cmp((a), (b)) == 0) +#define less(a, b) (c11_sv__cmp((a), (b)) < 0) +#define equal(a, b) c11__sveq((a), (b)) #include "pocketpy/xmacros/smallmap.h" #undef SMALLMAP_T__HEADER - #define SMALLMAP_T__HEADER #define K void* #define V py_i64 diff --git a/src/objects/namedict.c b/src/objects/namedict.c index 7e89c6df..ea37932d 100644 --- a/src/objects/namedict.c +++ b/src/objects/namedict.c @@ -5,7 +5,20 @@ #include #include -#define HASH_KEY(__k) ((uintptr_t)(__k) >> 3U) +// https://jfdube.wordpress.com/2011/10/12/hashing-strings-and-pointers-avoiding-common-pitfalls/ +uintptr_t ThomasWangInt32Hash(void* Ptr) { + // Here we think only the lower 32 bits are useful + uint32_t Value = (uint32_t)(uintptr_t)Ptr; + Value = ~Value + (Value << 15); + Value = Value ^ (Value >> 12); + Value = Value + (Value << 2); + Value = Value ^ (Value >> 4); + Value = Value * 2057; + Value = Value ^ (Value >> 16); + return Value; +} + +#define HASH_KEY(__k) ThomasWangInt32Hash(__k) #define HASH_PROBE_0(__k, ok, i) \ ok = false; \ diff --git a/src/public/py_dict.c b/src/public/py_dict.c index a3870cca..fee464db 100644 --- a/src/public/py_dict.c +++ b/src/public/py_dict.c @@ -136,16 +136,17 @@ static void Dict__set_index(Dict* self, uint32_t index, uint32_t value) { } } +// Dict__probe won't raise exception for string keys static bool Dict__probe(Dict* self, py_TValue* key, uint64_t* p_hash, uint32_t* p_idx, DictEntry** p_entry) { - py_i64 h_user; - if(!py_hash(key, &h_user)) return false; if(py_isstr(key)) { - *p_hash = (uint64_t)h_user; + *p_hash = c11_sv__hash(py_tosv(key)); } else { + py_i64 h_user; + if(!py_hash(key, &h_user)) return false; *p_hash = Dict__hash_2nd((uint64_t)h_user); } uint32_t mask = self->capacity - 1; @@ -155,13 +156,23 @@ static bool Dict__probe(Dict* self, if(idx2 == self->null_index_value) break; DictEntry* entry = c11__at(DictEntry, &self->entries, idx2); if(entry->hash == (*p_hash)) { - int res = py_equal(&entry->key, key); - if(res == 1) { - *p_idx = idx; - *p_entry = entry; - return true; + if(py_isstr(&entry->key) && py_isstr(key)) { + c11_sv lhs = py_tosv(&entry->key); + c11_sv rhs = py_tosv(key); + if(c11__sveq(lhs, rhs)) { + *p_idx = idx; + *p_entry = entry; + return true; + } + } else { + int res = py_equal(&entry->key, key); + if(res == 1) { + *p_idx = idx; + *p_entry = entry; + return true; + } + if(res == -1) return false; // error } - if(res == -1) return false; // error } // try next index idx = Dict__step(idx);