Skip to content

Commit

Permalink
Wiring SharedMemory with it's corresponding bindings (#255)
Browse files Browse the repository at this point in the history
* Wiring SharedMemory with it's corresponding bindings

* Restoring _bindings.py as is being autogenerated out of the C-API
interface in the wasmtime runtime repo

* Removing SharedMemory read/write methods, as they are also not
implemented in the Rust API for wasmtime.
A SharedMemory is not associated to any Store, and the read/write
methods from the linear memory seem  to rely on the Store in order
to properly operate.

* Making SharedMemory inherit from the Manager type

* Adding runtime typechecking to SharedMemory::_from_raw

* After regenerating the bindings from wasmtime c-api (main/dev branch)
and adjusting the shared memory arguments to match the new api, I
found out that I needed to adjust other parts of the bindings that I guess
have changed since the last release.
In this commit, I only change the (shared) memory related ones, but I
think I can adjust the rest of the c-api that has changed as well in
the following commits.

* Update to latest wasmtime dev (#257)

* Retoring bindings from main branch as it was updated to latest
changes in the C-APO from Wasmtime runtime repo

* * Fixing Python types as suggested by mypy
* Adding some unit tests for shared memory

* Adding unit tests for shared memory

---------

Co-authored-by: Jesse Rusak <[email protected]>
  • Loading branch information
atilag and jder authored Nov 25, 2024
1 parent d54945b commit aee787c
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 4 deletions.
56 changes: 56 additions & 0 deletions tests/test_shared_memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import unittest

from wasmtime import *


class TestSharedMemory(unittest.TestCase):
def test_new(self):
engine = Store().engine
memory_type = MemoryType(Limits(1, 2), is_64=False, shared=True)
assert(not memory_type.is_64)
shared_memory = SharedMemory(engine, memory_type)
with self.assertRaises(TypeError):
shared_memory.grow('') # type: ignore
with self.assertRaises(WasmtimeError):
shared_memory.grow(-1)
self.assertEqual(shared_memory.data_ptr()[0], 0)
self.assertEqual(shared_memory.data_len(), 65536)
self.assertTrue(isinstance(shared_memory.type(), MemoryType))

def test_grow(self):
engine = Store().engine
memory_type = MemoryType(Limits(1, 2), shared=True)
shared_memory = SharedMemory(engine, memory_type)
assert(shared_memory.grow(1) == 1)
assert(shared_memory.grow(0) == 2)
with self.assertRaises(WasmtimeError):
shared_memory.grow(1)

def test_errors(self):
engine = Store().engine
ty = MemoryType(Limits(1, 2), shared=True)
with self.assertRaises(AttributeError):
SharedMemory(1, ty) # type: ignore
with self.assertRaises(AttributeError):
SharedMemory(engine, 1) # type: ignore

def test_shared_memory_type_fails_if_no_max_specified(self):
with self.assertRaises(WasmtimeError):
# Shared memories must have a max size
MemoryType(Limits(0x100000000, None), shared=True)

def test_shared_memory_type_works_if_min_max_i64_is_set(self):
ty = MemoryType(Limits(0x100000000, 0x100000000), is_64=True, shared=True)
assert(ty.limits.min == 0x100000000)
assert(ty.limits.max == 0x100000000)
assert(ty.is_64)

def test_shared_memory_type_fails_if_is_too_large(self):
with self.assertRaises(WasmtimeError):
MemoryType(Limits(1, 0x100000000), shared=True)

def test_memory_type_has_to_be_shared(self):
engine = Store().engine
ty = MemoryType(Limits(1, 2))
with self.assertRaises(WasmtimeError):
SharedMemory(engine, ty)
2 changes: 2 additions & 0 deletions wasmtime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from ._instance import Instance
from ._wasi import WasiConfig, FilePerms, DirPerms
from ._linker import Linker
from ._sharedmemory import SharedMemory

__all__ = [
'wat2wasm',
Expand All @@ -50,6 +51,7 @@
'Caller',
'Table',
'Memory',
'SharedMemory',
'Global',
'Trap',
'TrapCode',
Expand Down
3 changes: 2 additions & 1 deletion wasmtime/_exportable.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
from ._func import Func
from ._globals import Global
from ._memory import Memory
from ._sharedmemory import SharedMemory
from ._table import Table
from ._module import Module
from ._instance import Instance

AsExtern = typing.Union["Func", "Table", "Memory", "Global", "Instance", "Module"]
AsExtern = typing.Union["Func", "Table", "Memory", "SharedMemory", "Global", "Instance", "Module"]
8 changes: 6 additions & 2 deletions wasmtime/_extern.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


def wrap_extern(ptr: ffi.wasmtime_extern_t) -> AsExtern:
from wasmtime import Func, Table, Global, Memory, Module, Instance
from wasmtime import Func, Table, Global, Memory, SharedMemory, Module, Instance

if ptr.kind == ffi.WASMTIME_EXTERN_FUNC.value:
return Func._from_raw(ptr.of.func)
Expand All @@ -15,6 +15,8 @@ def wrap_extern(ptr: ffi.wasmtime_extern_t) -> AsExtern:
return Global._from_raw(ptr.of.global_)
if ptr.kind == ffi.WASMTIME_EXTERN_MEMORY.value:
return Memory._from_raw(ptr.of.memory)
if ptr.kind == ffi.WASMTIME_EXTERN_SHAREDMEMORY.value:
return SharedMemory._from_ptr(ptr.of.sharedmemory)
if ptr.kind == ffi.WASMTIME_EXTERN_INSTANCE.value:
return Instance._from_raw(ptr.of.instance)
if ptr.kind == ffi.WASMTIME_EXTERN_MODULE.value:
Expand All @@ -23,14 +25,16 @@ def wrap_extern(ptr: ffi.wasmtime_extern_t) -> AsExtern:


def get_extern_ptr(item: AsExtern) -> ffi.wasmtime_extern_t:
from wasmtime import Func, Table, Global, Memory, Module, Instance
from wasmtime import Func, Table, Global, Memory, SharedMemory, Module, Instance

if isinstance(item, Func):
return item._as_extern()
elif isinstance(item, Global):
return item._as_extern()
elif isinstance(item, Memory):
return item._as_extern()
elif isinstance(item, SharedMemory):
return item._as_extern()
elif isinstance(item, Table):
return item._as_extern()
elif isinstance(item, Module):
Expand Down
1 change: 1 addition & 0 deletions wasmtime/_ffi.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
WASMTIME_EXTERN_GLOBAL = c_uint8(1)
WASMTIME_EXTERN_TABLE = c_uint8(2)
WASMTIME_EXTERN_MEMORY = c_uint8(3)
WASMTIME_EXTERN_SHAREDMEMORY = c_uint8(4)
WASMTIME_EXTERN_INSTANCE = c_uint8(4)
WASMTIME_EXTERN_MODULE = c_uint8(5)

Expand Down
80 changes: 80 additions & 0 deletions wasmtime/_sharedmemory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from . import _ffi as ffi
from ctypes import *
import ctypes
from typing import Optional, Any
from wasmtime import MemoryType, WasmtimeError, Engine, Managed
from ._store import Storelike



class SharedMemory(Managed["ctypes._Pointer[ffi.wasmtime_sharedmemory_t]"]):
def __init__(self, engine: Engine, ty: MemoryType):
"""
Creates a new shared memory in `store` with the given `ty`
"""

sharedmemory_ptr = POINTER(ffi.wasmtime_sharedmemory_t)()
error = ffi.wasmtime_sharedmemory_new(engine.ptr(), ty.ptr(), byref(sharedmemory_ptr))
if error:
raise WasmtimeError._from_ptr(error)
self._set_ptr(sharedmemory_ptr)

def _delete(self, ptr: "ctypes._Pointer[ffi.wasmtime_sharedmemory_t]") -> None:
ffi.wasmtime_sharedmemory_delete(ptr)

@classmethod
def _from_ptr(cls, ptr: "ctypes._Pointer[ffi.wasmtime_sharedmemory_t]") -> "SharedMemory":
if not isinstance(ptr, POINTER(ffi.wasmtime_sharedmemory_t)):
raise TypeError("wrong shared memory pointer type provided to _from_ptr")

ty: "SharedMemory" = cls.__new__(cls)
ty._set_ptr(ptr)
return ty

def type(self) -> MemoryType:
"""
Gets the type of this memory as a `MemoryType`
"""

ptr = ffi.wasmtime_sharedmemory_type(self.ptr())
return MemoryType._from_ptr(ptr, None)

def grow(self, delta: int) -> int:
"""
Grows this memory by the given number of pages
"""

if delta < 0:
raise WasmtimeError("cannot grow by negative amount")
prev = ffi.c_uint64(0)
error = ffi.wasmtime_sharedmemory_grow(self.ptr(), delta, byref(prev))
if error:
raise WasmtimeError._from_ptr(error)
return prev.value

def size(self) -> int:
"""
Returns the size, in WebAssembly pages, of this shared memory.
"""

return ffi.wasmtime_sharedmemory_size(byref(self.ptr()))

def data_ptr(self) -> "ctypes._Pointer[c_ubyte]":
"""
Returns the raw pointer in memory where this wasm shared memory lives.
Remember that all accesses to wasm shared memory should be bounds-checked
against the `data_len` method.
"""
return ffi.wasmtime_sharedmemory_data(self.ptr())

def data_len(self) -> int:
"""
Returns the raw byte length of this memory.
"""

return ffi.wasmtime_sharedmemory_data_size(self.ptr())

def _as_extern(self) -> ffi.wasmtime_extern_t:
union = ffi.wasmtime_extern_union(sharedmemory=pointer(self.ptr()))
return ffi.wasmtime_extern_t(ffi.WASMTIME_EXTERN_SHAREDMEMORY, union)
10 changes: 9 additions & 1 deletion wasmtime/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,10 +334,18 @@ def is_64(self) -> bool:
Returns whether or not this is a 64-bit memory
"""
return ffi.wasmtime_memorytype_is64(self.ptr())

@property
def is_shared(self) -> bool:
"""
Returns whether or not this is a shared memory
"""
return ffi.wasmtime_memorytype_isshared(self.ptr())


def _as_extern(self) -> "ctypes._Pointer[ffi.wasm_externtype_t]":
return ffi.wasm_memorytype_as_externtype_const(self.ptr())


def wrap_externtype(ptr: "ctypes._Pointer[ffi.wasm_externtype_t]", owner: Optional[Any]) -> "AsExternType":
if not isinstance(ptr, POINTER(ffi.wasm_externtype_t)):
Expand Down

0 comments on commit aee787c

Please sign in to comment.