def test_return_multiple_values_from_host_function(): store = Store() module = Module( store, """ (module (type $swap_t (func (param i32 i64) (result i64 i32))) (type $test_t (func (param i32 i64) (result i64 i32))) (import "env" "swap" (func $swap (type $swap_t))) (func $test (type $test_t) (param $x i32) (param $y i64) (result i64 i32) local.get $x local.get $y call $swap) (export "test" (func $test))) """ ) def swap(x: 'i32', y: 'i64') -> ('i64', 'i32'): return (y, x) import_object = ImportObject() import_object.register( "env", { "swap": Function(store, swap), } ) instance = Instance(module, import_object) assert instance.exports.test(41, 42) == (42, 41)
def test_import_global(): store = Store() module = Module( store, """ (module (import "env" "global" (global $global (mut i32))) (func (export "read_g") (result i32) global.get $global) (func (export "write_g") (param i32) local.get 0 global.set $global)) """) global_ = Global(store, Value.i32(7), mutable=True) import_object = ImportObject() import_object.register("env", {"global": global_}) instance = Instance(module, import_object) assert instance.exports.read_g() == 7 global_.value = 153 assert instance.exports.read_g() == 153 instance.exports.write_g(11) assert global_.value == 11
def test_import_memory(): store = Store() module = Module( store, """ (module (import "env" "memory" (memory $memory 1)) (func (export "increment") i32.const 0 i32.const 0 i32.load ;; load 0 i32.const 1 i32.add ;; add 1 i32.store ;; store at 0 )) """) memory = Memory(store, MemoryType(1, shared=False)) view = memory.uint8_view(offset=0) import_object = ImportObject() import_object.register("env", {"memory": memory}) instance = Instance(module, import_object) assert view[0] == 0 instance.exports.increment() assert view[0] == 1 instance.exports.increment() assert view[0] == 2
def test_early_exit(): store = Store() module = Module( store, """ (module (type $run_t (func (param i32 i32) (result i32))) (type $early_exit_t (func (param) (result))) (import "env" "early_exit" (func $early_exit (type $early_exit_t))) (func $run (type $run_t) (param $x i32) (param $y i32) (result i32) (call $early_exit) (i32.add local.get $x local.get $y)) (export "run" (func $run))) """) def early_exit(): raise Exception('oops') import_object = ImportObject() import_object.register("env", { "early_exit": Function(store, early_exit), }) instance = Instance(module, import_object) try: instance.exports.run(1, 2) except RuntimeError as err: assert 'oops' in str(err) else: assert False
def host_functions(store: Store) -> ImportObject: """ Provide an "abort()" host function. https://github.com/flow-heater/pytshello/issues/1 :param store: :return: """ # When creating an `Instance`, we can pass an `ImportObject`. All # entities that must be imported are registered inside the # `ImportObject`. import_object = ImportObject() # Let's write the Python function that is going to be imported, # i.e. called by the WebAssembly module. def abort(a: 'i32', b: 'i32', c: 'i32', d: 'i32'): pass abort_host_function = Function(store, abort) # Now let's register the `sum` import inside the `env` namespace. import_object.register("env", { "abort": abort_host_function, }) return import_object
def test_import_function(): def sum(x: int, y: int) -> int: return x + y store = Store() module = Module( store, """ (module (import "math" "sum" (func $sum (param i32 i32) (result i32))) (func (export "add_one") (param i32) (result i32) local.get 0 i32.const 1 call $sum)) """) import_object = ImportObject() import_object.register("math", {"sum": Function(store, sum)}) instance = Instance(module, import_object) assert instance.exports.add_one(1) == 2
def build_instance(): import_object = ImportObject() store = Store(engine.JIT(Compiler)) import_object.register( "env", { "__sys_getpid": Function(store, lambda: 42, FunctionType([], [Type.I32])), }) import_object.register( "wasi_snapshot_preview1", { "proc_exit": Function(store, lambda *args: None, FunctionType([Type.I32], [])), "clock_time_get": Function(store, lambda *args: int(time.time()), FunctionType([Type.I32, Type.I64, Type.I32], [Type.I32])), "fd_close": Function(store, lambda *args: 1, FunctionType([Type.I32], [Type.I32])), "fd_write": Function( store, lambda *args: 1, FunctionType([Type.I32, Type.I32, Type.I32, Type.I32], [Type.I32])), "fd_seek": Function( store, lambda *args: 1, FunctionType([Type.I32, Type.I64, Type.I32, Type.I32], [Type.I32])), "fd_read": Function( store, lambda *args: 1, FunctionType([Type.I32, Type.I32, Type.I32, Type.I32], [Type.I32])), }) # Let's compile the module to be able to execute it! module = Module(store, open(quiet_path, 'rb').read()) # Now the module is compiled, we can instantiate it. return Instance(module, import_object)
i32.const 1 call $sum)) """) # Create a store. store = Store(engine.Universal(Compiler)) # Let's compile the Wasm module. module = Module(store, wasm_bytes) # Here we go. # # When creating an `Instance`, we can pass an `ImportObject`. All # entities that must be imported are registered inside the # `ImportObject`. import_object = ImportObject() # Let's write the Python function that is going to be imported, # i.e. called by the WebAssembly module. def sum(x: int, y: int) -> int: return x + y sum_host_function = Function(store, sum) # See how we have used Python annotations to help `wasmer` to infer # the types of the host function? Well, it could be limited. For # example, `int` in Python matches to `i32` in WebAssembly. We can't # represent `i64`. Or… we can use a function type.
# Let's then create a global that is going to be imported. host_global = Global(store, Value.i32(42)) # Create an import object. # # Imports are stored in namespaces. We'll need to register each of the # namespaces with a name and add the imported entities there. # # Note that the namespace can also have an empty name. # # Our module requires us to import: # * A function `host_function` in a namespace with an empty name; # * A global `host_global` in the `env` namespace. # # Let's do this! import_object = ImportObject() import_object.register("", { "host_function": host_function, }) import_object.register("env", { "host_global": host_global, }) # Let's instantiate the module! instance = Instance(module, import_object) # And finally, let's play. # # The Wasm module exports some entities: # # * A function: `guest_function`,
def test_contains_namespace(): import_object = ImportObject() assert import_object.contains_namespace("foo") == False
def test_constructor(): import_object = ImportObject()
# Let's compile the Wasm module. module = Module(store, wasm_bytes) # Here we go. # # Let's write the Python function that is going to… fail! def early_exit(): raise Exception('oops') # When creating an `Instance`, we can pass an `ImportObject`. All # entities that must be imported are registered inside the # `ImportObject`. import_object = ImportObject() # Now let's register the `sum` import inside the `env` namespace. import_object.register("env", { "early_exit": Function(store, early_exit), }) # Let's instantiate the module! instance = Instance(module, import_object) # And finally, call the `run` exported function! try: instance.exports.run(1, 2) except RuntimeError as err: assert 'oops' in str(err) else:
def handle_band(width: int) -> int: print('got a line of:', width) assert width == 7 return 0 def mode_parsed(mode: int) -> int: print('mode selected:', mode) assert mode == 1 return 0 # load wasm engine store = Store(engine.JIT(Compiler)) module = Module(store, open('./decoder.wasm', 'rb').read()) import_object = ImportObject() import_object.register( "env", { "handle_band": Function(store, handle_band), 'mode_parsed': Function(store, mode_parsed), }) instance = Instance(module, import_object) mem = instance.exports.memory.int8_view() chunk_address = instance.exports.get_chunk_address() # load test data mem[chunk_address:] = TEST # run instance.exports.init(-1, 0, 256, 1) instance.exports.decode(0, len(TEST))
def make_import_object(store): INVALID_HANDLE_ERROR = 'Invalid array handle: ' INVALID_BOUNDS_ERROR = 'Array index out of bounds: ' HANDLES = [] VALID_INT_REGEX = compile(r'^\s*-?\d+\s*$') def check_bounds(a, i, error_message, fun_name): """Checks if i is within bounds of array a, otherwise throws an exception with a given error_message. """ if not (0 <= i < len(a)): print(f'Runtime error in function {fun_name}. {error_message}', file=sys.stderr) sys.exit(1) #---------------------------------------------------------------- # Functions to be imported from the Wasm module def printi(i: int) -> int: """Prints i to stdout as a decimal integer. Does not print a new line at the end. Returns 0. """ print(i, end='') return 0 def printc(c: int) -> int: """Prints a character to stdout, where c is its Unicode code point. Does not print a new line at the end. Returns 0. """ print(chr(c), end='') return 0 def prints(s: int) -> int: """Prints s to stdout as a string. s must be a handle to an array list containing zero or more Unicode code points. Does not print a new line at the end. Returns 0. """ check_bounds(HANDLES, s, INVALID_HANDLE_ERROR + str(s), currentframe().f_code.co_name) print(''.join([chr(c) for c in HANDLES[s]]), end='') return 0 def println() -> int: """Prints a newline character to stdout. Returns 0. """ print() return 0 def readi() -> int: """Reads from stdin a signed decimal integer and return its value. Does not return until a valid integer has been read. """ data = '' while not VALID_INT_REGEX.match(data): data = input() return int(data) def reads() -> int: """ Reads from stdin a string (until the end of line) and returns a handle to a newly created array list containing the Unicode code points of all the characters read, excluding the end of line. """ data = input() HANDLES.append([ord(c) for c in data]) return len(HANDLES) - 1 def new(n: int) -> int: """Creates a new array list object with n elements and returns its handle. All the elements of the array list are set to zero. Throws an exception if n is less than zero. """ if n < 0: print( 'Runtime error in function new. ' f"Can't create a negative size array: {n}", file=sys.stderr) sys.exit(1) HANDLES.append([0] * n) return len(HANDLES) - 1 def size(h: int) -> int: """Returns the size (number of elements) of the array list referenced by handle h. Throws an exception if h is not a valid handle. """ check_bounds(HANDLES, h, INVALID_HANDLE_ERROR + str(h), currentframe().f_code.co_name) return len(HANDLES[h]) def add(h: int, x: int) -> int: """Adds x at the end of the array list referenced by handle h. Returns 0. Throws an exception if h is not a valid handle. """ check_bounds(HANDLES, h, INVALID_HANDLE_ERROR + str(h), currentframe().f_code.co_name) HANDLES[h].append(x) return 0 def get(h: int, i: int) -> int: """Returns the value at index i from the array list referenced by handle h. Throws an exception if i is out of bounds or if h is not a valid handle. """ check_bounds(HANDLES, h, INVALID_HANDLE_ERROR + str(h), currentframe().f_code.co_name) check_bounds(HANDLES[h], i, INVALID_BOUNDS_ERROR + str(i), currentframe().f_code.co_name) return HANDLES[h][i] def set(h: int, i: int, x: int) -> int: """Sets to x the element at index i of the array list referenced by handle h. Returns 0. Throws an exception if i is out of bounds or if h is not a valid handle. """ check_bounds(HANDLES, h, INVALID_HANDLE_ERROR + str(h), currentframe().f_code.co_name) check_bounds(HANDLES[h], i, INVALID_BOUNDS_ERROR + str(i), currentframe().f_code.co_name) HANDLES[h][i] = x return 0 #---------------------------------------------------------------- import_object = ImportObject() import_object.register( "drac", { "printi": Function(store, printi), "printc": Function(store, printc), "prints": Function(store, prints), "println": Function(store, println), "readi": Function(store, readi), "reads": Function(store, reads), "new": Function(store, new), "size": Function(store, size), "add": Function(store, add), "get": Function(store, get), "set": Function(store, set), }) return import_object