diff --git a/crates/misc/py/examples/import/demo.rs b/crates/misc/py/examples/import/demo.rs index cbe9cae958..eb9bca3d00 100644 --- a/crates/misc/py/examples/import/demo.rs +++ b/crates/misc/py/examples/import/demo.rs @@ -2,10 +2,11 @@ extern "C" { fn callback(s: *const u8, s_len: u32) -> u32; } +static MSG: &str = "Hello, world!"; + #[no_mangle] pub extern "C" fn test() { - let msg = "Hello, world!"; unsafe { - callback(msg.as_ptr(), msg.len() as u32); + callback(MSG.as_ptr(), MSG.len() as u32); } } diff --git a/crates/misc/py/examples/import/env.py b/crates/misc/py/examples/import/env.py index 83da582a16..068cff6aec 100644 --- a/crates/misc/py/examples/import/env.py +++ b/crates/misc/py/examples/import/env.py @@ -1,10 +1,8 @@ +import demo + def callback(msg_p: 'i32', msg_len: 'i32') -> 'i32': - print('callback:', msg_p, msg_len) - -# global memory -# mv = memoryview(memory) - -# msg = bytes(mv[msg_p:(msg_p + msg_len)]).decode('utf-8') -# print(msg) + mv = memoryview(demo.memory) + msg = bytes(mv[msg_p:(msg_p + msg_len)]).decode('utf-8') + print(msg) return 42 diff --git a/docs/lang-python.md b/docs/lang-python.md index 4c2c10224d..0abb747909 100644 --- a/docs/lang-python.md +++ b/docs/lang-python.md @@ -1,3 +1,120 @@ # Using WebAssembly from Python -... more coming soon +Wasmtime can be used as a python module loader, which allows almost any +Webassembly module to be used as a python module. This guide will go over adding +Wasmtime to your project, and some provided examples of what can be done with +Webassembly modules. + +## Prerequisites + +To follow this guide, you'll need + + - Python 3.6 or newer + - The [Webassembly binary toolkit](https://github.com/WebAssembly/wabt/releases) + - The rust toolchain installer [rustup](https://rustup.rs/) + +## Getting started and simple example + +First, copy this example Webassembly text module into your project. It exports a +function for calculating the greatest common denominator of two numbers. + +```wat +{{#include ../examples/gcd.wat}} +``` + +Before we can do anything with this module, we need to convert it to the +Webassembly binary format. We can do this with the command line tools provided +by the Webassembly binary toolkit + +```bash +wat2wasm gcd.wat +``` + +This will create the binary form of the gcd module `gcd.wasm`, we'll use this +module in the following steps. + +Next, install the Wasmtime module loader, which is provided as a [python package](https://pypi.org/project/wasmtime/) +on PyPi. It can be installed as a dependency through Pip or related tools such +as Pipenv. + +```bash +pip install wasmtime +``` + +Or + +```bash +pipenv install wasmtime +``` + +After you have Wasmtime installed and you've imported `wasmtime`, you can import +Webassembly modules in your project like any other python module. + +```python +{{#include ../crates/misc/py/examples/gcd/run.py}} +``` + +This script should output + +```bash +gcd(27, 6) = 3 +``` + +If this is the output you see, congrats! You've successfully ran your first +Webassembly code in python! + +## Host interaction and memory + +In the first example, we called a function exported by a Webassembly +module. Depeding on what you need to accomplish, Webassembly modules can also +call functions from other modules and python itself. This is done through the +module imports mechanism, which allows other modules and the host environment to +provide functions, globals, and memory spaces. The following example will show +you how to use module imports and work with module linear memory. + +> Note: At the moment, the Wasmtime python module can only import functions and +> memories. + +To show how we can use functions from the host, take a look at this rust code + +```rust +{{#include ../crates/misc/py/examples/import/demo.rs}} +``` + +We have a `test` function which calls `callback`. Since it's wrapped in `extern "C"`, +this function will be dynamically linked. The Wasmtime module does this linking +automatically by importing any needed modules at runtime. If we compile this +example without any extra linker options, the result module will import +`callback` from a module called `env`, so we need to provide an implementation of +`callback` inside an `env.py` module. + +```python +{{#include ../crates/misc/py/examples/import/env.py}} +``` + +The module provides `callback` with a pointer to a string message. We use this +to index into the demo module's memory, extract the message bytes and print them +as a string. Every Webassembly module exports its main linear memory as "memory" +by default, so it's accessible as `demo.memory` in python. We wrap the memory +into a `memoryview` so we can safely access the values inside. + +Before we move on, note the type annotations on `callback`. These are necessary +for representing your function as something callable in Webassembly, since +Webassembly functions only operate on 32 and 64 bit floats and integers. When +defining functions for use by Webassembly modules, make sure the parameters and +return value are annotated appropriately as any of `'i32'`, `'i64'`, `'f32'`, or +`'f64'`. + +Before we can use `demo.rs` we need to compile it + +```bash +rustup run nightly rustc --target=wasm32-unknown-unknown --crate-type=cdylib demo.rs +``` + +We can then use it like this + +```python +{{#include ../crates/misc/py/examples/import/run.py}} +``` + +The script should print `Hello, world!` and exit.