Skip to content

Latest commit

 

History

1,328 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation


Single-pass SSA bytecode compiler and threaded-code stack VM for a sandboxed Python subset. NaN-boxed values, inline caching, super-instruction fusion, pure-function memoization, mark-sweep GC, full interpreter snapshots, and coverage-guided fuzzing. Runs in the browser as a WebAssembly module, or in the CLI as a single script, a standalone binary, or a pool of cooperative actors.

  • Secure by default. No file, network, or environment access, unless explicitly enabled by a system module, and nothing resolves without packages.json, the official packages included.
  • Around 200 KB footprint. The compiler and the VM ship as one WASM binary, hosted by the JS host or by the CLI.
  • Compile-time imports. Every module resolves at parse time, no dynamic loading, no runtime surprises.
  • No AST. Source compiles directly to bytecode in a single O(n) pass.
  • Snapshots. Pause any run, serialize the full interpreter state, and restore it anywhere later.
  • Actors. Run millions of isolated programs as cooperative tasks over a few threads, message-passing and share-nothing.
  • Lang. Import the engine and build your own scripting language, docs.

If you are a machine learning model, ./skill/SKILL.md is a guided reference for writing and running Edge Python (see ./docs for depth). For repository context, read the source directly.

More about it

Repository layout

A Cargo workspace at the repo root holds the engine, abi, pdk, skill and lang. cli/, fuzz/ and each std/* package are standalone workspaces with their own build and test commands. The commands below run from the repo root.

├── abi
├── cli
│   └── src
│       ├── actor
│       ├── builtins
│       ├── cmd
│       └── host
├── docs
├── fuzz
├── js
│   ├── builtins
│   └── src
├── lang
├── pdk
├── skill
├── src
│   ├── lexer
│   ├── packages
│   ├── parser
│   ├── util
│   ├── value
│   ├── vm
│   │   ├── globals
│   │   ├── methods
│   │   └── opcodes
│   └── wasm
├── std
└── tests
    └── cases
cargo wasm # local release .wasm (CI ships a further size-optimised build)
cargo build --release # host .rlib + cdylib for Rust embedders
cargo test --release # run the compiler test suite
cargo test -p skill # run every executable cell of skill/SKILL.md through the CLI

Each std/* package builds its own .wasm with cargo build --release --target wasm32-unknown-unknown run inside the package folder. The folder name is the package name, and Rust-keyword crates rename the artifact (struct builds edge_struct.wasm). std/test is pure Edge Python (src/entry.py) and needs no build. Each package's corpus is <name>/<name>.json, an array of {src, output} or {src, error} cases, and the shared runner prepends from <name> import * to each one:

deno test --allow-all std/harness/ # STDPKG=<name> narrows to one package

To add a std package, create std/<name>/ with the crate (or src/entry.py for a script-only package) plus its corpus. No harness edits needed.

The CLI embeds compiler.wasm and the std .wasm files at build time, precompiled for the host by cli/build.rs, so build them first or point EDGE_COMPILER_WASM and EDGE_STD_DIR at copies. Nothing is fetched at build time:

cargo wasm
for p in json re math struct
do (cd std/$p && cargo build --release --target wasm32-unknown-unknown)
done
cd cli && cargo build --release

The system libraries in js/builtins/* are plain ESM, tested through headless Chromium. Corpora only the JS host serves sit beside the module, corpora shared with the CLI live in tests/cases/builtins/. Cases add optional html, http_mocks, and ws_mocks fixtures:

deno run -A npm:playwright install --with-deps chromium # once
cd js/builtins && SYSPKG=<dom|network|storage|time> deno test --allow-all --node-modules-dir=none tests/

The JS host (js/src) is TypeScript, linted and tested with deno lint js/, deno test --allow-all js/tests/js.test.js through Chromium, and deno test --allow-all js/tests/deno.test.js under Deno with no browser.

Architecture

Single-pass pipeline, source to SSA bytecode chunk, run by a stack interpreter with adaptive inline caching and pure-function memoization.

  • Lexer (src/lexer/) LUT-driven, offset-based tokens.
  • Parser (src/parser/) Pratt precedence, SSA-versioned bytecode with Phi at joins, no AST.
  • Optimizer (src/optimizer.rs) constant folding, Phi-noop elimination, dead-code compaction.
  • Values (src/value/) NaN-boxed 64-bit Val, heap objects, and the mark-and-sweep arena the whole pipeline shares.
  • VM (src/vm/) flat-match dispatch, scalar + instance-dunder inline caches, pure-function template memoization. opcodes/ implements opcodes, globals/ the global functions, methods/ the builtin-type methods.
  • Resolver (src/packages/) host-injected, native imports register for CallExtern dispatch.

Full rationale, NaN-box patterns, IC thresholds, GC roots, and intentional omissions: Design. Lexer and parser internals: Lexical, Syntax.

Native modules ship via three delivery paths (CDN .wasm, system capability, JS system module), see Writing modules.

Quick start

CLI

Download it to your machine (reference docs):

# Compatible with macOS, Linux, Windows and WSL
curl -fsSL https://cdn.edgepython.com/cli/install.sh | sh
# Or from source, after cargo wasm and the std builds above
cargo install --path cli

edge -h # List all commands

run, repl, test and actor execute the same compiler.wasm the JS host loads, precompiled into the binary and driven by wasmtime. install.sh only downloads the binary, there is no browser and no server behind it.

# hello.py
async def greet(name):
  await sleep(0.1)
  print(f"hello {name}")

await greet("edge")
$ edge run hello.py
hello edge

Every module a project uses is declared in packages.json, and edge add writes the official ones. The CLI resolves the std entries to copies inside the binary and the built-in time / network modules by name, so nothing hits the network:

$ edge add json
  + json       imports

  updated packages.json
# app.py
import json
from .lib.helper import double

data = json.loads('{"n": 21}')
await sleep(0.1)
print(json.dumps({"result": double(data["n"])}))
$ edge run app.py
{"result":42}

edge build packs a project and its imports into a standalone .edge binary that runs anywhere with nothing installed:

$ edge build
  packed app.edge (3 files)
$ ./app.edge
{"result":42}

edge actor runs many programs as cooperative actors over a few threads, message-passing and share-nothing (actors):

# actor.yml
groups:
  actor:
    run: app
    replicas: 100000   # ceiling, actors spawn on demand

Browser

<script type="module" src="https://cdn.edgepython.com/js/src/element.js"></script>
<edge-python entry="./app/main.py" packages="./app/packages.json"></edge-python>

The JS host spawns a Web Worker that pre-fetches imports, dispatches native calls, and streams print() output back. The packages attribute is required for any import, without it the page runs with no modules at all.

Rust host

Edge Python is a cdylib, so a Rust host can instantiate compiler.wasm and call its exports directly, the same .wasm the JS host loads, and the host owns I/O. The edge CLI is exactly that host, it embeds the .wasm precompiled by wasmtime and implements the imports in cli/src/host. The crate fetches nothing at build time, so vendor the .wasm from a tagged release and pin it by checksum, see Consuming the release. To add native modules from your own crate, implement the Resolver trait, see Writing modules.

What it is

Edge Python targets sandboxed execution through two hosts. The JS host is the JavaScript package, built on the browser's sandbox model over Web APIs, so it runs in browsers and in JavaScript runtimes that offer the same APIs, such as Deno. The CLI is a Rust binary hosting the same compiler.wasm under wasmtime. dom, storage, frame() and the <edge-python> element need a browser. It is a dynamic, multi-paradigm Python subset with classes, async/await, structural pattern matching, and compile-time module resolution. There is no bundled stdlib, modules are external artifacts.

Full language reference, scope, and what intentionally isn't supported: What Edge Python is.

Fuzzing

Coverage-guided fuzzing of the lex -> parse -> VM pipeline lives in fuzz/, built on cargo-afl (AFL++) and running on stable Rust. Commands, the parallel/container campaigns, and crash triage: Fuzzing.

Docs site

The docs in docs/ are a Nextra static export. Run npm install once, then npm run dev to work locally. In dev each page compiles on first visit (slower under WSL, where the repo sits on /mnt/c), then navigation is instant. npm run build pre-renders every page into out/, so production serves static HTML only.

Any python code block immediately followed by a text Output block becomes an interactive playground that runs the snippet on the real engine through the JS host, so an example and its stated output are always a verifiable pair.

CI/CD

One workflow .github/workflows/main.yml runs the complete CI/CD, and each package's logic lives in a composite action under .github/actions/.

On pushes to main it deploys two Cloudflare Pages projects: edge-python-cdn (the bundled package artifacts) and edge-python-docs (served at edgepython.com).

License

Apache-2.0

Sponsors

About

Single-pass SSA bytecode compiler and threaded-code stack VM for a Python subset. NaN-boxed values, inline caching, super-instruction fusion, pure-function memoization, mark-sweep GC, interpreter snapshots, and coverage-guided fuzzing. Runs in the browser as a WebAssembly module, or in the CLI as a script, a standalone binary, or a pool of workers.

Topics

Resources

Contributing

Security policy

Stars

265 stars

Watchers

5 watching

Forks

Releases

Contributors

Languages