Implement Python language binding (#1192) (#1195)

Implement the first version of Python language binding

Co-authored-by: liang.he <liang.he@intel.com>
This commit is contained in:
Wenyong Huang 2022-05-31 16:39:46 +08:00 committed by GitHub
parent 3168ba8dcf
commit 3d34a91f0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 6276 additions and 0 deletions

160
language-bindings/python/.gitignore vendored Normal file
View File

@ -0,0 +1,160 @@
# Refer to https://github.com/github/gitignore/blob/main/Python.gitignore
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# virtual environment
Pipfile
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# VSCode settings
.vscode/

View File

@ -0,0 +1 @@
../../LICENSE

View File

@ -0,0 +1,31 @@
# wamr-python
## Installation
### Installing from the source code
Installing from local source tree is in _development mode_. The package appears to be installed but still is editable from the source tree.
```bash
$ python -m pip install -e /path/to/wamr-root/binding/python
```
## Usage
```python
import wamr.ffi as ffi
```
### Preparation
The binding will load the shared library _libiwasm.so_ from the WAMR repo. So before running the binding, you need to build the library yourself.
The default compile options are good enough.
Please be aware that `wasm_frame_xxx` and `wasm_trap_xxx` only work well when enabling `WAMR_BUILD_DUMP_CALL_STACK`.
### Examples
There is a [simple example](./samples/hello_procedural.py) to show how to use bindings. Actually, the python binding follows C-APIs. There it should be easy if be familiar with _programming with wasm-c-api_.
Unit test cases under _./tests_ could be another but more complete references.

View File

@ -0,0 +1,708 @@
# how to implement a python binding of WAMR
A python language binding of Wasm runtime allows its users to call a set of APIs of
the runtime from the python world. Those APIs maybe implemented in C, C++, or Rust.
In the WAMR case, a python binding allows APIs in `core/iwasm/include/wasm_c_api.h`
to be used in the python scripts. To achieve that, we will create two kinds
of stuff: wrappers of structured data types and wrappers of functions under the
help of _ctypes_.
Cyptes is a tool in the standard library for creating Python bindings. It
provides a low-level toolset for loading shared libraries and marshaling
data between Python and C. Other options include _cffi_, _pybind11_,
_cpython_ and so on. Because we tend to make the binding depending on least
items. The built-in module, _ctypes_, is a good choice.
## General rules to marshal
The core of the idea of a language binding is how to translate different
representations of types in different language.
### load libraries
The `ctypes` supports locating a dynamic link library in a way similar to the
compiler does.
Currently, `ctypes.LoadLibrary` supports:
- `CDLL`. Those libraries use the standard C calling conversion.
- `OleDLL` and `WinDLL`. Those libraries use the `stdcall` calling conversion on
Windows only
### fundamental datatypes
_ctypes_ provides [primitive C compatiable data types](https://docs.python.org/3/library/ctypes.html#fundamental-data-types).
Like `c_bool`, `c_byte`, `c_int`, `c_long` and so on.
> `c_int` represents the _C_ `signed int` datatype. On platforms where
> `sizeof(int) == sizeof(long)` it is an alias to `c_long`.
| c datatypes | ctypes |
| ------------------- | ----------------------- |
| bool | c_bool |
| byte_t | c_ubyte |
| char | c_char |
| float32_t | c_float |
| float64_t | c_double |
| int32_t | c_int32 |
| int64_t | c_int64 |
| intptr_t | c_void_p |
| size_t | c_size_t |
| uint8_t | c_uint8 |
| uint32_t | c_uint32 |
| void | None |
| wasm_byte_t | c_ubyte |
| wasm_externkind_t | c_uint8 |
| wasm_memory_pages_t | c_uint32 |
| wasm_mutability_t | c_bool |
| wasm_table_size_t | c_uint32 |
| wasm_valkind_t | c_uint8 |
| wasm_data_type\* | POINTER(wasm_data_type) |
- `c_void_p` only represents `void *` only
- `None` represents `void` in function parameter lists and return lists
### structured datatypes
Create a corresponding concept for every native structured data type includes
`enum`, `struct` and `union`, in the python world.
#### Enum types
For example, if there is a `enum wams_mutability_enum` in native.
```c
typedef uint8_t wams_mutability_t;
enum wams_mutability_enum {
WASM_CONST,
WASM_VAR
};
```
Use `ctypes.int`(or any integer types in ctypes) to represents its value directly.
```python
# represents enum wams_mutability_enum
wasm_mutability_t = c_uint8
WASM_CONST = 0
WASM_VAR = 1
```
> C standard only requires "Each enumerated type shall be compatible with char,
> a signed integer type, or an unsigned integer type. The choice of the integer
> type is implementation-defined, but shall be capable of representing the
> values of all the members of the enumeration.
#### Struct types
If there is a `struct wasm_byte_vec_t` in native(in C).
```c
typedef struct wasm_byte_vec_t {
size_t size;
wasm_byte_t *data;
size_t num_elems;
size_t size_of_elem;
} wasm_byte_vec_t;
```
Use `ctypes.Structure` to create its corresponding data type in python.
```python
class wasm_byte_vec_t(ctypes.Structure):
_fileds_ = [
("size", ctypes.c_size_t),
("data", ctypes.POINTER(c_ubyte)),
("num_elems", ctypes.c_size_t),
("size_of_elem", ctypes.c_size_t),
]
```
a list of `Structures`
| name |
| ----------------- |
| wasm_engine_t |
| wasm_store_t |
| wasm_limits_t |
| wasm_valtype_t |
| wasm_functype_t |
| wasm_globaltype_t |
| wasm_tabletype_t |
| wasm_memorytype_t |
| wasm_externtype_t |
| wasm_importtype_t |
| wasm_exporttype_t |
| wasm_ref_t |
| wasm_ref_t |
| wasm_frame_t |
| wasm_trap_t |
| wasm_foreign_t |
| WASMModuleCommon |
| WASMModuleCommon |
| wasm_func_t |
| wasm_global_t |
| wasm_table_t |
| wasm_memory_t |
| wasm_extern_t |
| wasm_instance_t |
not supported `struct`
- wasm_config_t
If there is an anonymous `union` in native.
```c
typedef struct wasm_val_t {
wasm_valkind_t kind;
union {
int32_t i32;
int64_t i64;
float32_t f32;
float64_t f64;
} of;
} wasm_val_t;
```
Use `ctypes.Union` to create its corresponding data type in python.
```python
class _OF(ctypes.Union):
_fields_ = [
("i32", ctypes.c_int32),
("i64", ctypes.c_int64),
("f32", ctypes.c_float),
("f64", ctypes.c_double),
]
class wasm_val_t(ctypes.Structure):
_anonymous_ = ("of",)
_fields_ = [
("kind", ctypes.c_uint8)
("of", _OF)
]
```
### wrappers of functions
Foreign functions (C functions) can be accessed as attributes of loaded shared
libraries or an instance of function prototypes. Callback functions(python
functions) can only be accessed by instantiating function prototypes.
For example,
```c
void wasm_name_new(wasm_name_t* out, size_t len, wasm_byte_t [] data);
```
Assume there are:
- `class wasm_name_t` of python represents `wasm_name_t` of C
- `libiwasm` represents loaded _libiwasm.so_
If to access a c function like an attribute,
```python
def wasm_name_new(out, len, data):
_wasm_name_new = libiwasm.wasm_name_new
_wasm_name_new.argtypes = (ctypes.POINTER(wasm_name_t), ctypes.c_size_t, ctypes.POINTER(ctypes.c_ubyte))
_wasm_name_new.restype = None
return _wasm_name_new(out, len, data)
```
Or to instantiate a function prototype,
```python
def wasm_name_new(out, len, data):
return ctypes.CFUNCTYPE(None, (ctypes.POINTER(wasm_name_t), ctypes.c_size_t, ctypes.POINTER(ctypes.c_ubyte)))(
("wasm_name_new", libiwasm), out, len, data)
```
Now it is able to create a `wasm_name_t` with `wasm_name_new()` in python.
Sometimes, need to create a python function as a callback of c.
```c
wasm_trap_t* (*wasm_func_callback_t)(wasm_val_vec_t* args, wasm_val_vec_t *results);
```
Use `cyptes.CFUNCTYPE` to create a _pointer of function_
```python
def hello(args, results):
print("hello from a callback")
wasm_func_callback_t = ctypes.CFUNCTYPE(c_size_t, POINTER(wasm_val_vec_t), POINTER(wasm_val_vec_t))
hello_callback = wasm_func_callback_t(hello)
```
or with a decorator
```python
def wasm_func_cb_decl(func):
return @ctypes.CFUNCTYPE(ctypes.POINTER(wasm_trap_t), (ctypes.POINTER(wasm_val_vec_t), ctypes.POINTER(wasm_val_vec_t)))(func)
@wasm_func_cb_decl
def hello(args, results):
print("hello from a callback")
```
### programming tips
#### `struct` and `ctypes.Structure`
There are two kinds of `cytes.Structure` in `binding.py`.
- has `__field__` definition. like `class wasm_byte_vec_t(Structure)`
- doesn't have `__field__` definition. like `class wasm_config_t(Structure)`
Since, `ctypes` will create its C world _mirror_ variable according to `__field__`
information, `wasm_config_t()` will only create a python instance without binding
to any C variable. `wasm_byte_vec_t()` will return a python instance with an internal
C variable.
That is why `pointer(wasm_config_t())` is a NULL pointer which can not be dereferenced.
#### deal with pointers
`byref()` and `pointer()` are two functions can return a pointer.
```python
x = ctypes.c_int(2)
# use pointer() to creates a new pointer instance which would later be used in Python
x_ptr = ctypes.pointer(x)
...
struct_use_pointer = Mystruct()
struct_use_pointer.ptr = x_ptr
# use byref() pass a pointer to an object to a foreign function call
func(ctypes.byref(x))
```
The main difference is that `pointer()` does a lot more work since it
constructs a real pointer object. It is faster to use `byref(`) if don't need
the pointer object in Python itself(e.g. only use it as an argument to pass
to a function).
There is no doubt that `wasm_xxx_new()` which return type is `ctypes.POINTER`
can return a pointer. Plus, the return value of `wasm_xxx_t()` can also be
used as a pointer without casting by `byref` or `pointer`.
#### array
In [ctypes document](https://docs.python.org/3/library/ctypes.html#arrays),
it states that "The recommended way to create array types is by multiplying a
data type with a positive integer". So _multiplying a data type_ should be a
better way to create arrays
```python
from ctypes import *
class POINT(Structure):
_fields_ = ("x", c_int), ("y", c_int)
# multiplying a data type
# type(TenPointsArrayType) is <class '_ctypes.PyCArrayType'>
TenPointsArrayType = POINT * 10
# Instances are created in the usual way, by calling the class:
arr = TenPointsArrayType()
arr[0] = POINT(3,2)
for pt in arr:
print(pt.x, pt.y)
```
On both sides, it is OK to assign an array to a pointer.
```c
char buf[128] = {0};
char *ptr = buf;
```
```python
binary = wasm_byte_vec_t()
binary.data = (ctypes.c_ubyte * len(wasm)).from_buffer_copy(wasm)
```
#### exceptions and traps
Interfaces of _wasm-c-api_ have their return values to represent failures.
The python binding should just keep and transfer them to callers instead of
raising any additional exception.
The python binding should raise exceptions when the python partial is failed.
#### readonly buffer
```python
with open("hello.wasm", "rb") as f:
wasm = f.read()
binary = wasm_byte_vec_t()
wasm_byte_vec_new_uninitialized(byref(binary), len(wasm))
# create a ctypes instance (byte[] in c) and copy the content
# from wasm(bytearray in python)
binary.data = (ctypes.c_ubyte * len(wasm)).from_buffer_copy(wasm)
```
in the above example, `wasm` is a python-created readable buffer. It is not
writable and needs to be copied into a ctype array.
#### variable arguments
A function with _variable arugments_ makes it hard to specify the required
argument types for the function prototype. It leaves us one way to call it
directly without any arguments type checking.
```python
libc.printf(b"Hello, an int %d, a float %f, a string %s\n", c_int(1), c_doulbe(3.14), "World!")
```
#### Use `c_bool` to represent `wasm_mutability_t `
- `True` for `WASM_CONST`
- `False` for `WASM_VALUE`
#### customize class builtins
- `__eq__` for comparation.
- `__repr__` for printing.
### bindgen.py
`bindge.py` is a tool to create WAMR python binding automatically. `binding.py`
is generated. We should avoid modification on it. Additional helpers should go
to `ffi.py`.
`bindgen.py` uses _pycparser_. Visit the AST of `core/iwasm/include/wasm_c_api.h`
created by _gcc_ and generate necessary wrappers.
```python
from pycparser import c_ast
class Visitor(c_ast.NodeVisitor):
def visit_Struct(self, node):
pass
def visit_Union(self, node):
pass
def visit_TypeDef(self, node):
pass
def visit_FuncDecl(self, node):
pass
ast = parse_file(...)
v = Visitor()
v.visit(ast)
```
Before running _bindgen.py_, the shared library _libiwasm.so_ should be generated.
```bash
$ cd /path/to/wamr/repo
$ # if it is in linux
$ pushd product-mini/platforms/linux/
$ cmake -S . -B build ..
$ cmake --build build --target iwasm
$ popd
$ cd binding/python
$ python utils/bindgen.py
```
`wasm_frame_xxx` and `wasm_trap_xxx` only work well when enabling `WAMR_BUILD_DUMP_CALL_STACK`.
```bash
$ cmake -S . -B build -DWAMR_BUILD_DUMP_CALL_STACK=1 ..
```
## OOP wrappers
Based on the above general rules, there will be corresponding python
APIs for every C API in `wasm_c_api.h` with same name. Users can do procedural
programming with those.
In next phase, we will create OOP APIs. Almost follow the
[C++ version of wasm_c_api](https://github.com/WebAssembly/wasm-c-api/blob/master/include/wasm.hh)
## A big list
| WASM Concept | Procedural APIs | OOP APIs | OOP APIs methods |
| ------------ | ------------------------------ | ---------- | ---------------- |
| XXX_vec | wasm_xxx_vec_new | | list |
| | wasm_xxx_vec_new_uninitialized | | |
| | wasm_xxx_vec_new_empty | | |
| | wasm_xxx_vec_copy | | |
| | wasm_xxx_vec_delete | | |
| valtype | wasm_valtype_new | valtype | \_\_init\_\_ |
| | wasm_valtype_delete | | \_\_del\_\_ |
| | wasm_valtype_kind | | \_\_eq\_\_ |
| | wasm_valtype_copy | | |
| | _vector methods_ | | |
| functype | wasm_functype_new | functype | |
| | wasm_functype_delete | | |
| | wasm_functype_params | | |
| | wasm_functype_results | | |
| | wasm_functype_copy | | |
| | _vector methods_ | | |
| globaltype | wasm_globaltype_new | globaltype | \_\_init\_\_ |
| | wasm_globaltype_delete | | \_\_del\_\_ |
| | wasm_globaltype_content | | \_\_eq\_\_ |
| | wasm_globaltype_mutability | | |
| | wasm_globaltype_copy | | |
| | _vector methods_ | | |
| tabletype | wasm_tabletype_new | tabletype | \_\_init\_\_ |
| | wasm_tabletype_delete | | \_\_del\_\_ |
| | wasm_tabletype_element | | \_\_eq\_\_ |
| | wasm_tabletype_limits | | |
| | wasm_tabletype_copy | | |
| | _vector methods_ | | |
| memorytype | wasm_memorytype_new | memorytype | \_\_init\_\_ |
| | wasm_memorytype_delete | | \_\_del\_\_ |
| | wasm_memorytype_limits | | \_\_eq\_\_ |
| | wasm_memorytype_copy | | |
| | _vector methods_ | | |
| externtype | wasm_externtype_as_XXX | externtype | |
| | wasm_XXX_as_externtype | | |
| | wasm_externtype_copy | | |
| | wasm_externtype_delete | | |
| | wasm_externtype_kind | | |
| | _vector methods_ | | |
| importtype | wasm_importtype_new | importtype | |
| | wasm_importtype_delete | | |
| | wasm_importtype_module | | |
| | wasm_importtype_name | | |
| | wasm_importtype_type | | |
| | wasm_importtype_copy | | |
| | _vector methods_ | | |
| exportype | wasm_exporttype_new | exporttype | |
| | wasm_exporttype_delete | | |
| | wasm_exporttype_name | | |
| | wasm_exporttype_type | | |
| | wasm_exporttype_copy | | |
| | _vector methods_ | | |
| val | wasm_val_delete | val | |
| | wasm_val_copy | | |
| | _vector methods_ | | |
| frame | wasm_frame_delete | frame | |
| | wasm_frame_instance | | |
| | wasm_frame_func_index | | |
| | wasm_frame_func_offset | | |
| | wasm_frame_module_offset | | |
| | wasm_frame_copy | | |
| | _vector methods_ | | |
| trap | wasm_trap_new | trap | |
| | wasm_trap_delete | | |
| | wasm_trap_message | | |
| | wasm_trap_origin | | |
| | wasm_trap_trace | | |
| | _vector methods_ | | |
| foreign | wasm_foreign_new | foreign | |
| | wasm_foreign_delete | | |
| | _vector methods_ | | |
| engine | wasm_engine_new | engine | |
| | wasm_engine_new_with_args\* | | |
| | wasm_engine_new_with_config | | |
| | wasm_engine_delete | | |
| store | wasm_store_new | store | |
| | wasm_store_delete | | |
| | _vector methods_ | | |
| module | wasm_module_new | module | |
| | wasm_module_delete | | |
| | wasm_module_validate | | |
| | wasm_module_imports | | |
| | wasm_module_exports | | |
| instance | wasm_instance_new | instance | |
| | wasm_instance_delete | | |
| | wasm_instance_new_with_args\* | | |
| | wasm_instance_exports | | |
| | _vector methods_ | | |
| func | wasm_func_new | func | |
| | wasm_func_new_with_env | | |
| | wasm_func_delete | | |
| | wasm_func_type | | |
| | wasm_func_call | | |
| | wasm_func_param_arity | | |
| | wasm_func_result_arity | | |
| | _vector methods_ | | |
| global | wasm_global_new | global | |
| | wasm_global_delete | | |
| | wasm_global_type | | |
| | wasm_global_get | | |
| | wasm_global_set | | |
| | _vector methods_ | | |
| table | wasm_table_new | table | |
| | wasm_table_delete | | |
| | wasm_table_type | | |
| | wasm_table_get | | |
| | wasm_table_set | | |
| | wasm_table_size | | |
| | _vector methods_ | | |
| memory | wasm_memory_new | memory | |
| | wasm_memory_delete | | |
| | wasm_memory_type | | |
| | wasm_memory_data | | |
| | wasm_memory_data_size | | |
| | wasm_memory_size | | |
| | _vector methods_ | | |
| extern | wasm_extern_delete | extern | |
| | wasm_extern_as_XXX | | |
| | wasm_XXX_as_extern | | |
| | wasm_extern_kind | | |
| | wasm_extern_type | | |
| | _vector methods_ | | |
not supported _functions_
- wasm_config_XXX
- wasm_module_deserialize
- wasm_module_serialize
- wasm_ref_XXX
- wasm_XXX_as_ref
- wasm_XXX_as_ref_const
- wasm_XXX_copy
- wasm_XXX_get_host_info
- wasm_XXX_set_host_info
## test
there will be two kinds of tests in the project
- unit test. located in `./tests`. driven by _unittest_. run by
`$ python -m unittest` or `$ make test`.
- integration test. located in `./samples`.
The whole project is under test-driven development. Every wrapper function will
have two kinds of test cases. The first kind is a positive case. It checks a
wrapper function with expected and safe arguments combinations. Its goal is the
function should work well with expected inputs. Another kind is a negative
case. It feeds unexpected arguments combinations into a wrapper function. Arguments
should include but not be limited to `None`. It ensures that the function will
gracefully handle invalid input or unexpected behaviors.
## distribution
### package
Create a python package named `wamr`. Users should import it after installation
just like any other python module.
```python
from wamr import *
```
### PyPI
Refer to [tutorial provided by PyPA](https://packaging.python.org/en/latest/tutorials/packaging-projects/).
Steps to publish WAMR Python library:
1. Creating `pyproject.toml` tells build tools (like pip and build) what is
required to build a project. An example .toml file uses _setuptools_
```toml
[build-system]
requires = [
"setuptools>=42",
"wheel"
]
build-backend = "setuptools.build_meta"
```
2. Configuring metadata tells build tools about a package (such as the name
and the version), as well as which code files to include
- Static metadata (`setup.cfg`): guaranteed to be the same every time.
It is simpler, easier to read, and avoids many common errors, like
encoding errors.
- Dynamic metadata (`setup.py`): possibly non-deterministic. Any items that
are dynamic or determined at install-time, as well as extension modules
or extensions to setuptools, need to go into setup.py.
**_Static metadata should be preferred_**. Dynamic metadata should be used
only as an escape hatch when necessary. setup.py used to be
required, but can be omitted with newer versions of setuptools and pip.
3. Including other files in the distribution
- For [source distribution](https://packaging.python.org/en/latest/glossary/#term-Source-Distribution-or-sdist):
It's usually generated using `python setup.py sdist`, providing metadata
and the essential source files needed for installing by a tool like pip,
or for generating a Built Distribution.
It includes our Python modules, pyproject.toml, metadata, README.md,
LICENSE. If you want to control what goes in this explicitly,
see [Including files in source distributions with MANIFEST.in](https://packaging.python.org/en/latest/guides/using-manifest-in/#using-manifest-in).
- For [final built distribution](https://packaging.python.org/en/latest/glossary/#term-Built-Distribution)
A Distribution format containing files and metadata that only need to be
moved to the correct location on the target system, to be installed.
e.g. `Wheel`
It will have the Python files in the discovered or listed Python packages.
If you want to control what goes here, such as to add data files,
see [Including Data Files](https://setuptools.pypa.io/en/latest/userguide/datafiles.html) from the [setuptools docs](https://setuptools.pypa.io/en/latest/index.html).
4. Generating distribution archives. These are archives that are uploaded to
the Python Package Index and can be installed by pip.
example using `setuptools`
```shell
python3 -m pip install --upgrade build
python3 -m build
```
generated files:
```shell
dist/
WAMR-package-0.0.1-py3-none-any.whl
WAMR-package-0.0.1.tar.gz
```
The `tar.gz` file is a _source archive_ whereas the `.whl file` is a
_built distribution_. Newer pip versions preferentially install built
distributions but will fall back to source archives if needed. You should
always upload a source archive and provide built archives for compatibility
reasons.
5. Uploading the distribution archives
- Register an account on https://pypi.org.
- To securely upload your project, youll need a
[PyPI API token](https://pypi.org/help/#apitoken). It can create at
[here](https://pypi.org/manage/account/#api-tokens), and the “Scope”
the setting needs to be “Entire account”.
- After registration, now twine can be used to upload the distribution packages.
```shell
# install twine
python3 -m pip install --upgrade twine
# --repository is https://pypi.org/ by default.
# You will be prompted for a username and password. For the username, use __token__. For the password, use the token value, including the pypi- prefix.
twine upload dist/*
```
after all, the python binding will be installed with
```shell
$ pip install wamr
```
PS: A example lifecycle of a python package
![python-package-lifecycle](images/python_package_life_cycle.png)
## CI
There are several parts:
- code format check.
- test. include running all unit test cases and examples.
- publish built distribution.

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View File

@ -0,0 +1,12 @@
Use a python virtual environment tool to create an environment for development. All necessary packages are in _../requirements.txt_.
python code formatter is provided by _black_.
python code linter is provided by _pylint_ and default configuration.
Unit tests are driven by _unittest_.
```bash
$ python -m unittest -v tests/test_basics.py
$ python -m unittest -v tests/test_advanced.py
```

View File

@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"

View File

@ -0,0 +1,5 @@
black
nose
pycparser
pylint

View File

@ -0,0 +1,4 @@
(module
(func $hello (import "" "hello"))
(func (export "run") (call $hello))
)

View File

@ -0,0 +1,41 @@
#!/usr/bin/env python3
#
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
import ctypes
from wamr import *
def hello_callback():
print("Calling back...")
print("> Hello World!")
def main():
print("Initializing...")
engine = Engine()
store = Store(engine)
print("Loading binary...")
print("Compiling module...")
module = Module.from_file(engine, "./hello.wasm")
print("Creating callback...")
hello = Func(store, FuncType([], []), hello_callback)
print("Instantiating module...")
instance = Instance(store, module, [hello])
print("Extracting export...")
run = instance.exports(store)["run"]
print("Calling export...")
run(store)
print("Shutting down...")
print("Done.")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
#
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
import ctypes
import wamr.ffi as ffi
WAMS_BINARY_CONTENT = (
b"\x00asm\x01\x00\x00\x00\x01\x84\x80\x80\x80\x00\x01`\x00\x00\x02\x8a\x80"
b"\x80\x80\x00\x01\x00\x05hello\x00\x00\x03\x82\x80\x80\x80\x00\x01\x00"
b"\x07\x87\x80\x80\x80\x00\x01\x03run\x00\x01\n\x8a\x80\x80\x80\x00\x01"
b"\x84\x80\x80\x80\x00\x00\x10\x00\x0b"
)
@ffi.wasm_func_cb_decl
def hello_callback(args, results):
print("Calling back...")
print("> Hello World!")
def main():
print("Initializing...")
engine = ffi.wasm_engine_new()
store = ffi.wasm_store_new(engine)
print("Loading binary...")
# for convenience, use binary content instead of open file
# with open("./hello.wasm", "rb") as f:
# wasm = f.read()
wasm = WAMS_BINARY_CONTENT
binary = ffi.wasm_byte_vec_t()
ffi.wasm_byte_vec_new_uninitialized(binary, len(wasm))
# underlying buffer is not writable
binary.data = (ctypes.c_ubyte * len(wasm)).from_buffer_copy(wasm)
print("Compiling module...")
module = ffi.wasm_module_new(store, binary)
if not module:
raise RuntimeError("Compiling module failed")
binary.data = None
ffi.wasm_byte_vec_delete(binary)
print("Creating callback...")
hello_type = ffi.wasm_functype_new_0_0()
hello_func = ffi.wasm_func_new(
store,
hello_type,
hello_callback,
)
ffi.wasm_functype_delete(hello_type)
print("Instantiating module...")
imports = ffi.wasm_extern_vec_t()
ffi.wasm_extern_vec_new((imports), 1, ffi.wasm_func_as_extern(hello_func))
instance = ffi.wasm_instance_new(store, module, imports, None)
ffi.wasm_func_delete(hello_func)
print("Extracting export...")
exports = ffi.wasm_extern_vec_t()
ffi.wasm_instance_exports(instance, exports)
run_func = ffi.wasm_extern_as_func(exports.data[0])
if not run_func:
raise RuntimeError("can not extract exported function")
ffi.wasm_instance_delete(instance)
ffi.wasm_module_delete(module)
print("Calling export...")
args = ffi.wasm_val_vec_t()
results = ffi.wasm_val_vec_t()
ffi.wasm_val_vec_new_empty(args)
ffi.wasm_val_vec_new_empty(results)
ffi.wasm_func_call(run_func, args, results)
print("Shutting down...")
ffi.wasm_store_delete(store)
ffi.wasm_engine_delete(engine)
print("Done.")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
#
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# pylint: disable=missing-class-docstring
# pylint: disable=missing-function-docstring
# pylint: disable=missing-module-docstring
from setuptools import setup, find_packages
with open("README.md") as f:
readme = f.read()
with open("LICENSE") as f:
license = f.read()
setup(
name="wamr-python",
version="0.1.0",
description="A WebAssembly runtime powered by WAMR",
long_description=readme,
author="The WAMR Project Developers",
author_email="hello@bytecodealliance.org",
url="https://github.com/bytecodealliance/wamr-python",
license=license,
packages=["wamr"],
)

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
#
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
__all__ = ["test_basic", "test_advanced"]

View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
#
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
import wamr

View File

@ -0,0 +1,525 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
#
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# pylint: disable=missing-class-docstring
# pylint: disable=missing-function-docstring
# pylint: disable=missing-module-docstring
import ctypes as c
import math
import unittest
import wamr.ffi as ffi
# It is a module likes:
# (module
# (import "mod" "g0" (global i32))
# (import "mod" "f0" (func (param f32) (result f64)))
#
# (func (export "f1") (param i32 i64))
# (global (export "g1") (mut f32) (f32.const 3.14))
# (memory (export "m1") 1 2)
# (table (export "t1") 1 funcref)
#
# (func (export "f2") (unreachable))
# )
MODULE_BINARY = (
b"\x00asm\x01\x00\x00\x00\x01\x0e\x03`\x01}\x01|`\x02\x7f~\x00`\x00"
b"\x00\x02\x14\x02\x03mod\x02g0\x03\x7f\x00\x03mod\x02f0\x00\x00\x03\x03"
b"\x02\x01\x02\x04\x04\x01p\x00\x01\x05\x04\x01\x01\x01\x02\x06\t\x01}\x01C"
b"\xc3\xf5H@\x0b\x07\x1a\x05\x02f1\x00\x01\x02g1\x03\x01\x02m1\x02\x00\x02t1"
b"\x01\x00\x02f2\x00\x02\n\x08\x02\x02\x00\x0b\x03\x00\x00\x0b"
)
# False -> True when testing with a library enabling WAMR_BUILD_DUMP_CALL_STACK flag
TEST_WITH_WAMR_BUILD_DUMP_CALL_STACK = False
@ffi.wasm_func_cb_decl
def callback(args, results):
args = ffi.dereference(args)
results = ffi.dereference(results)
arg_v = args.data[0]
result_v = ffi.wasm_f64_val(arg_v.of.f32 * 2.0)
ffi.wasm_val_copy(results.data[0], result_v)
results.num_elems = 1
print(f"\nIn callback: {arg_v} --> {result_v}\n")
@ffi.wasm_func_with_env_cb_decl
def callback_with_env(env, args, results):
# pylint: disable=unused-argument
print("summer")
class AdvancedTestSuite(unittest.TestCase):
@classmethod
def setUpClass(cls):
print("Initializing...")
cls._wasm_engine = ffi.wasm_engine_new()
cls._wasm_store = ffi.wasm_store_new(cls._wasm_engine)
def assertIsNullPointer(self, pointer):
# pylint: disable=invalid-name
if not ffi.is_null_pointer(pointer):
self.fail("not a non-null pointer")
def assertIsNotNullPointer(self, pointer):
# pylint: disable=invalid-name
if ffi.is_null_pointer(pointer):
self.fail("not a non-null pointer")
def load_binary(self, binary_string):
print("Load binary...")
binary = ffi.load_module_file(binary_string)
binary = c.pointer(binary)
self.assertIsNotNullPointer(binary)
return binary
def compile(self, binary):
print("Compile...")
module = ffi.wasm_module_new(self._wasm_store, binary)
self.assertIsNotNullPointer(module)
return module
def prepare_imports_local(self):
print("Prepare imports...")
func_type = ffi.wasm_functype_new_1_1(
ffi.wasm_valtype_new(ffi.WASM_F32),
ffi.wasm_valtype_new(ffi.WASM_F64),
)
func = ffi.wasm_func_new(self._wasm_store, func_type, callback)
self.assertIsNotNullPointer(func)
ffi.wasm_functype_delete(func_type)
glbl_type = ffi.wasm_globaltype_new(ffi.wasm_valtype_new(ffi.WASM_I32), True)
init = ffi.wasm_i32_val(1024)
glbl = ffi.wasm_global_new(self._wasm_store, glbl_type, init)
self.assertIsNotNullPointer(glbl)
ffi.wasm_globaltype_delete(glbl_type)
imports = ffi.wasm_extern_vec_t()
data = ffi.list_to_carray(
c.POINTER(ffi.wasm_extern_t),
ffi.wasm_func_as_extern(func),
ffi.wasm_global_as_extern(glbl),
)
ffi.wasm_extern_vec_new(imports, 2, data)
imports = c.pointer(imports)
self.assertIsNotNullPointer(imports)
return imports
def instantiate(self, module, imports):
print("Instantiate module...")
instance = ffi.wasm_instance_new(
self._wasm_store, module, imports, ffi.create_null_pointer(ffi.wasm_trap_t)
)
self.assertIsNotNone(instance)
self.assertIsNotNullPointer(instance)
return instance
def extract_exports(self, instance):
print("Extracting exports...")
exports = ffi.wasm_extern_vec_t()
ffi.wasm_instance_exports(instance, exports)
exports = c.pointer(exports)
self.assertIsNotNullPointer(exports)
return exports
def setUp(self):
binary = self.load_binary(MODULE_BINARY)
self.module = self.compile(binary)
self.imports = self.prepare_imports_local()
self.instance = self.instantiate(self.module, self.imports)
self.exports = self.extract_exports(self.instance)
ffi.wasm_byte_vec_delete(binary)
def tearDown(self):
if self.imports:
ffi.wasm_extern_vec_delete(self.imports)
if self.exports:
ffi.wasm_extern_vec_delete(self.exports)
ffi.wasm_instance_delete(self.instance)
ffi.wasm_module_delete(self.module)
def test_wasm_func_call_wasm(self):
export_list = ffi.wasm_vec_to_list(self.exports)
print(export_list)
func = ffi.wasm_extern_as_func(export_list[0])
self.assertIsNotNullPointer(func)
# make a call
params = ffi.wasm_val_vec_t()
data = ffi.list_to_carray(
ffi.wasm_val_t,
ffi.wasm_i32_val(1024),
ffi.wasm_i64_val(1024 * 1024),
)
ffi.wasm_val_vec_new(params, 2, data)
results = ffi.wasm_val_vec_t()
ffi.wasm_val_vec_new_empty(results)
ffi.wasm_func_call(func, params, results)
def test_wasm_func_call_native(self):
import_list = ffi.wasm_vec_to_list(self.imports)
func = ffi.wasm_extern_as_func(import_list[0])
self.assertIsNotNullPointer(func)
params = ffi.wasm_val_vec_t()
ffi.wasm_val_vec_new(
params, 1, ffi.list_to_carray(ffi.wasm_val_t, ffi.wasm_f32_val(3.14))
)
results = ffi.wasm_val_vec_t()
ffi.wasm_val_vec_new_uninitialized(results, 1)
ffi.wasm_func_call(func, params, results)
self.assertEqual(params.data[0].of.f32 * 2, results.data[0].of.f64)
def test_wasm_func_call_wrong_params(self):
export_list = ffi.wasm_vec_to_list(self.exports)
func = ffi.wasm_extern_as_func(export_list[0])
# make a call
params = ffi.wasm_val_vec_t()
ffi.wasm_val_vec_new_empty(params)
results = ffi.wasm_val_vec_t()
ffi.wasm_val_vec_new_empty(results)
trap = ffi.wasm_func_call(func, params, results)
self.assertIsNotNullPointer(trap)
def test_wasm_func_call_unlinked(self):
ft = ffi.wasm_functype_new_0_0()
func = ffi.wasm_func_new(self._wasm_store, ft, callback)
params = ffi.wasm_val_vec_t()
ffi.wasm_val_vec_new_empty(params)
results = ffi.wasm_val_vec_t()
ffi.wasm_val_vec_new_empty(results)
trap = ffi.wasm_func_call(func, params, results)
ffi.wasm_func_delete(func)
def test_wasm_global_get_wasm(self):
export_list = ffi.wasm_vec_to_list(self.exports)
glb = ffi.wasm_extern_as_global(export_list[1])
self.assertIsNotNullPointer(glb)
# access the global
val = ffi.wasm_val_t()
ffi.wasm_global_get(glb, val)
self.assertAlmostEqual(val.of.f32, 3.14, places=3)
def test_wasm_global_get_native(self):
import_list = ffi.wasm_vec_to_list(self.imports)
glb = ffi.wasm_extern_as_global(import_list[1])
self.assertIsNotNullPointer(glb)
val = ffi.wasm_val_t()
ffi.wasm_global_get(glb, val)
self.assertEqual(val.of.i32, 1024)
def test_wasm_global_get_unlinked(self):
gt = ffi.wasm_globaltype_new(ffi.wasm_valtype_new(ffi.WASM_I32), True)
init = ffi.wasm_i32_val(32)
glbl = ffi.wasm_global_new(self._wasm_store, gt, init)
val_ret = ffi.wasm_f32_val(3.14)
ffi.wasm_global_get(glbl, val_ret)
ffi.wasm_global_delete(glbl)
# val_ret wasn't touched, keep the original value
self.assertAlmostEqual(val_ret.of.f32, 3.14, 3)
def test_wasm_global_get_null_val(self):
export_list = ffi.wasm_vec_to_list(self.exports)
glb = ffi.wasm_extern_as_global(export_list[1])
ffi.wasm_global_get(glb, ffi.create_null_pointer(ffi.wasm_val_t))
def test_wasm_global_get_null_global(self):
val = ffi.wasm_val_t()
ffi.wasm_global_get(ffi.create_null_pointer(ffi.wasm_global_t), val)
def test_wasm_global_set_wasm(self):
export_list = ffi.wasm_vec_to_list(self.exports)
glb = ffi.wasm_extern_as_global(export_list[1])
self.assertIsNotNullPointer(glb)
# access the global
new_val = ffi.wasm_f32_val(math.e)
ffi.wasm_global_set(glb, new_val)
val = ffi.wasm_val_t()
ffi.wasm_global_get(glb, val)
self.assertNotEqual(val.of.f32, 3.14)
def test_wasm_global_set_native(self):
import_list = ffi.wasm_vec_to_list(self.imports)
glb = ffi.wasm_extern_as_global(import_list[1])
self.assertIsNotNullPointer(glb)
new_val = ffi.wasm_i32_val(2048)
ffi.wasm_global_set(glb, new_val)
val = ffi.wasm_val_t()
ffi.wasm_global_get(glb, val)
self.assertEqual(val, new_val)
def test_wasm_global_set_unlinked(self):
gt = ffi.wasm_globaltype_new(ffi.wasm_valtype_new(ffi.WASM_I32), True)
init = ffi.wasm_i32_val(32)
glbl = ffi.wasm_global_new(self._wasm_store, gt, init)
val_ret = ffi.wasm_f32_val(3.14)
ffi.wasm_global_set(glbl, val_ret)
ffi.wasm_global_delete(glbl)
def test_wasm_global_set_null_v(self):
export_list = ffi.wasm_vec_to_list(self.exports)
glb = ffi.wasm_extern_as_global(export_list[1])
# access the global
ffi.wasm_global_set(glb, ffi.create_null_pointer(ffi.wasm_val_t))
def test_wasm_global_set_null_global(self):
# access the global
new_val = ffi.wasm_f32_val(math.e)
ffi.wasm_global_set(ffi.create_null_pointer(ffi.wasm_global_t), new_val)
def test_wasm_table_size(self):
export_list = ffi.wasm_vec_to_list(self.exports)
tbl = ffi.wasm_extern_as_table(export_list[3])
self.assertIsNotNullPointer(tbl)
tbl_sz = ffi.wasm_table_size(tbl)
self.assertEqual(tbl_sz, 1)
def test_wasm_table_size_unlink(self):
vt = ffi.wasm_valtype_new(ffi.WASM_FUNCREF)
limits = ffi.wasm_limits_new(10, 15)
tt = ffi.wasm_tabletype_new(vt, limits)
tbl = ffi.wasm_table_new(
self._wasm_store, tt, ffi.create_null_pointer(ffi.wasm_ref_t)
)
tbl_sz = ffi.wasm_table_size(tbl)
ffi.wasm_table_delete(tbl)
def test_wasm_table_size_null_table(self):
ffi.wasm_table_size(ffi.create_null_pointer(ffi.wasm_table_t))
def test_wasm_table_get(self):
export_list = ffi.wasm_vec_to_list(self.exports)
tbl = ffi.wasm_extern_as_table(export_list[3])
self.assertIsNotNullPointer(tbl)
ref = ffi.wasm_table_get(tbl, 0)
self.assertIsNullPointer(ref)
ref = ffi.wasm_table_get(tbl, 4096)
self.assertIsNullPointer(ref)
def test_wasm_table_get_unlinked(self):
vt = ffi.wasm_valtype_new(ffi.WASM_FUNCREF)
limits = ffi.wasm_limits_new(10, 15)
tt = ffi.wasm_tabletype_new(vt, limits)
tbl = ffi.wasm_table_new(
self._wasm_store, tt, ffi.create_null_pointer(ffi.wasm_ref_t)
)
ffi.wasm_table_get(tbl, 0)
ffi.wasm_table_delete(tbl)
def test_wasm_table_get_null_table(self):
ffi.wasm_table_get(ffi.create_null_pointer(ffi.wasm_table_t), 0)
def test_wasm_table_get_out_of_bounds(self):
export_list = ffi.wasm_vec_to_list(self.exports)
tbl = ffi.wasm_extern_as_table(export_list[3])
ffi.wasm_table_get(tbl, 1_000_000_000)
def test_wasm_ref(self):
export_list = ffi.wasm_vec_to_list(self.exports)
func = ffi.wasm_extern_as_func(export_list[0])
self.assertIsNotNullPointer(func)
ref = ffi.wasm_func_as_ref(func)
self.assertIsNotNullPointer(ref)
func_from_ref = ffi.wasm_ref_as_func(ref)
self.assertEqual(
ffi.dereference(ffi.wasm_func_type(func)),
ffi.dereference(ffi.wasm_func_type(func_from_ref)),
)
def test_wasm_table_set(self):
export_list = ffi.wasm_vec_to_list(self.exports)
tbl = ffi.wasm_extern_as_table(export_list[3])
self.assertIsNotNullPointer(tbl)
func = ffi.wasm_extern_as_func(export_list[0])
ref = ffi.wasm_func_as_ref(func)
ffi.wasm_table_set(tbl, 0, ref)
ref_ret = ffi.wasm_table_get(tbl, 0)
self.assertIsNotNullPointer(ref_ret)
func_ret = ffi.wasm_ref_as_func(ref_ret)
self.assertEqual(
ffi.dereference(ffi.wasm_func_type(func)),
ffi.dereference(ffi.wasm_func_type(func_ret)),
)
def test_wasm_table_set_unlinked(self):
vt = ffi.wasm_valtype_new(ffi.WASM_FUNCREF)
limits = ffi.wasm_limits_new(10, 15)
tt = ffi.wasm_tabletype_new(vt, limits)
tbl = ffi.wasm_table_new(
self._wasm_store, tt, ffi.create_null_pointer(ffi.wasm_ref_t)
)
export_list = ffi.wasm_vec_to_list(self.exports)
func = ffi.wasm_extern_as_func(export_list[0])
ref = ffi.wasm_func_as_ref(func)
ffi.wasm_table_set(tbl, 0, ref)
ffi.wasm_table_delete(tbl)
def test_wasm_table_set_null_table(self):
export_list = ffi.wasm_vec_to_list(self.exports)
func = ffi.wasm_extern_as_func(export_list[0])
ref = ffi.wasm_func_as_ref(func)
ffi.wasm_table_set(ffi.create_null_pointer(ffi.wasm_table_t), 0, ref)
def test_wasm_table_set_null_ref(self):
export_list = ffi.wasm_vec_to_list(self.exports)
tbl = ffi.wasm_extern_as_table(export_list[3])
ffi.wasm_table_set(tbl, 0, ffi.create_null_pointer(ffi.wasm_ref_t))
def test_wasm_table_set_out_of_bounds(self):
export_list = ffi.wasm_vec_to_list(self.exports)
tbl = ffi.wasm_extern_as_table(export_list[3])
func = ffi.wasm_extern_as_func(export_list[0])
ref = ffi.wasm_func_as_ref(func)
ffi.wasm_table_set(tbl, 1_000_000_000, ref)
def test_wasm_memory_size(self):
export_list = ffi.wasm_vec_to_list(self.exports)
mem = ffi.wasm_extern_as_memory(export_list[2])
self.assertIsNotNullPointer(mem)
pg_sz = ffi.wasm_memory_size(mem)
self.assertEqual(pg_sz, 1)
def test_wasm_memory_size_unlinked(self):
limits = ffi.wasm_limits_new(10, 12)
mt = ffi.wasm_memorytype_new(limits)
mem = ffi.wasm_memory_new(self._wasm_store, mt)
ffi.wasm_memory_size(mem)
ffi.wasm_memory_delete(mem)
def test_wasm_memory_data(self):
export_list = ffi.wasm_vec_to_list(self.exports)
mem = ffi.wasm_extern_as_memory(export_list[2])
self.assertIsNotNullPointer(mem)
data_base = ffi.wasm_memory_data(mem)
self.assertIsNotNone(data_base)
def test_wasm_memory_data_unlinked(self):
limits = ffi.wasm_limits_new(10, 12)
mt = ffi.wasm_memorytype_new(limits)
mem = ffi.wasm_memory_new(self._wasm_store, mt)
ffi.wasm_memory_data(mem)
ffi.wasm_memory_delete(mem)
def test_wasm_memory_data_size(self):
export_list = ffi.wasm_vec_to_list(self.exports)
mem = ffi.wasm_extern_as_memory(export_list[2])
self.assertIsNotNullPointer(mem)
mem_sz = ffi.wasm_memory_data_size(mem)
self.assertGreater(mem_sz, 0)
def test_wasm_memory_data_size_unlinked(self):
limits = ffi.wasm_limits_new(10, 12)
mt = ffi.wasm_memorytype_new(limits)
mem = ffi.wasm_memory_new(self._wasm_store, mt)
ffi.wasm_memory_data_size(mem)
ffi.wasm_memory_delete(mem)
def test_wasm_trap(self):
export_list = ffi.wasm_vec_to_list(self.exports)
func = ffi.wasm_extern_as_func(export_list[0])
# make a call
params = ffi.wasm_val_vec_t()
ffi.wasm_val_vec_new_empty(params)
results = ffi.wasm_val_vec_t()
ffi.wasm_val_vec_new_empty(results)
trap = ffi.wasm_func_call(func, params, results)
self.assertIsNotNullPointer(trap)
message = ffi.wasm_message_t()
ffi.wasm_trap_message(trap, message)
self.assertIsNotNullPointer(c.pointer(message))
# not a function internal exception
frame = ffi.wasm_trap_origin(trap)
self.assertIsNullPointer(frame)
@unittest.skipUnless(
TEST_WITH_WAMR_BUILD_DUMP_CALL_STACK,
"need to enable WAMR_BUILD_DUMP_CALL_STACK",
)
# assertions only works if enabling WAMR_BUILD_DUMP_CALL_STACK
def test_wasm_frame(self):
export_list = ffi.wasm_vec_to_list(self.exports)
func = ffi.wasm_extern_as_func(export_list[4])
# make a call
params = ffi.wasm_val_vec_t()
ffi.wasm_val_vec_new_empty(params)
results = ffi.wasm_val_vec_t()
ffi.wasm_val_vec_new_empty(results)
print("Making a call...")
trap = ffi.wasm_func_call(func, params, results)
message = ffi.wasm_message_t()
ffi.wasm_trap_message(trap, message)
self.assertIsNotNullPointer(c.pointer(message))
print(message)
frame = ffi.wasm_trap_origin(trap)
self.assertIsNotNullPointer(frame)
print(ffi.dereference(frame))
traces = ffi.wasm_frame_vec_t()
ffi.wasm_trap_trace(trap, traces)
self.assertIsNotNullPointer(c.pointer(frame))
instance = ffi.wasm_frame_instance(frame)
self.assertIsNotNullPointer(instance)
module_offset = ffi.wasm_frame_module_offset(frame)
func_index = ffi.wasm_frame_func_index(frame)
self.assertEqual(func_index, 2)
func_offset = ffi.wasm_frame_func_offset(frame)
self.assertGreater(func_offset, 0)
@classmethod
def tearDownClass(cls):
print("Shutting down...")
ffi.wasm_store_delete(cls._wasm_store)
ffi.wasm_engine_delete(cls._wasm_engine)
if __name__ == "__main__":
unittest.main()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,386 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
#
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# pylint: disable=missing-class-docstring
# pylint: disable=missing-function-docstring
# pylint: disable=missing-module-docstring
"""
- Need to run *download_wamr.py* firstly.
- Parse *./wasm-micro-runtime/core/iwasm/include/wasm_c_api.h* and generate
*wamr/binding.py*
"""
import os
import pathlib
import shutil
import sys
from pycparser import c_ast, parse_file
WASM_C_API_HEADER = "core/iwasm/include/wasm_c_api.h"
BINDING_PATH = "wamr/binding.py"
# 4 spaces as default indent
INDENT = " "
IGNORE_SYMOLS = (
"wasm_engine_new_with_args",
"wasm_valkind_is_num",
"wasm_valkind_is_ref",
"wasm_valtype_is_num",
"wasm_valtype_is_ref",
"wasm_valtype_new_i32",
"wasm_valtype_new_i64",
"wasm_valtype_new_f32",
"wasm_valtype_new_f64",
"wasm_valtype_new_anyref",
"wasm_valtype_new_funcref",
"wasm_functype_new_0_0",
"wasm_functype_new_0_0",
"wasm_functype_new_1_0",
"wasm_functype_new_2_0",
"wasm_functype_new_3_0",
"wasm_functype_new_0_1",
"wasm_functype_new_1_1",
"wasm_functype_new_2_1",
"wasm_functype_new_3_1",
"wasm_functype_new_0_2",
"wasm_functype_new_1_2",
"wasm_functype_new_2_2",
"wasm_functype_new_3_2",
"wasm_val_init_ptr",
"wasm_val_ptr",
"wasm_val_t",
"wasm_ref_t",
"wasm_name_new_from_string",
"wasm_name_new_from_string_nt",
)
class Visitor(c_ast.NodeVisitor):
def __init__(self):
self.type_map = {
"_Bool": "c_bool",
"byte_t": "c_ubyte",
"char": "c_char",
"errno_t": "c_int",
"int": "c_int",
"long": "c_long",
"size_t": "c_size_t",
"uint32_t": "c_uint32",
"uint8_t": "c_uint8",
"void": "None",
}
self.ret = (
"# -*- coding: utf-8 -*-\n"
"#!/usr/bin/env python3\n"
"#\n"
"# Copyright (C) 2019 Intel Corporation. All rights reserved.\n"
"# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception\n"
"#\n"
"#It is a generated file. DO NOT EDIT.\n"
"#\n"
"from ctypes import *\n"
"\n"
"from .ffi import dereference, libiwasm, wasm_ref_t, wasm_val_t\n"
"\n"
"\n"
)
def get_type_name(self, c_type):
if isinstance(c_type, c_ast.TypeDecl):
return self.get_type_name(c_type.type)
elif isinstance(c_type, c_ast.PtrDecl):
pointed_type = self.get_type_name(c_type.type)
if isinstance(c_type.type, c_ast.FuncDecl):
# CFUCNTYPE is a pointer of function
return pointed_type
if "None" == pointed_type:
return "c_void_p"
return f"POINTER({pointed_type})"
elif isinstance(c_type, c_ast.ArrayDecl):
return f"POINTER({self.get_type_name(c_type.type)})"
elif isinstance(c_type, c_ast.IdentifierType):
if len(c_type.names) > 1:
raise RuntimeError(f"unexpected type with a long names: {c_type}")
type_name = c_type.names[0]
if type_name.startswith("wasm_"):
return type_name
if not type_name in self.type_map:
raise RuntimeError(f"a new type should be in type_map: {type_name}")
return self.type_map.get(type_name)
elif isinstance(c_type, c_ast.Union):
if not c_type.name:
raise RuntimeError(f"found an anonymous union {c_type}")
return c_type.name
elif isinstance(c_type, c_ast.Struct):
if not c_type.name:
raise RuntimeError(f"found an anonymous union {c_type}")
return c_type.name
elif isinstance(c_type, c_ast.FuncDecl):
content = "CFUNCTYPE("
if isinstance(c_type.type, c_ast.PtrDecl):
# there is a bug in CFUNCTYPE if the result type is a pointer
content += "c_void_p"
else:
content += f"{self.get_type_name(c_type.type)}"
content += f",{self.get_type_name(c_type.args)}" if c_type.args else ""
content += ")"
return content
elif isinstance(c_type, c_ast.Decl):
return self.get_type_name(c_type.type)
elif isinstance(c_type, c_ast.ParamList):
content = ",".join(
[self.get_type_name(param.type) for param in c_type.params]
)
return content
else:
raise RuntimeError(f"unexpected type: {c_type.show()}")
def visit_Struct(self, node):
# pylint: disable=invalid-name
def gen_fields(info, indent):
content = ""
for k, v in info.items():
content += f'{indent}("{k}", {v}),\n'
return content[:-1]
def gen_equal(info, indent):
content = f"{indent}return"
for k, v in info.items():
# not compare pointer value in __eq__
if v.startswith("POINTER") or v.startswith("c_void_p"):
continue
content += f" self.{k} == other.{k} and"
return content[:-4]
def gen_repr(info, indent):
content = f'{indent}return f"{{{{'
for k, _ in info.items():
content += f"{k}={{self.{k}}}, "
content = content[:-2] + '}}"'
return content
def gen_vector_repr(info, indent):
content = f'{indent}ret = ""\n'
content += f"{indent}for i in range(self.num_elems):\n"
if 1 == info["data"].count("POINTER"):
# pointer
content += f"{2*indent}ret += str(self.data[i])\n"
else:
# pointer of pointer
content += f"{2*indent}ret += str(dereference(self.data[i]))\n"
content += f'{2*indent}ret += " "\n'
content += f"{indent}return ret\n"
return content
if not node.name or not node.name.lower().startswith("wasm"):
return
if node.name in IGNORE_SYMOLS:
return
name = node.name
info = {}
if node.decls:
for decl in node.decls:
info[decl.name] = self.get_type_name(decl.type)
if info:
self.ret += (
f"class {name}(Structure):\n"
f"{INDENT}_fields_ = [\n"
f"{gen_fields(info, INDENT*2)}\n"
f"{INDENT}]\n"
f"\n"
f"{INDENT}def __eq__(self, other):\n"
f"{INDENT*2}if not isinstance(other, {name}):\n"
f"{INDENT*3}return False\n"
f"{gen_equal(info, INDENT*2)}\n"
f"\n"
f"{INDENT}def __repr__(self):\n"
)
self.ret += (
f"{gen_vector_repr(info, INDENT*2)}\n"
if name.endswith("_vec_t")
else f"{gen_repr(info, INDENT*2)}\n"
)
self.ret += "\n"
else:
self.ret += f"class {name}(Structure):\n{INDENT}pass\n"
self.ret += "\n"
def visit_Union(self, node):
# pylint: disable=invalid-name
print(f"Union: {node.show()}")
def visit_Typedef(self, node):
# pylint: disable=invalid-name
# system defined
if not node.name:
return
if not node.name.startswith("wasm_"):
return
if node.name in IGNORE_SYMOLS:
return
self.visit(node.type)
if node.name == self.get_type_name(node.type):
return
else:
self.ret += f"{node.name} = {self.get_type_name(node.type)}\n"
self.ret += "\n"
def visit_FuncDecl(self, node):
# pylint: disable=invalid-name
restype = self.get_type_name(node.type)
if isinstance(node.type, c_ast.TypeDecl):
func_name = node.type.declname
elif isinstance(node.type, c_ast.PtrDecl):
func_name = node.type.type.declname
else:
raise RuntimeError(f"unexpected type in FuncDecl: {type}")
if not func_name.startswith("wasm_") or func_name.endswith("_t"):
return
if func_name in IGNORE_SYMOLS:
return
params_len = 0
for arg in node.args.params:
# ignore void but not void*
if isinstance(arg.type, c_ast.TypeDecl):
type_name = self.get_type_name(arg.type)
if "None" == type_name:
continue
params_len += 1
args = (
"" if not params_len else ",".join([f"arg{i}" for i in range(params_len)])
)
argtypes = f"[{self.get_type_name(node.args)}]" if params_len else "None"
self.ret += (
f"def {func_name}({args}):\n"
f"{INDENT}_{func_name} = libiwasm.{func_name}\n"
f"{INDENT}_{func_name}.restype = {restype}\n"
f"{INDENT}_{func_name}.argtypes = {argtypes}\n"
f"{INDENT}return _{func_name}({args})\n"
)
self.ret += "\n"
def visit_Enum(self, node):
# pylint: disable=invalid-name
elem_value = 0
# generate enum elementes directly as consts with values
for i, elem in enumerate(node.values.enumerators):
self.ret += f"{elem.name}"
if elem.value:
elem_value = int(elem.value.value)
else:
if 0 == i:
elem_value = 0
else:
elem_value += 1
self.ret += f" = {elem_value}\n"
self.ret += "\n"
def preflight_check(workspace):
wamr_repo = workspace.joinpath("wasm-micro-runtime")
file_check_list = [
wamr_repo.exists(),
wamr_repo.joinpath(WASM_C_API_HEADER).exists(),
]
if not all(file_check_list):
print(
"please run utils/download_wamr.py to download the repo, or re-download the repo"
)
return False
if not shutil.which("gcc"):
print("please install gcc")
return False
return True
def do_parse(workspace):
filename = workspace.joinpath(WASM_C_API_HEADER)
filename = str(filename)
ast = parse_file(
filename,
use_cpp=True,
cpp_path="gcc",
cpp_args=[
"-E",
"-D__attribute__(x)=",
"-D__asm__(x)=",
"-D__asm(x)=",
"-D__builtin_va_list=int",
"-D__extension__=",
"-D__inline__=",
"-D__restrict=",
"-D__restrict__=",
"-D_Static_assert(x, y)=",
"-D__signed=",
"-D__volatile__(x)=",
"-Dstatic_assert(x, y)=",
],
)
ast_visitor = Visitor()
ast_visitor.visit(ast)
return ast_visitor.ret
def main():
current_file = pathlib.Path(__file__)
if current_file.is_symlink():
current_file = pathlib.Path(os.readlink(current_file))
current_dir = current_file.parent.resolve()
root_dir = current_dir.joinpath("..").resolve()
if not preflight_check(root_dir):
return False
wamr_repo = root_dir.joinpath("wasm-micro-runtime")
binding_file_path = root_dir.joinpath(BINDING_PATH)
with open(binding_file_path, "wt", encoding="utf-8") as binding_file:
binding_file.write(do_parse(wamr_repo))
return True
if __name__ == "__main__":
sys.exit(0 if main() else 1)

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
#
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
__all__ = ["ffi"]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,642 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
#
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# pylint: disable=missing-class-docstring
# pylint: disable=missing-function-docstring
# pylint: disable=missing-module-docstring
import ctypes as c
import os
from pathlib import Path
import sys
#
# Prologue. Dependencies of binding
#
# how to open the library file of WAMR
if sys.platform == "linux":
BUILDING_DIR = "product-mini/platforms/linux/build"
LIBRARY_NAME = "libiwasm.so"
elif sys.platform == "win32":
BUILDING_DIR = "product-mini/platforms/windows/build"
LIBRARY_NAME = "iwasm.dll"
elif sys.platform == "darwin":
BUILDING_DIR = "product-mini/platforms/darwin/build"
LIBRARY_NAME = "libiwasm.dylib"
else:
raise RuntimeError(f"unsupported platform `{sys.platform}`")
# FIXME: should load libiwasm.so from current system library path
current_file = Path(__file__)
if current_file.is_symlink():
current_file = Path(os.readlink(current_file))
current_dir = current_file.parent.resolve()
root_dir = current_dir.parent.parent.parent.parent.resolve()
wamr_dir = root_dir.joinpath("wasm-micro-runtime").resolve()
if not wamr_dir.exists():
raise RuntimeError(f"not found the repo of wasm-micro-runtime under {root_dir}")
libpath = wamr_dir.joinpath(BUILDING_DIR).joinpath(LIBRARY_NAME).resolve()
if not libpath.exists():
raise RuntimeError(f"not found precompiled wamr library at {libpath}")
print(f"loading WAMR library from {libpath} ...")
libiwasm = c.cdll.LoadLibrary(libpath)
class wasm_ref_t(c.Structure):
# pylint: disable=invalid-name
pass
class wasm_val_union(c.Union):
# pylint: disable=invalid-name
_fields_ = [
("i32", c.c_int32),
("i64", c.c_int64),
("f32", c.c_float),
("f64", c.c_double),
("ref", c.POINTER(wasm_ref_t)),
]
class wasm_val_t(c.Structure):
# pylint: disable=invalid-name
_fields_ = [
("kind", c.c_uint8),
("of", wasm_val_union),
]
def dereference(p):
# pylint: disable=protected-access
if not isinstance(p, c._Pointer):
raise RuntimeError("not a pointer")
return p.contents
# HELPERs
def create_null_pointer(struct_type):
return c.POINTER(struct_type)()
def is_null_pointer(c_pointer):
# pylint: disable=protected-access
if isinstance(c_pointer, c._Pointer):
return False if c_pointer else True
else:
raise RuntimeError("not a pointer")
def wasm_vec_to_list(vec):
"""
Converts a vector or a POINTER(vector) to a list
vector of type pointers -> list of type pointers
"""
known_vec_type = [
wasm_byte_vec_t,
wasm_valtype_vec_t,
wasm_functype_vec_t,
wasm_globaltype_vec_t,
wasm_tabletype_vec_t,
wasm_memorytype_vec_t,
wasm_externtype_vec_t,
wasm_importtype_vec_t,
wasm_exporttype_vec_t,
wasm_val_vec_t,
wasm_frame_vec_t,
wasm_extern_vec_t,
]
known_vec_pointer_type = [POINTER(type) for type in known_vec_type]
if any([isinstance(vec, type) for type in known_vec_pointer_type]):
vec = dereference(vec)
return [vec.data[i] for i in range(vec.num_elems)]
elif any([isinstance(vec, type) for type in known_vec_type]):
return [vec.data[i] for i in range(vec.num_elems)]
else:
raise RuntimeError("not a known vector type")
def list_to_carray(elem_type, *args):
"""
Converts a python list into a C array
"""
data = (elem_type * len(args))(*args)
return data
def load_module_file(wasm_content):
binary = wasm_byte_vec_t()
wasm_byte_vec_new_uninitialized(binary, len(wasm_content))
# has to use malloced memory.
c.memmove(binary.data, wasm_content, len(wasm_content))
binary.num_elems = len(wasm_content)
return binary
#
# Enhancment of binding
#
from .binding import *
# Built-in functions for Structure
wasm_finalizer = CFUNCTYPE(None, c_void_p)
def __repr_wasm_limits_t(self):
return f"{self.min:#x} {self.max:#x}"
# overwrite
wasm_limits_t.__repr__ = __repr_wasm_limits_t
def __compare_wasm_valtype_t(self, other):
if not isinstance(other, wasm_valtype_t):
return False
return wasm_valtype_kind(byref(self)) == wasm_valtype_kind(byref(other))
def __repr_wasm_valtype_t(self):
val_kind = wasm_valtype_kind(byref(self))
if WASM_I32 == val_kind:
return "i32"
elif WASM_I64 == val_kind:
return "i64"
elif WASM_F32 == val_kind:
return "f32"
elif WASM_F64 == val_kind:
return "f64"
elif WASM_FUNCREF == val_kind:
return "funcref"
else:
return "anyref"
wasm_valtype_t.__eq__ = __compare_wasm_valtype_t
wasm_valtype_t.__repr__ = __repr_wasm_valtype_t
def __compare_wasm_byte_vec_t(self, other):
if not isinstance(other, wasm_byte_vec_t):
return False
if self.num_elems != other.num_elems:
return False
self_data = bytes(self.data[: self.num_elems])
other_data = bytes(other.data[: other.num_elems])
return self_data.decode() == other_data.decode()
def __repr_wasm_byte_vec_t(self):
data = bytes(self.data[: self.num_elems])
return data.decode() if self.size else ""
wasm_byte_vec_t.__eq__ = __compare_wasm_byte_vec_t
wasm_byte_vec_t.__repr__ = __repr_wasm_byte_vec_t
def __compare_wasm_functype_t(self, other):
if not isinstance(other, wasm_functype_t):
return False
params1 = dereference(wasm_functype_params(byref(self)))
params2 = dereference(wasm_functype_params(byref(other)))
results1 = dereference(wasm_functype_results(byref(self)))
results2 = dereference(wasm_functype_results(byref(other)))
return params1 == params2 and results1 == results2
def __repr_wasm_functype_t(self):
params = dereference(wasm_functype_params(byref(self)))
results = dereference(wasm_functype_results(byref(self)))
params = f" (params {params})" if params.size else ""
results = f" (results {results})" if results.size else ""
return f"(func{params}{results})"
wasm_functype_t.__eq__ = __compare_wasm_functype_t
wasm_functype_t.__repr__ = __repr_wasm_functype_t
def __compare_wasm_globaltype_t(self, other):
if not isinstance(other, wasm_globaltype_t):
return False
content1 = dereference(wasm_globaltype_content(byref(self)))
content2 = dereference(wasm_globaltype_content(byref(other)))
mutability1 = wasm_globaltype_mutability(byref(self))
mutability2 = wasm_globaltype_mutability(byref(other))
return content1 == content2 and mutability1 == mutability2
def __repr_wasm_globaltype_t(self):
mutability = f"{wasm_globaltype_mutability(byref(self))}"
content = f"{dereference(wasm_globaltype_content(byref(self)))}"
return f"(global{' mut ' if mutability else ' '}{content})"
wasm_globaltype_t.__eq__ = __compare_wasm_globaltype_t
wasm_globaltype_t.__repr__ = __repr_wasm_globaltype_t
def __compare_wasm_tabletype_t(self, other):
if not isinstance(other, wasm_tabletype_t):
return False
element1 = dereference(wasm_tabletype_element(byref(self)))
element2 = dereference(wasm_tabletype_element(byref(other)))
limits1 = dereference(wasm_tabletype_limits(byref(self)))
limits2 = dereference(wasm_tabletype_limits(byref(other)))
return element1 == element2 and limits1 == limits2
def __repr_wasm_tabletype_t(self):
element = dereference(wasm_tabletype_element(byref(self)))
limit = dereference(wasm_tabletype_limits(byref(self)))
return f"(table {limit} {element})"
wasm_tabletype_t.__eq__ = __compare_wasm_tabletype_t
wasm_tabletype_t.__repr__ = __repr_wasm_tabletype_t
def __compare_wasm_memorytype_t(self, other):
if not isinstance(other, wasm_memorytype_t):
return False
limits1 = dereference(wasm_memorytype_limits(byref(self)))
limits2 = dereference(wasm_memorytype_limits(byref(other)))
return limits1 == limits2
def __repr_wasm_memorytype_t(self):
limit = dereference(wasm_memorytype_limits(byref(self)))
return f"(memory {limit})"
wasm_memorytype_t.__eq__ = __compare_wasm_memorytype_t
wasm_memorytype_t.__repr__ = __repr_wasm_memorytype_t
def __compare_wasm_externtype_t(self, other):
if not isinstance(other, wasm_externtype_t):
return False
if wasm_externtype_kind(byref(self)) != wasm_externtype_kind(byref(other)):
return False
extern_kind = wasm_externtype_kind(byref(self))
if WASM_EXTERN_FUNC == extern_kind:
return dereference(wasm_externtype_as_functype(self)) == dereference(
wasm_externtype_as_functype(other)
)
elif WASM_EXTERN_GLOBAL == extern_kind:
return dereference(wasm_externtype_as_globaltype(self)) == dereference(
wasm_externtype_as_globaltype(other)
)
elif WASM_EXTERN_MEMORY == extern_kind:
return dereference(wasm_externtype_as_memorytype(self)) == dereference(
wasm_externtype_as_memorytype(other)
)
elif WASM_EXTERN_TABLE == extern_kind:
return dereference(wasm_externtype_as_tabletype(self)) == dereference(
wasm_externtype_as_tabletype(other)
)
else:
raise RuntimeError("not a valid wasm_externtype_t")
def __repr_wasm_externtype_t(self):
extern_kind = wasm_externtype_kind(byref(self))
if WASM_EXTERN_FUNC == extern_kind:
return str(dereference(wasm_externtype_as_functype(byref(self))))
elif WASM_EXTERN_GLOBAL == extern_kind:
return str(dereference(wasm_externtype_as_globaltype(byref(self))))
elif WASM_EXTERN_MEMORY == extern_kind:
return str(dereference(wasm_externtype_as_memorytype(byref(self))))
elif WASM_EXTERN_TABLE == extern_kind:
return str(dereference(wasm_externtype_as_tabletype(byref(self))))
else:
raise RuntimeError("not a valid wasm_externtype_t")
wasm_externtype_t.__eq__ = __compare_wasm_externtype_t
wasm_externtype_t.__repr__ = __repr_wasm_externtype_t
def __compare_wasm_importtype_t(self, other):
if not isinstance(other, wasm_importtype_t):
return False
if dereference(wasm_importtype_module(self)) != dereference(
wasm_importtype_module(other)
):
return False
if dereference(wasm_importtype_name(self)) != dereference(
wasm_importtype_name(other)
):
return False
self_type = dereference(wasm_importtype_type(byref(self)))
other_type = dereference(wasm_importtype_type(byref(other)))
return self_type == other_type
def __repr_wasm_importtype_t(self):
module = wasm_importtype_module(byref(self))
name = wasm_importtype_name(byref(self))
extern_type = wasm_importtype_type(byref(self))
return f'(import "{dereference(module)}" "{dereference(name)}" {dereference(extern_type)})'
wasm_importtype_t.__eq__ = __compare_wasm_importtype_t
wasm_importtype_t.__repr__ = __repr_wasm_importtype_t
def __compare_wasm_exporttype_t(self, other):
if not isinstance(other, wasm_exporttype_t):
return False
self_name = dereference(wasm_exporttype_name(byref(self)))
other_name = dereference(wasm_exporttype_name(byref(other)))
if self_name != other_name:
return False
self_type = dereference(wasm_exporttype_type(byref(self)))
other_type = dereference(wasm_exporttype_type(byref(other)))
return self_type == other_type
def __repr_wasm_exporttype_t(self):
name = wasm_exporttype_name(byref(self))
extern_type = wasm_exporttype_type(byref(self))
return f'(export "{dereference(name)}" {dereference(extern_type)})'
wasm_exporttype_t.__eq__ = __compare_wasm_exporttype_t
wasm_exporttype_t.__repr__ = __repr_wasm_exporttype_t
def __compare_wasm_val_t(self, other):
if not isinstance(other, wasm_val_t):
return False
if self.kind != other.kind:
return False
if WASM_I32 == self.kind:
return self.of.i32 == other.of.i32
elif WASM_I64 == self.kind:
return self.of.i64 == other.of.i64
elif WASM_F32 == self.kind:
return self.of.f32 == other.of.f32
elif WASM_F64 == self.kind:
return self.of.f64 == other.of.f63
elif WASM_ANYREF == self.kind:
raise RuntimeError("FIXME")
else:
raise RuntimeError("not a valid val kind")
def __repr_wasm_val_t(self):
if WASM_I32 == self.kind:
return f"i32 {self.of.i32}"
elif WASM_I64 == self.kind:
return f"i64 {self.of.i64}"
elif WASM_F32 == self.kind:
return f"f32 {self.of.f32}"
elif WASM_F64 == self.kind:
return f"f64 {self.of.f64}"
elif WASM_ANYREF == self.kind:
return f"anyref {self.of.ref}"
else:
raise RuntimeError("not a valid val kind")
wasm_val_t.__repr__ = __repr_wasm_val_t
wasm_val_t.__eq__ = __compare_wasm_val_t
def __repr_wasm_trap_t(self):
message = wasm_message_t()
wasm_trap_message(self, message)
return f'(trap "{str(message)}")'
wasm_trap_t.__repr__ = __repr_wasm_trap_t
def __repr_wasm_frame_t(self):
instance = wasm_frame_instance(self)
module_offset = wasm_frame_module_offset(self)
func_index = wasm_frame_func_index(self)
func_offset = wasm_frame_func_offset(self)
return f"> module:{module_offset:#x} => func#{func_index:#x}.{func_offset:#x}"
wasm_frame_t.__repr__ = __repr_wasm_frame_t
def __repr_wasm_module_t(self):
imports = wasm_importtype_vec_t()
wasm_module_imports(self, imports)
exports = wasm_exporttype_vec_t()
wasm_module_exports(self, exports)
ret = "(module"
ret += str(imports).replace("(import", "\n (import")
ret += str(exports).replace("(export", "\n (export")
ret += "\n)"
return ret
wasm_module_t.__repr__ = __repr_wasm_module_t
def __repr_wasm_instance_t(self):
exports = wasm_extern_vec_t()
wasm_instance_exports(self, exports)
ret = "(instance"
ret += str(exports).replace("(export", "\n (export")
ret += "\n)"
return ret
wasm_instance_t.__repr__ = __repr_wasm_instance_t
def __repr_wasm_func_t(self):
ft = wasm_func_type(self)
return f"{str(dereference(ft))[:-1]} ... )"
wasm_func_t.__repr__ = __repr_wasm_func_t
def __repr_wasm_global_t(self):
gt = wasm_global_type(self)
return f"{str(dereference(gt))[:-1]} ... )"
wasm_global_t.__repr__ = __repr_wasm_global_t
def __repr_wasm_table_t(self):
tt = wasm_table_type(self)
return f"{str(dereference(tt))[:-1]} ... )"
wasm_table_t.__repr__ = __repr_wasm_table_t
def __repr_wasm_memory_t(self):
mt = wasm_memory_type(self)
return f"{str(dereference(mt))[:-1]} ... )"
wasm_memory_t.__repr__ = __repr_wasm_memory_t
def __repr_wasm_extern_t(self):
ext_type = wasm_extern_type(self)
ext_kind = wasm_extern_kind(self)
ret = "(export "
if WASM_EXTERN_FUNC == ext_kind:
ft = wasm_externtype_as_functype(ext_type)
ret += str(dereference(ft))
elif WASM_EXTERN_GLOBAL == ext_kind:
gt = wasm_externtype_as_globaltype(ext_type)
ret += str(dereference(gt))
elif WASM_EXTERN_MEMORY == ext_kind:
mt = wasm_externtype_as_memorytype(ext_type)
ret += str(dereference(mt))
elif WASM_EXTERN_TABLE == ext_kind:
tt = wasm_externtype_as_tabletype(ext_type)
ret += str(dereference(tt))
else:
raise RuntimeError("not a valid extern kind")
ret += ")"
return ret
wasm_extern_t.__repr__ = __repr_wasm_extern_t
# Function Types construction short-hands
def wasm_name_new_from_string(s):
name = wasm_name_t()
data = ((c.c_ubyte) * len(s)).from_buffer_copy(s.encode())
wasm_byte_vec_new(byref(name), len(s), data)
return name
def __wasm_functype_new(param_list, result_list):
def __list_to_wasm_valtype_vec(l):
vec = wasm_valtype_vec_t()
if not l:
wasm_valtype_vec_new_empty(byref(vec))
else:
data_type = POINTER(wasm_valtype_t) * len(l)
data = data_type()
for i in range(len(l)):
data[i] = l[i]
wasm_valtype_vec_new(byref(vec), len(l), data)
return vec
params = __list_to_wasm_valtype_vec(param_list)
results = __list_to_wasm_valtype_vec(result_list)
return wasm_functype_new(byref(params), byref(results))
def wasm_functype_new_0_0():
return __wasm_functype_new([], [])
def wasm_functype_new_1_0(p1):
return __wasm_functype_new([p1], [])
def wasm_functype_new_2_0(p1, p2):
return __wasm_functype_new([p1, p2], [])
def wasm_functype_new_3_0(p1, p2, p3):
return __wasm_functype_new([p1, p2, p3], [])
def wasm_functype_new_0_1(r1):
return __wasm_functype_new([], [r1])
def wasm_functype_new_1_1(p1, r1):
return __wasm_functype_new([p1], [r1])
def wasm_functype_new_2_1(p1, p2, r1):
return __wasm_functype_new([p1, p2], [r1])
def wasm_functype_new_3_1(p1, p2, p3, r1):
return __wasm_functype_new([p1, p2, p3], [r1])
def wasm_limits_new(min, max):
limit = wasm_limits_t()
limit.min = min
limit.max = max
return c.pointer(limit)
def wasm_i32_val(i):
v = wasm_val_t()
v.kind = WASM_I32
v.of.i32 = i
return v
def wasm_i64_val(i):
v = wasm_val_t()
v.kind = WASM_I64
v.of.i64 = i
return v
def wasm_f32_val(z):
v = wasm_val_t()
v.kind = WASM_F32
v.of.f32 = z
return v
def wasm_f64_val(z):
v = wasm_val_t()
v.kind = WASM_F64
v.of.f64 = z
return v
def wasm_func_cb_decl(func):
return wasm_func_callback_t(func)
def wasm_func_with_env_cb_decl(func):
return wasm_func_callback_with_env_t(func)