Note
This is an AI-generated research report. All text and code in this report was created by an LLM (Large Language Model). For more information on how these reports are created, see the main research repository.
Python bindings for the epsilon WebAssembly runtime.
Epsilon is a pure Go WebAssembly runtime with zero dependencies, created by Google. Key features:
- Pure Go Implementation: No cgo dependencies for the core runtime, runs on any Go-supported architecture
- WebAssembly 2.0 Complete: Full specification support including SIMD (v128) instructions
- Zero External Dependencies: Self-contained implementation
- Clean API: Simple, intuitive interface for embedding WASM in Go applications
- Apache 2.0 Licensed: Permissive open-source license
| Feature | Support |
|---|---|
| WebAssembly 2.0 | β Full |
| SIMD (v128) | β Full |
| Multiple Memories | β Experimental |
| Host Functions | β Full |
| Tables | β Full |
| Globals | β Full |
| Memory Import/Export | β Full |
| Interactive REPL | β Included |
Epsilon uses a bytecode interpreter design:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Runtime β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β VM β β
β β ββββββββββββ ββββββββββββ ββββββββββββββββ β β
β β β Store β β Stack β β Call Stack β β β
β β β (funcs, β β (values) β β (frames) β β β
β β β memories,β β β β β β β
β β β tables, β β β β β β β
β β β globals) β β β β β β β
β β ββββββββββββ ββββββββββββ ββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Module Instance β β
β β - Function addresses β β
β β - Memory addresses β β
β β - Table addresses β β
β β - Global addresses β β
β β - Exports β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Python 3.8+
- Go 1.24+ (for building)
# Clone this repository
git clone <this-repo>
cd epsilon-python-wrapper
# Install in development mode
pip install -e .
# Or build the wheel
pip install build
python -m buildfrom epsilon import Runtime
# Load and run a WebAssembly module
with Runtime() as runtime:
wasm_bytes = open("add.wasm", "rb").read()
with runtime.instantiate(wasm_bytes) as module:
result = module.call("add", 5, 37)
print(result) # [42]with Runtime() as runtime:
with runtime.instantiate(wasm_bytes) as module:
# Read memory
data = module.read_memory(offset=0, length=100, memory_name="memory")
# Write memory
module.write_memory(offset=0, data=b"Hello, WASM!", memory_name="memory")
# Get memory size (in pages, 1 page = 64KB)
size = module.get_memory_size("memory")with Runtime() as runtime:
with runtime.instantiate(wasm_bytes) as module:
exports = module.get_export_names()
print(exports) # ['add', 'memory', 'global_counter', ...]with Runtime() as runtime:
with runtime.instantiate(wasm_bytes) as module:
# Call with typed arguments
result = module.call_typed("compute", [
(42, 'i32'),
(3.14, 'f64'),
(1000000, 'i64'),
])# Limit memory to 256 pages (16 MB)
with Runtime(max_memory_pages=256) as runtime:
module = runtime.instantiate(wasm_bytes)
# Or per-module
with Runtime() as runtime:
module = runtime.instantiate(wasm_bytes, max_memory_pages=128)# Set a timeout (in milliseconds)
result = module.call("slow_function", 42, timeout_ms=5000)Important limitations of the timeout mechanism:
-
Non-preemptive: Epsilon does not support context cancellation. The timeout uses a Go context wrapper, but WASM execution cannot be interrupted mid-instruction.
-
Works for returning functions: If a function completes before the timeout, results are returned normally. If it takes longer, a
EpsilonTimeoutErroris raised. -
Infinite loops: An infinite loop in WASM will NOT be interrupted by the timeout. The Go goroutine will continue running indefinitely.
Epsilon has a hardcoded call stack depth limit of 1000 frames. This protects against stack overflow from deeply nested recursion.
| Resource | Built-in Limit | Configurable |
|---|---|---|
| Memory (pages) | 32,768 max (2GB) | β Via Limits.Max |
| Call Stack | 1,000 frames | β Hardcoded |
| CPU Time | None | β Not available |
| Fuel/Gas | None | β Not available |
| Instructions | None | β Not available |
| Feature | Epsilon | wazero | wasmtime |
|---|---|---|---|
| Memory Limits | β | β | β |
| Context Cancellation | β | β | β |
| Fuel Metering | β | β | β |
| Instruction Counting | β | β | β |
| Pure Go | β | β | β (Rust) |
| CGo Free | β | β | β |
If you need strict CPU/time limits, consider these approaches:
-
Process-level limits (Linux):
import resource resource.setrlimit(resource.RLIMIT_CPU, (5, 5)) # 5 second CPU limit
-
Subprocess with timeout:
import subprocess result = subprocess.run( ["python", "run_wasm.py"], timeout=5.0, capture_output=True )
-
Multiprocessing:
from multiprocessing import Process, Queue def run_wasm(q): result = module.call("function") q.put(result) q = Queue() p = Process(target=run_wasm, args=(q,)) p.start() p.join(timeout=5.0) if p.is_alive(): p.terminate()
-
Signal-based timeout (Unix only):
import signal def timeout_handler(signum, frame): raise TimeoutError("Execution timed out") signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(5) # 5 second timeout try: result = module.call("function") finally: signal.alarm(0)
class Runtime:
PAGE_SIZE = 65536 # 64KB
MAX_PAGES = 32768 # 2GB total
def __init__(self, max_memory_pages: int = 0):
"""Create a new WebAssembly runtime.
Args:
max_memory_pages: Default max memory for modules (0 = no limit)
"""
def instantiate(self, wasm_bytes, max_memory_pages=None) -> Module:
"""Instantiate a WASM module from bytes."""
def instantiate_file(self, file_path, max_memory_pages=None) -> Module:
"""Instantiate a WASM module from a file."""
def close(self):
"""Close the runtime and free resources."""class Module:
def call(self, func_name: str, *args, timeout_ms=None) -> List[int]:
"""Call an exported function."""
def call_typed(self, func_name: str, args: List[tuple], timeout_ms=None) -> List[int]:
"""Call with typed arguments: [(value, 'i32'|'i64'|'f32'|'f64'), ...]"""
def get_export_names(self) -> List[str]:
"""Get names of all exports."""
def get_memory_size(self, memory_name="memory") -> int:
"""Get memory size in pages."""
def read_memory(self, offset, length, memory_name="memory") -> bytes:
"""Read bytes from linear memory."""
def write_memory(self, offset, data, memory_name="memory") -> int:
"""Write bytes to linear memory."""
def get_global(self, global_name: str) -> int:
"""Get the value of an exported global."""
def close(self):
"""Close the module and free resources."""class EpsilonError(Exception):
"""Base exception for epsilon errors."""
class EpsilonTimeoutError(EpsilonError):
"""Raised when execution times out."""def version() -> str:
"""Get the epsilon library version."""
def wrapper_version() -> str:
"""Get the Python wrapper version."""- Page size: 64 KiB (65,536 bytes)
- Maximum pages: 32,768 (2 GiB total)
- Limits: Configurable min/max via
MemoryType.Limits
| Type | Description | Size |
|---|---|---|
| i32 | 32-bit integer | 4 bytes |
| i64 | 64-bit integer | 8 bytes |
| f32 | 32-bit float | 4 bytes |
| f64 | 64-bit float | 8 bytes |
| v128 | 128-bit SIMD vector | 16 bytes |
| funcref | Function reference | pointer |
| externref | External reference | pointer |
| Limit | Value | Configurable |
|---|---|---|
| Call stack depth | 1,000 | No |
| Value stack | Unlimited* | No |
| Memory pages | 32,768 | Yes |
| Table size | 2^32-1 | Yes |
*Value stack is implemented as a Go slice and grows as needed
- No true timeout support: WASM execution cannot be preemptively interrupted
- No fuel metering: Cannot limit instruction count or CPU cycles
- Single-threaded: Epsilon does not support WebAssembly threads proposal
- No WASI: Epsilon is a pure runtime without WASI support
- Fixed call depth: 1000 frame limit is hardcoded
Apache 2.0 - see LICENSE
- Epsilon by Google
- Inspired by wazero-python wrapper design