Add WAMR API bindings in Python (#1959)

Before adding the new bindings:
1. Moved wasm-c-api in a subfolder wasmcapi in the package.
2. Adapted the tests to be able to run in this new structure.

New:
1. Added the WAMR API in another folder wamrapi in the same level as wasm-c-api.
2. Created an OOP proposal.
3. Added an example using this proposal.
This commit is contained in:
tonibofarull
2023-02-16 08:21:28 +01:00
committed by GitHub
parent f60c3c6111
commit 3cc132e8fc
30 changed files with 2054 additions and 28 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,123 @@
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from ctypes import Array
from ctypes import c_char
from ctypes import c_uint
from ctypes import c_uint8
from ctypes import c_void_p
from ctypes import cast
from ctypes import create_string_buffer
from ctypes import POINTER
from ctypes import pointer
from wamr.wamrapi.iwasm import Alloc_With_Pool
from wamr.wamrapi.iwasm import RuntimeInitArgs
from wamr.wamrapi.iwasm import wasm_exec_env_t
from wamr.wamrapi.iwasm import wasm_function_inst_t
from wamr.wamrapi.iwasm import wasm_module_inst_t
from wamr.wamrapi.iwasm import wasm_module_t
from wamr.wamrapi.iwasm import wasm_runtime_call_wasm
from wamr.wamrapi.iwasm import wasm_runtime_create_exec_env
from wamr.wamrapi.iwasm import wasm_runtime_deinstantiate
from wamr.wamrapi.iwasm import wasm_runtime_destroy
from wamr.wamrapi.iwasm import wasm_runtime_destroy_exec_env
from wamr.wamrapi.iwasm import wasm_runtime_full_init
from wamr.wamrapi.iwasm import wasm_runtime_instantiate
from wamr.wamrapi.iwasm import wasm_runtime_load
from wamr.wamrapi.iwasm import wasm_runtime_lookup_function
from wamr.wamrapi.iwasm import wasm_runtime_unload
class Engine:
def __init__(self):
self.init_args = self._get_init_args()
wasm_runtime_full_init(pointer(self.init_args))
def __del__(self):
print("deleting Engine")
wasm_runtime_destroy()
def _get_init_args(self, heap_size: int = 1024 * 512) -> RuntimeInitArgs:
init_args = RuntimeInitArgs()
init_args.mem_alloc_type = Alloc_With_Pool
init_args.mem_alloc_option.pool.heap_buf = cast(
(c_char * heap_size)(), c_void_p
)
init_args.mem_alloc_option.pool.heap_size = heap_size
return init_args
class Module:
__create_key = object()
@classmethod
def from_file(cls, engine: Engine, fp: str) -> "Module":
return Module(cls.__create_key, engine, fp)
def __init__(self, create_key: object, engine: Engine, fp: str) -> None:
assert (
create_key == Module.__create_key
), "Module objects must be created using Module.from_file"
self.engine = engine
self.module, self.file_data = self._create_module(fp)
def __del__(self):
print("deleting Module")
wasm_runtime_unload(self.module)
def _create_module(self, fp: str) -> tuple[wasm_module_t, Array[c_uint]]:
with open(fp, "rb") as f:
data = f.read()
data = (c_uint8 * len(data))(*data)
error_buf = create_string_buffer(128)
module = wasm_runtime_load(data, len(data), error_buf, len(error_buf))
if not module:
raise Exception("Error while creating module")
return module, data
class Instance:
def __init__(self, module: Module, stack_size: int = 65536, heap_size: int = 16384):
self.module = module
self.module_inst = self._create_module_inst(module, stack_size, heap_size)
def __del__(self):
print("deleting Instance")
wasm_runtime_deinstantiate(self.module_inst)
def lookup_function(self, name: str):
func = wasm_runtime_lookup_function(self.module_inst, name, None)
if not func:
raise Exception("Error while looking-up function")
return func
def _create_module_inst(self, module: Module, stack_size: int, heap_size: int) -> wasm_module_inst_t:
error_buf = create_string_buffer(128)
module_inst = wasm_runtime_instantiate(
module.module, stack_size, heap_size, error_buf, len(error_buf)
)
if not module_inst:
raise Exception("Error while creating module instance")
return module_inst
class ExecEnv:
def __init__(self, module_inst: Instance, stack_size: int = 65536):
self.module_inst = module_inst
self.exec_env = self._create_exec_env(module_inst, stack_size)
def __del__(self):
print("deleting ExecEnv")
wasm_runtime_destroy_exec_env(self.exec_env)
def call(self, func: wasm_function_inst_t, argc: int, argv: "POINTER[c_uint]"):
if not wasm_runtime_call_wasm(self.exec_env, func, argc, argv):
raise Exception("Error while calling function")
def _create_exec_env(self, module_inst: Instance, stack_size: int) -> wasm_exec_env_t:
exec_env = wasm_runtime_create_exec_env(module_inst.module_inst, stack_size)
if not exec_env:
raise Exception("Error while creating execution environment")
return exec_env

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.parents[4].resolve()
wamr_dir = root_dir.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)