Python extension to run WebAssembly binaries
Project description
Wasmer is a Python library for executing WebAssembly binaries:
- Easy to use: The
wasmer
API mimics the standard WebAssembly API, - Fast:
wasmer
executes the WebAssembly modules as fast as possible, close to native speed, - Safe: All calls to WebAssembly will be fast, but more importantly, completely safe and sandboxed.
Install
To install the wasmer
Python library, just run this command in your
shell:
$ pip install wasmer
Note: There is a limited set of wheels published so far. More are coming.
Example
There is a toy program in examples/simple.rs
, written in Rust (or
any other language that compiles to WebAssembly):
#[no_mangle]
pub extern fn sum(x: i32, y: i32) -> i32 {
x + y
}
After compilation to WebAssembly, the
examples/simple.wasm
binary file is generated. (Download
it).
Then, we can excecute it in Python:
from wasmer import Instance
wasm_bytes = open('simple.wasm', 'rb').read()
instance = Instance(wasm_bytes)
result = instance.exports.sum(5, 37)
print(result) # 42!
And then, finally, enjoy by running:
$ python examples/simple.py
API of the wasmer
extension/module
The Instance
class
Instantiates a WebAssembly module represented by bytes, and calls exported functions on it:
from wasmer import Instance
# Get the Wasm module as bytes.
wasm_bytes = open('my_program.wasm', 'rb').read()
# Instantiate the Wasm module.
instance = Instance(wasm_bytes)
# Call a function on it.
result = instance.exports.sum(1, 2)
print(result) # 3
All exported functions are accessible on the exports
getter.
Arguments of these functions are automatically casted to WebAssembly
values. If one wants to explicitely pass a value of a particular type,
it is possible to use the Value
class,
e.g. instance.exports.sum(Value.i32(1), Value.i32(2))
. Note that for
most usecases, this is not necessary.
The memory
getter exposes the Memory
class representing the memory
of that particular instance, e.g.:
view = instance.memory.uint8_view()
See below for more information.
The Module
class
Compiles a sequence of bytes into a WebAssembly module. From here, it is possible to instantiate it:
from wasmer import Module
# Get the Wasm bytes.
wasm_bytes = open('my_program.wasm', 'rb').read()
# Compile the bytes into a Wasm module.
module = Module(wasm_bytes)
# Instantiate the Wasm module.
instance = module.instantiate()
# Call a function on it.
result = instance.exports.sum(1, 2)
print(result) # 3
The Module.serialize
method and its complementary
Module.deserialize
static method help to respectively serialize and
deserialize a compiled WebAssembly module, thus saving the compilation
time for the next use:
from wasmer import Module
# Get the Wasm bytes.
wasm_bytes = open('my_program.wasm', 'rb').read()
# Compile the bytes into a Wasm module.
module1 = Module(wasm_bytes)
# Serialize the module.
serialized_module = module1.serialize()
# Let's forget about the module for this example.
del module1
# Deserialize the module.
module2 = Module.deserialize(serialized_module)
# Instantiate and use it.
result = module2.instantiate().exports.sum(1, 2)
print(result) # 3
A serialized module is a sequence of bytes. They can be saved in any storage.
The Module.validate
static method check whether the given bytes
represent valid WebAssembly bytes:
from wasmer import Module
wasm_bytes = open('my_program.wasm', 'rb').read()
if not Module.validate(wasm_bytes):
print('The program seems corrupted.')
The Value
class
Builds WebAssembly values with the correct types:
from wasmer import Value
# Integer on 32-bits.
value_i32 = Value.i32(7)
# Integer on 64-bits.
value_i64 = Value.i64(7)
# Float on 32-bits.
value_f32 = Value.f32(7.42)
# Float on 64-bits.
value_f64 = Value.f64(7.42)
The Value.[if](32|64)
static methods must be considered as static
constructors.
The __repr__
method allows to get a string representation of a
Value
instance:
print(repr(value_i32)) # I32(7)
The Memory
class
A WebAssembly instance has its own memory, represented by the Memory
class. It is accessible by the Instance.memory
getter.
The Memory.grow
method allows to grow the memory by a number of
pages (of 65kb each).
instance.memory.grow(1)
The Memory
class offers methods to create views of the memory
internal buffer, e.g. uint8_view
, int8_view
, uint16_view
etc. All these methods accept one argument: offset
, to subset the
memory buffer at a particular offset. These methods return
respectively a *Array
object, i.e. uint8_view
returns a
Uint8Array
object etc.
offset = 7
view = instance.memory.uint8_view(offset)
print(view[0])
The *Array
classes
These classes represent views over a memory buffer of an instance.
Class | View buffer as a sequence of… | Bytes per element |
---|---|---|
Int8Array |
int8 |
1 |
Uint8Array |
uint8 |
1 |
Int16Array |
int16 |
2 |
Uint16Array |
uint16 |
2 |
Int32Array |
int32 |
4 |
Uint32Array |
uint32 |
4 |
All these classes share the same implementation. Taking the example of
Uint8Array
, the class looks like this:
class Uint8Array:
@property
def bytes_per_element()
def __len__()
def __getitem__(index|slice)
def __setitem__(index, value)
Let's see it in action:
from wasmer import Instance
# Get the Wasm module as bytes.
wasm_bytes = open('my_program.wasm', 'rb').read()
# Instantiate the Wasm module.
instance = Instance(wasm_bytes)
# Call a function that returns a pointer to a string for instance.
pointer = instance.exports.return_string()
# Get the memory view, with the offset set to `pointer` (default is 0).
memory = instance.memory.uint8_view(pointer)
memory_length = len(memory)
# Read the string pointed by the pointer.
nth = 0;
string = ''
while nth < memory_length:
char = memory[nth]
if char == 0:
break
string += chr(char)
nth += 1
print(string) # Hello, World!
A slice can be used as index of the __getitem__
method, which is
useful when we already know the size of the data we want to read, e.g.:
print(''.join(map(chr, memory[0:13]))) # Hello, World!
Notice that *Array
treat bytes in little-endian, as required by the
WebAssembly specification, Chapter Structure, Section Instructions,
Sub-Section Memory
Instructions:
All values are read and written in little endian byte order.
Each view shares the same memory buffer internally. Let's have some fun:
int8 = instance.memory.int8_view()
int16 = instance.memory.int16_view()
int32 = instance.memory.int32_view()
b₁
┌┬┬┬┬┬┬┐
int8[0] = 0b00000001
b₂
┌┬┬┬┬┬┬┐
int8[1] = 0b00000100
b₃
┌┬┬┬┬┬┬┐
int8[2] = 0b00010000
b₄
┌┬┬┬┬┬┬┐
int8[3] = 0b01000000
// No surprise with the following assertions.
b₁
┌┬┬┬┬┬┬┐
assert int8[0] == 0b00000001
b₂
┌┬┬┬┬┬┬┐
assert int8[1] == 0b00000100
b₃
┌┬┬┬┬┬┬┐
assert int8[2] == 0b00010000
b₄
┌┬┬┬┬┬┬┐
assert int8[3] == 0b01000000
// The `int16` view reads 2 bytes.
b₂ b₁
┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐
assert int16[0] == 0b00000100_00000001
b₄ b₃
┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐
assert int16[1] == 0b01000000_00010000
// The `int32` view reads 4 bytes.
b₄ b₃ b₂ b₁
┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐
assert int32[0] == 0b01000000_00010000_00000100_00000001
Development
The Python extension is written in Rust, with pyo3
and
pyo3-pack
.
To set up your environment, run only once:
$ just prelude
It will install pyo3-pack
for Python and for Rust. It will also
install virtualenv
.
Then, simply run:
$ .env/bin/activate
$ just build
$ just python-run examples/simple.py
If you need to interact with Python, or run a specific file, use the following commands:
$ just python-run
$ just python-run file/to/run.py
Finally, to inspect the extension; run:
$ just inspect
(Yes, you need just
).
Testing
Once the extension is compiled and installed (just run just build
),
run the following command:
$ just test
What is WebAssembly?
Quoting the WebAssembly site:
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust, enabling deployment on the web for client and server applications.
About speed:
WebAssembly aims to execute at native speed by taking advantage of common hardware capabilities available on a wide range of platforms.
About safety:
WebAssembly describes a memory-safe, sandboxed execution environment […].
License
The entire project is under the MIT License. Please read the
LICENSE
file.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Hashes for wasmer-0.3.0-cp37-cp37m-manylinux1_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | fcfe2c7a9fbf323f3520ef9766b82e80cd433d7f8c87ff084b18bcde716923af |
|
MD5 | 53f21d0390bccc9aca2299869788b4f6 |
|
BLAKE2b-256 | ee5f93eac0a1c307ab97df5fd91cbb91c23dc464df604f1af62b520031abd8d7 |
Hashes for wasmer-0.3.0-cp37-cp37m-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 2edb87608daa3b46bd2520e0b5b90580fde9c805be4d92eeb98c22b29a21abc6 |
|
MD5 | 21c38368d7668f073f1f1303c5823194 |
|
BLAKE2b-256 | 02618cd89aec2149cf81c005532157471bb1e0c4837a2b9332e619454ec99ded |
Hashes for wasmer-0.3.0-cp36-cp36m-manylinux1_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e547b1074e52c10f0581de415b509aa61e577f5248340a68b356938393d773c8 |
|
MD5 | 83013f26426f0025aac1039de011149c |
|
BLAKE2b-256 | a789739e34035f355ab4aa0801f5cdda07ce50247d7feed38a6075b8962514ed |
Hashes for wasmer-0.3.0-cp36-cp36m-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4fe592b764fc09d535757682d0ced6da1037976a7eb97986fce3523779a89682 |
|
MD5 | b8a874746e9534928860f09c28ba4844 |
|
BLAKE2b-256 | f3d143eedf0c73e9b8af15f7382ee9dbdfac043f2424187d716f8563808346f1 |
Hashes for wasmer-0.3.0-cp35-cp35m-manylinux1_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7b5235dd4ee1cf48d054e7216a1fefe15b8b1a48ffe5e9bb2655724cf84d7a31 |
|
MD5 | f25c78c02d68e2707b488eb1dc054e83 |
|
BLAKE2b-256 | 7a8192e72fa0643a8360edf12eadaf76bc63592187e5190de2c75be807056ce2 |
Hashes for wasmer-0.3.0-cp35-cp35m-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 75d854cb5acdc32f289ceb310a72d66190fa531dd126eac970ed6788939a5d40 |
|
MD5 | a674346ea871cf33cc9622dd22fb7c37 |
|
BLAKE2b-256 | ef62c345db886f6ec90f1d9e3398ea2ed18432223a564eb040bc5e4468746687 |