Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
WASM := target/wasm32-unknown-unknown
RUNTIME := --target wasm32-unknown-unknown -p edge-python --lib --no-default-features --features runtime --crate-type cdylib
WASM_FEATURES := --enable-bulk-memory-opt --enable-nontrapping-float-to-int --enable-sign-ext
# wasm-opt inlines a function with one caller at any size, which merges the VM's loops into one function past what V8 gives
# its full register allocator (8192 virtual registers); a big one stays apart.
WASM_INLINE := --one-caller-inline-max-function-size=1000
TSC := npm:typescript@5.9.3/tsc
TREE := $(CURDIR)/_cdn
EXE := $(if $(filter Windows_NT,$(OS)),.exe)
Expand Down Expand Up @@ -46,9 +49,9 @@ wasm-ship: wasm-small wasm-cli-opt
wasm-small: export RUSTFLAGS = -Z location-detail=none -Z fmt-debug=none -Z unstable-options -C panic=immediate-abort
wasm-small:
cargo +nightly rustc --locked --release $(RUNTIME) -Z build-std=std,panic_abort -Z build-std-features=optimize_for_size
wasm-opt -Oz --converge --generate-global-effects --strip-debug --strip-producers $(WASM_FEATURES) -o $(WASM)/release/compiler.1.wasm $(WASM)/release/compiler.wasm
wasm-opt --flatten --rereloop -Oz -Oz $(WASM_FEATURES) -o $(WASM)/release/compiler.2.wasm $(WASM)/release/compiler.1.wasm
wasm-opt -Oz --converge $(WASM_FEATURES) -o $(WASM)/release/compiler.wasm $(WASM)/release/compiler.2.wasm
wasm-opt -Oz $(WASM_INLINE) --converge --generate-global-effects --strip-debug --strip-producers $(WASM_FEATURES) -o $(WASM)/release/compiler.1.wasm $(WASM)/release/compiler.wasm
wasm-opt --flatten --rereloop -Oz -Oz $(WASM_INLINE) $(WASM_FEATURES) -o $(WASM)/release/compiler.2.wasm $(WASM)/release/compiler.1.wasm
wasm-opt -Oz $(WASM_INLINE) --converge $(WASM_FEATURES) -o $(WASM)/release/compiler.wasm $(WASM)/release/compiler.2.wasm

# A copy beside the speed build, since the bench counts the build before wasm-opt.
wasm-cli-opt: wasm-cli
Expand Down
14,645 changes: 7,527 additions & 7,118 deletions bench/.snapshot

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion cli/tests/hosts.json
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@
"main.py": "def f(n):\n if n == 0:\n raise ValueError('bottom')\n return f(n - 1)\n\nf(2000)\n"
},
"run": ["run", "main.py"],
"fails": ["ValueError: bottom", "--> main.py:3:9", "note: the same call 1999 more times", "--> main.py:6:2"]
"fails": ["RecursionError: maximum recursion depth exceeded", "--> main.py:4:5", "note: the same call 997 more times", "--> main.py:6:2"]
},
{
"given": {
Expand Down
4 changes: 2 additions & 2 deletions docs/04-reference/05-limits-and-errors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Every execution is metered, builtins included, since a builtin that scans or cop

| Limit | Value | What hitting it raises |
|---|---|---|
| Max call depth | Frames up to a sixteenth of the memory limit, 128 nested through builtins and dunders | `RecursionError` |
| Max call depth | 1000, as Python's default recursion limit, the module's frame among them, and 128 nested through builtins | `RecursionError` |
| Comparison depth of nested containers | 100 | `RecursionError` |
| Max operations | 100,000,000 | `RuntimeError` |
| Max memory | 256 MiB | `MemoryError` |
Expand All @@ -24,7 +24,7 @@ Memory and operations can be raised in three places.
- The `limits` of a group under `edge actor`, see [Actors](/docs/edge-python/platforms/actors).
- The `--memory` and `--ops` flags of `edge run` and `edge test`.

A plain call runs as a frame of the instruction loop that made it, so the depth follows the memory limit. The frames of a run may take a sixteenth of it, about eighty thousand frames of a small function under the default. A call that a builtin, a dunder method, a generator step or `eval` makes nests a loop in Rust instead. Those stop at 128 levels, which leaves the deepest of them room on the 1 MiB wasm stack of the CLI and on the stack of a browser, and an `eval` level counts as three since its loop takes three times the stack. A coroutine holds at most 768 frames suspended at once.
Python code calls 1000 deep at most, as Python's default recursion limit, and raises `RecursionError` where Python does, counting one for the module's frame, each function, method, operator dunder, property getter and generator frame, and each level of a recursion run through sections. Edge has no `sys.setrecursionlimit`. A call that a builtin, a dunder method, a generator step or `eval` makes nests a loop in Rust instead. Those stop at 128 levels, which leaves the deepest of them room on the 1 MiB wasm stack of the CLI and on the stack of a browser, and an `eval` level counts as three since its loop takes three times the stack. A coroutine holds at most 768 frames suspended at once.

The memory limit counts every byte the allocator hands the program while it runs, its objects along with the frames, caches and buffers the engine keeps for it. Nothing is estimated, though a 64-bit host counts wider pointers than `compiler.wasm` does. A number held in a container costs only its 8 bytes, so a list of numbers is far cheaper than an object per row. Garbage counts until a collection frees it, and a run that still holds more than the limit after one raises `MemoryError`. Programs sharing one wasm instance, like the actors of a pool, also share a budget of half its address space, 2 GB, and the one whose allocation passes it raises `MemoryError` while the others keep running. A plugin keeps its own memory, which the CLI lets grow to three times the limit plus 64 MB, and a call that needs more traps and raises `RuntimeError`.

Expand Down
Loading
Loading