From f22e88ce64fa686462a706cba5655339d668b68e Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 12 Apr 2019 14:41:57 -0700 Subject: [PATCH 01/15] feat(extension) Partially replace `rust-cypthon` by `pyo3`. --- Cargo.lock | 132 +++++++++++++++++++++++++++++---- Cargo.toml | 2 +- examples/simple.py | 11 ++- justfile | 2 +- rust-toolchain | 1 + src/error.rs | 20 ----- src/instance.rs | 181 +++++++++++++++++++++++++-------------------- src/lib.rs | 93 ++++++++--------------- src/memory_view.rs | 90 +++++++++++++--------- src/value.rs | 89 +++++++--------------- 10 files changed, 341 insertions(+), 280 deletions(-) create mode 100644 rust-toolchain delete mode 100644 src/error.rs diff --git a/Cargo.lock b/Cargo.lock index 69ffde3e..83a2d3a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,16 +176,6 @@ name = "constant_time_eq" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "cpython" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "python3-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "cranelift-bforest" version = "0.30.0" @@ -287,6 +277,15 @@ dependencies = [ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ctor" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "digest" version = "0.8.0" @@ -369,6 +368,16 @@ dependencies = [ "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ghost" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "glob" version = "0.2.11" @@ -402,6 +411,26 @@ name = "indexmap" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "inventory" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ctor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ghost 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "inventory-impl 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "inventory-impl" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itoa" version = "0.4.3" @@ -452,6 +481,24 @@ dependencies = [ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mashup" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "mashup-impl 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mashup-impl" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "memchr" version = "2.2.0" @@ -566,6 +613,19 @@ name = "peeking_take_while" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "proc-macro-hack" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack-impl 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro-hack-impl" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "proc-macro2" version = "0.4.27" @@ -575,12 +635,39 @@ dependencies = [ ] [[package]] -name = "python3-sys" -version = "0.2.1" +name = "pyo3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "inventory 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "mashup 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "pyo3cls 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pyo3-derive-backend" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pyo3cls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "pyo3-derive-backend 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -847,6 +934,11 @@ name = "smallvec" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "spin" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "stable_deref_trait" version = "1.1.1" @@ -966,7 +1058,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "wasmer" version = "0.1.4" dependencies = [ - "cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pyo3 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-runtime 0.2.1 (git+https://github.com/wasmerio/wasmer)", ] @@ -1130,7 +1222,6 @@ dependencies = [ "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum cmake 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "96210eec534fc3fbfc0452a63769424eaa80205fda6cea98e5b61cb3d97bcec8" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" -"checksum cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b489034e723e7f5109fecd19b719e664f89ef925be785885252469e9822fa940" "checksum cranelift-bforest 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e5a357d20666bf4a8c2d626a19f1b59dbca66cd844fb1e66c5612254fd0f7505" "checksum cranelift-codegen 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab00cb149a5bb0f7e6dd391357356a5d71c335a431e8eece94f32da2d5a043f7" "checksum cranelift-codegen-meta 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3797a2f450ac71297e083dd440d0cdd0d3bceabe4a3ca6bcb9e4077e9c0327d" @@ -1141,6 +1232,7 @@ dependencies = [ "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" +"checksum ctor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "e5cc1c7c759bf979c651ce1da82d06065375e2223b65c070190b8000787da58b" "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" "checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" @@ -1151,11 +1243,14 @@ dependencies = [ "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" +"checksum ghost 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5297b71943dc9fea26a3241b178c140ee215798b7f79f7773fd61683e25bca74" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da" "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" +"checksum inventory 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "21df85981fe094480bc2267723d3dc0fd1ae0d1f136affc659b7398be615d922" +"checksum inventory-impl 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8a877ae8bce77402d5e9ed870730939e097aad827b2a932b361958fa9d6e75aa" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" @@ -1163,6 +1258,8 @@ dependencies = [ "checksum libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3ad660d7cb8c5822cd83d10897b0f1f1526792737a179e73896152f85b88c2" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" +"checksum mashup 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f2d82b34c7fb11bb41719465c060589e291d505ca4735ea30016a91f6fc79c3b" +"checksum mashup-impl 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "aa607bfb674b4efb310512527d64266b065de3f894fc52f84efcbf7eaa5965fb" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" @@ -1177,8 +1274,12 @@ dependencies = [ "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" "checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +"checksum proc-macro-hack 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c725b36c99df7af7bf9324e9c999b9e37d92c8f8caf106d82e1d7953218d2d8" +"checksum proc-macro-hack-impl 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2b753ad9ed99dd8efeaa7d2fb8453c8f6bc3e54b97966d35f1bc77ca6865254a" "checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" -"checksum python3-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "61e4aac43f833fd637e429506cb2ac9d7df672c4b68f2eaaa163649b7fdc0444" +"checksum pyo3 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb313941bd4d3f422151b2b2d0d2d9e86f308c2ba2d08cf0375e7cfed202e7e8" +"checksum pyo3-derive-backend 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "783639e1c566f08b19e74956c3fff4d953c0c271b39de2db22a9102fbedbeab1" +"checksum pyo3cls 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c4f91eb8e5394cabd1219c29ac99f7a93103a0daf3a71df0f0b6d9c395d08a86" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" @@ -1211,6 +1312,7 @@ dependencies = [ "checksum serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "58fc82bec244f168b23d1963b45c8bf5726e9a15a9d146a067f9081aeed2de79" "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" +"checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)" = "66c8865bf5a7cbb662d8b011950060b3c8743dca141b054bf7195b20d314d8e2" diff --git a/Cargo.toml b/Cargo.toml index 6862d3e7..c92ac8d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,8 @@ name = "wasmer" crate-type = ["cdylib"] [dependencies] -cpython = { version = "0.2", features = ["extension-module"] } wasmer-runtime = { git = "https://github.com/wasmerio/wasmer", branch = "master" } +pyo3 = { version = "0.6.0", features = ["extension-module"] } [package.metadata.pyo3-pack] classifier = [ diff --git a/examples/simple.py b/examples/simple.py index ff47302e..202cdfaf 100644 --- a/examples/simple.py +++ b/examples/simple.py @@ -1,10 +1,15 @@ -from wasmer import Instance, Value +from wasmer import Instance, Value, Uint8MemoryView import os __dir__ = os.path.dirname(os.path.realpath(__file__)) wasm_bytes = open(__dir__ + '/simple.wasm', 'rb').read() instance = Instance(wasm_bytes) -result = instance.call('sum', [Value.i32(5), Value.i32(37)]) +memory = instance.uint8_memory_view() +print(len(memory)) +print(memory[42]) +print(memory.BYTES_PER_ELEMENT) +print(Uint8MemoryView.BYTES_PER_ELEMENT) +#result = instance.call('sum', [Value.i32(5), Value.i32(37)]) -print(result) # 42! +#print(result) # 42! diff --git a/justfile b/justfile index bd86d0d9..f2dc2a8b 100644 --- a/justfile +++ b/justfile @@ -17,7 +17,7 @@ sleep: rust: export PYTHON_SYS_EXECUTABLE=$(which python3) cargo check - pyo3-pack develop --release --strip + pyo3-pack develop --binding_crate pyo3 --release --strip # Run Python. python-run file='': diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 00000000..07ade694 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly \ No newline at end of file diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 9e42dfcf..00000000 --- a/src/error.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Utils to manipulate Python errors.o - -use cpython::{exc::RuntimeError, PyErr, Python, PythonObject, ToPyObject}; - -/// Create a `RuntimeError` error in Python. -/// -/// # Examples -/// -/// ```rs,ignore -/// fn f(py: Python) -> PyResult<()> { -/// let error = new_runtime_error(py, "foobar"); -/// Err(error) -/// } -/// ``` -pub fn new_runtime_error(py: Python, error_message: &str) -> PyErr { - PyErr::new_lazy_init( - py.get_type::(), - Some(error_message.to_py_object(py).into_object()), - ) -} diff --git a/src/instance.rs b/src/instance.rs index a86bb3b2..b8953a3c 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,124 +1,145 @@ //! The `Instance` Python object to build WebAssembly instances. -use crate::{ - error::new_runtime_error, - memory_view, - value::{get_wasm_value, wasm_value_into_python_object, Value}, - Shell, +//use crate::{ +// error::new_runtime_error, +// memory_view, +// value::{get_wasm_value, wasm_value_into_python_object, Value}, +// Shell, +//}; +//use cpython::{PyBytes, PyObject, PyResult, Python}; +use crate::memory_view; +use pyo3::{ + prelude::*, + types::{PyAny, PyBytes}, + PyTryFrom, + exceptions::RuntimeError, }; -use cpython::{PyBytes, PyObject, PyResult, Python}; use wasmer_runtime::{ - self as runtime, imports, instantiate, validate as wasm_validate, Export, Memory, - Value as WasmValue, + self as runtime, + imports, + instantiate, + Export, + Memory, }; -/// The Python `Instance` class. -/// -/// # Examples -/// -/// ```python,ignore -/// from wasmer import Instance, Value -/// -/// file = open('my_program.wasm', 'rb') # note the mode contains `b` to get bytes, and not UTF-8 characters. -/// bytes = file.read() -/// -/// instance = Instance(bytes) -/// result = instance.call('add_one', [Value::from_i32(1)]) -/// ``` -py_class!(pub class Instance |py| { - data instance: Shell; +#[pyclass] +pub struct Instance { + instance: runtime::Instance, +} - def __new__(_cls, bytes: PyBytes) -> PyResult { - let bytes = bytes.data(py); +#[pymethods] +impl Instance { + #[new] + fn new(object: &PyRawObject, bytes: &PyAny) -> PyResult<()> { + let bytes = ::try_from(bytes)?.as_bytes(); let imports = imports! {}; let instance = match instantiate(bytes, &imports) { Ok(instance) => instance, - Err(e) => return Err(new_runtime_error(py, &format!("Failed to instantiate the module:\n {}", e))) - }; - - Instance::create_instance(py, Shell::new(instance)) - } - - def call(&self, function_name: &str, function_arguments: Vec = Vec::new()) -> PyResult { - let function_arguments: Vec = - function_arguments - .into_iter() - .map(|value_object| get_wasm_value(py, &value_object)) - .collect(); - - let instance = self.instance(py); - let function = match instance.dyn_func(function_name) { - Ok(function) => function, - Err(_) => return Err(new_runtime_error(py, &format!("Function `{}` does not exist.", function_name))) + Err(e) => return Err(RuntimeError::py_err(format!("Failed to instantiate the module:\n {}", e))), }; - let results = match function.call(function_arguments.as_slice()) { - Ok(results) => results, - Err(e) => return Err(new_runtime_error(py, &format!("{}", e))) - }; + object.init({ + Self { + instance + } + }); - Ok(wasm_value_into_python_object(py, &results[0])) + Ok(()) } - def uint8_memory_view(&self, offset: usize = 0) -> PyResult { - get_instance_memory(&self, py) +// fn call(&self, function_name: &str, function_arguments: Value) -> PyResult { +// /* +// let function_arguments: Vec = +// function_arguments +// .into_iter() +// .map(|value_object| value_object.value) +// .collect(); +// +// let instance = self.instance; +// let function = match instance.dyn_func(function_name) { +// Ok(function) => function, +// Err(_) => return Err(RuntimeError::py_err(format!("Function `{}` does not exist.", function_name))) +// }; +// +// let results = match function.call(function_arguments.as_slice()) { +// Ok(results) => results, +// Err(e) => return Err(RuntimeError::py_err(format!("{}", e))) +// }; +// */ +// +// Ok(42) //wasm_value_into_python_object(py, &results[0])) +// } + + #[args(offset=0)] + fn uint8_memory_view(&self, py: Python, offset: usize) -> PyResult> { + get_instance_memory(&self) .map_or_else( - || Err(new_runtime_error(py, "No memory exported.")), - |memory| Ok(memory_view::new_uint8_memory_view(py, memory, offset)) + || Err(RuntimeError::py_err("No memory exported.")), + |memory| { + Py::new(py, memory_view::Uint8MemoryView { memory, offset }) + } ) } - def int8_memory_view(&self, offset: usize = 0) -> PyResult { - get_instance_memory(&self, py) + #[args(offset=0)] + fn int8_memory_view(&self, py: Python, offset: usize) -> PyResult> { + get_instance_memory(&self) .map_or_else( - || Err(new_runtime_error(py, "No memory exported.")), - |memory| Ok(memory_view::new_int8_memory_view(py, memory, offset)) + || Err(RuntimeError::py_err("No memory exported.")), + |memory| { + Py::new(py, memory_view::Int8MemoryView { memory, offset }) + } ) } - def uint16_memory_view(&self, offset: usize = 0) -> PyResult { - get_instance_memory(&self, py) + #[args(offset=0)] + fn uint16_memory_view(&self, py: Python, offset: usize) -> PyResult> { + get_instance_memory(&self) .map_or_else( - || Err(new_runtime_error(py, "No memory exported.")), - |memory| Ok(memory_view::new_uint16_memory_view(py, memory, offset)) + || Err(RuntimeError::py_err("No memory exported.")), + |memory| { + Py::new(py, memory_view::Uint16MemoryView { memory, offset }) + } ) } - def int16_memory_view(&self, offset: usize = 0) -> PyResult { - get_instance_memory(&self, py) + #[args(offset=0)] + fn int16_memory_view(&self, py: Python, offset: usize) -> PyResult> { + get_instance_memory(&self) .map_or_else( - || Err(new_runtime_error(py, "No memory exported.")), - |memory| Ok(memory_view::new_int16_memory_view(py, memory, offset)) + || Err(RuntimeError::py_err("No memory exported.")), + |memory| { + Py::new(py, memory_view::Int16MemoryView { memory, offset }) + } ) } - def uint32_memory_view(&self, offset: usize = 0) -> PyResult { - get_instance_memory(&self, py) + #[args(offset=0)] + fn uint32_memory_view(&self, py: Python, offset: usize) -> PyResult> { + get_instance_memory(&self) .map_or_else( - || Err(new_runtime_error(py, "No memory exported.")), - |memory| Ok(memory_view::new_uint32_memory_view(py, memory, offset)) + || Err(RuntimeError::py_err("No memory exported.")), + |memory| { + Py::new(py, memory_view::Uint32MemoryView { memory, offset }) + } ) } - def int32_memory_view(&self, offset: usize = 0) -> PyResult { - get_instance_memory(&self, py) + #[args(offset=0)] + fn int32_memory_view(&self, py: Python, offset: usize) -> PyResult> { + get_instance_memory(&self) .map_or_else( - || Err(new_runtime_error(py, "No memory exported.")), - |memory| Ok(memory_view::new_int32_memory_view(py, memory, offset)) + || Err(RuntimeError::py_err("No memory exported.")), + |memory| { + Py::new(py, memory_view::Int32MemoryView { memory, offset }) + } ) } -}); - -/// The Python `validate` function. -/// -/// -pub fn validate(py: Python, bytes: PyBytes) -> PyResult { - Ok(wasm_validate(bytes.data(py))) } -fn get_instance_memory(instance: &Instance, py: Python) -> Option { +fn get_instance_memory(instance: &Instance) -> Option { instance - .instance(py) + .instance .exports() .find_map(|(_, export)| match export { Export::Memory(memory) => Some(memory), diff --git a/src/lib.rs b/src/lib.rs index d1dcad85..3409da29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,75 +1,44 @@ -#![deny(warnings)] +//#![deny(warnings)] -#[macro_use] -extern crate cpython; +use pyo3::{ + prelude::*, + PyTryFrom, + wrap_pyfunction, + types::{PyAny, PyBytes}, +}; +use wasmer_runtime::validate as wasm_validate; -use cpython::PyBytes; -use std::{ops::Deref, thread}; - -mod error; mod instance; mod memory_view; mod value; -use instance::{validate, Instance}; +use instance::Instance; use value::Value; -/// A `Shell` is a thread-safe wrapper over a value `T` that will fail -/// if used in another thread. Why? All data used by Python must be -/// thread-safe. However some WebAssembly data cannot be thread-safe, -/// like unshared memory. With a `Shell`, the program will compile and -/// Python will be able to use the value `T`, but it must not be -/// passed between threads. The documentation will specify it. -pub struct Shell { - /// The thread ID where the datum has been created. - thread_id: thread::ThreadId, +/// This extension allows to manipulate and to execute WebAssembly binaries. +#[pymodule] +fn wasmer(_py: Python, module: &PyModule) -> PyResult<()> { + module.add_wrapped(wrap_pyfunction!(validate))?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; - /// The datum. - value: T, + Ok(()) } -impl Shell { - pub fn new(value: T) -> Self { - Self { - thread_id: thread::current().id(), - value, - } +#[pyfunction] +/// validate(bytes, /) +/// -- +/// +/// Check a WebAssembly module is valid. +pub fn validate(bytes: &PyAny) -> PyResult { + match ::try_from(bytes) { + Ok(bytes) => Ok(wasm_validate(bytes.as_bytes())), + _ => Ok(false) } } - -/// A `Shell` is sendable. -unsafe impl Send for Shell {} - -/// Dereferences the value if it's inside the same thread than the -/// creation thread. -impl Deref for Shell { - type Target = T; - - fn deref(&self) -> &Self::Target { - if thread::current().id() != self.thread_id { - panic!("The current `Shell` cannot be dereferenced in a different thread."); - } - - &self.value - } -} - -// Declare the module. -py_module_initializer!(libwasmer, initlibwasmer, PyInit_wasmer, |python, module| { - module.add( - python, - "__doc__", - "This extension allows to manipulate and to execute WebAssembly binaries.", - )?; - module.add_class::(python)?; - module.add_class::(python)?; - module.add_class::(python)?; - module.add_class::(python)?; - module.add_class::(python)?; - module.add_class::(python)?; - module.add_class::(python)?; - module.add_class::(python)?; - module.add(python, "validate", py_fn!(python, validate(bytes: PyBytes)))?; - - Ok(()) -}); diff --git a/src/memory_view.rs b/src/memory_view.rs index 5da1de64..bdec2ca2 100644 --- a/src/memory_view.rs +++ b/src/memory_view.rs @@ -1,35 +1,49 @@ //! The `Buffer` Python object to build WebAssembly values. -use crate::{error::new_runtime_error, Shell}; -use cpython::{PyResult, Python}; use std::mem::size_of; +use pyo3::{ + prelude::*, + class::PySequenceProtocol, + exceptions::IndexError, +}; use wasmer_runtime::memory::Memory; macro_rules! memory_view { - ($class_name:ident over $wasm_type:ty [$bytes_per_element:expr], with $constructor_name:ident) => { - /// A `MemoryView` Python object represents a view over the memory - /// of a WebAssembly instance. - py_class!(pub class $class_name |py| { - static BYTES_PER_ELEMENT = $bytes_per_element; - - data memory: Shell; - data offset: usize; + ($class_name:ident over $wasm_type:ty | $bytes_per_element:expr) => { + #[pyclass] + pub struct $class_name { + pub memory: Memory, + pub offset: usize, + } - def __len__(&self) -> PyResult { - let offset = *self.offset(py); + #[pymethods] + impl $class_name { + #[getter] + fn bytes_per_element(&self) -> PyResult { + Ok($bytes_per_element) + } + } - Ok(self.memory(py).view::<$wasm_type>()[offset..].len() / size_of::<$wasm_type>()) + #[pyproto] + impl PySequenceProtocol for $class_name { + fn __len__(&self) -> PyResult { + Ok(self.memory.view::<$wasm_type>()[self.offset..].len() / size_of::<$wasm_type>()) } - def __getitem__(&self, index: usize) -> PyResult<$wasm_type> { - let offset = *self.offset(py); - let view = self.memory(py).view::<$wasm_type>(); + fn __getitem__(&self, index: isize) -> PyResult<$wasm_type> { + let offset = self.offset; + let view = self.memory.view::<$wasm_type>(); + + if index < 0 { + return Err(IndexError::py_err("Out of bound: Index cannot be negative.")) + } + + let index = index as usize; if view.len() <= offset + index { Err( - new_runtime_error( - py, - &format!( + IndexError::py_err( + format!( "Out of bound: Absolute index {} is larger than the memory size {}.", offset + index, view.len() @@ -41,15 +55,21 @@ macro_rules! memory_view { } } - def __setitem__(&self, index: usize, value: $wasm_type) -> PyResult<()> { - let offset = *self.offset(py); - let view = self.memory(py).view::<$wasm_type>(); + /* + fn __setitem__(&mut self, index: isize, value: u8) -> PyResult<()> { + let offset = self.offset; + let view = self.memory.view::(); + + if index < 0 { + return Err(IndexError::py_err("Out of bound: Index cannot be negative.")) + } + + let index = index as usize; if view.len() <= offset + index { Err( - new_runtime_error( - py, - &format!( + IndexError::py_err( + format!( "Out of bound: Absolute index {} is larger than the memory size {}.", offset + index, view.len() @@ -62,18 +82,14 @@ macro_rules! memory_view { Ok(()) } } - }); - - /// Construct a `MemoryView` Python object. - pub fn $constructor_name(py: Python, memory: Memory, offset: usize) -> $class_name { - $class_name::create_instance(py, Shell::new(memory), offset).unwrap() + */ } - }; + } } -memory_view!(Uint8MemoryView over u8 [1], with new_uint8_memory_view); -memory_view!(Int8MemoryView over i8 [1], with new_int8_memory_view); -memory_view!(Uint16MemoryView over u16 [2], with new_uint16_memory_view); -memory_view!(Int16MemoryView over i16 [2], with new_int16_memory_view); -memory_view!(Uint32MemoryView over u32 [4], with new_uint32_memory_view); -memory_view!(Int32MemoryView over i32 [4], with new_int32_memory_view); +memory_view!(Uint8MemoryView over u8|1); +memory_view!(Int8MemoryView over i8|1); +memory_view!(Uint16MemoryView over u16|2); +memory_view!(Int16MemoryView over i16|2); +memory_view!(Uint32MemoryView over u32|4); +memory_view!(Int32MemoryView over i32|4); diff --git a/src/value.rs b/src/value.rs index 44322677..510146f6 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,76 +1,43 @@ -//! The `Value` Python object to build WebAssembly values. +//! The `Value` Python class to build WebAssembly values. -use cpython::{PyObject, PyResult, Python, PythonObject, ToPyObject}; +use pyo3::{ + prelude::*, + class::basic::PyObjectProtocol, +}; use wasmer_runtime::Value as WasmValue; -/// The `Value` Python object represents a WebAssembly value. -/// -/// # Examples -/// -/// ```python,ignore -/// from wasmer import Value -/// -/// value1 = Value.from_i32(42) -/// value2 = Value.from_i64(42) -/// value3 = Value.from_f32(4.2) -/// value4 = Value.from_f64(4.2) -/// -/// print(repr(value1)) // "I32(42)" -/// print(repr(value2)) // "I64(42)" -/// print(repr(value3)) // "F32(4.2)" -/// print(repr(value4)) // "F64(4.2)" -/// ``` -py_class!(pub class Value |py| { - data value: WasmValue; - - @staticmethod - def i32(value: i32) -> PyResult { - Value::create_instance( - py, - WasmValue::I32(value) - ) - } +#[pyclass] +/// The `Value` class represents a WebAssembly value. +pub struct Value { + pub value: WasmValue, +} - @staticmethod - def i64(value: i64) -> PyResult { - Value::create_instance( - py, - WasmValue::I64(value) - ) +#[pymethods] +impl Value { + #[staticmethod] + fn i32(value: i32) -> PyResult { + Ok(Self { value: WasmValue::I32(value) }) } - @staticmethod - def f32(value: f32) -> PyResult { - Value::create_instance( - py, - WasmValue::F32(value) - ) + #[staticmethod] + fn i64(value: i64) -> PyResult { + Ok(Self { value: WasmValue::I64(value) }) } - @staticmethod - def f64(value: f64) -> PyResult { - Value::create_instance( - py, - WasmValue::F64(value) - ) + #[staticmethod] + fn f32(value: f32) -> PyResult { + Ok(Self { value: WasmValue::F32(value) }) } - def __repr__(&self) -> PyResult { - Ok(format!("{:?}", self.value(py))) + #[staticmethod] + fn f64(value: f64) -> PyResult { + Ok(Self { value: WasmValue::F64(value) }) } -}); - -/// Getter to access the private `value` attribute of the `Value` Python object. -pub(crate) fn get_wasm_value(py: Python, value: &Value) -> WasmValue { - value.value(py).clone() } -/// Transform a `WasmValue` into a `PyObject`. -pub(crate) fn wasm_value_into_python_object(py: Python, wasm_value: &WasmValue) -> PyObject { - match wasm_value { - WasmValue::I32(value) => value.into_py_object(py).into_object(), - WasmValue::I64(value) => value.into_py_object(py).into_object(), - WasmValue::F32(value) => value.into_py_object(py).into_object(), - WasmValue::F64(value) => value.into_py_object(py).into_object(), +#[pyproto] +impl PyObjectProtocol for Value { + fn __repr__(&self) -> PyResult { + Ok(format!("{:?}", self.value)) } } From 9d3f7aba9db825643fb84a44eff3187ab189f7e3 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Sat, 13 Apr 2019 12:49:12 -0700 Subject: [PATCH 02/15] !temp --- src/instance.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/instance.rs b/src/instance.rs index b8953a3c..62ece94c 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -10,10 +10,12 @@ use crate::memory_view; use pyo3::{ prelude::*, - types::{PyAny, PyBytes}, + types::{PyAny, PyBytes, PyTuple, PyDict}, PyTryFrom, exceptions::RuntimeError, + PyNativeType, }; +use std::rc::Rc; use wasmer_runtime::{ self as runtime, imports, @@ -22,9 +24,26 @@ use wasmer_runtime::{ Memory, }; +#[pyclass] +pub struct ExportedFunction { + function_name: String, + instance: Rc, +} + +#[pymethods] +impl ExportedFunction { + #[call] + #[args(args="*")] + fn __call__(&self, args: &PyTuple) -> PyResult { + println!("exported function has been called {:?}", args); + Ok(self.function_name.clone()) + } +} + #[pyclass] pub struct Instance { - instance: runtime::Instance, + instance: Rc, + exports: PyObject, } #[pymethods] @@ -34,19 +53,43 @@ impl Instance { let bytes = ::try_from(bytes)?.as_bytes(); let imports = imports! {}; let instance = match instantiate(bytes, &imports) { - Ok(instance) => instance, + Ok(instance) => Rc::new(instance), Err(e) => return Err(RuntimeError::py_err(format!("Failed to instantiate the module:\n {}", e))), }; + let py = object.py(); + + let dict = PyDict::new(py); + let function_name = String::from("sum"); + dict.set_item( + function_name.clone(), + Py::new( + py, + ExportedFunction { + function_name, + instance: instance.clone() + } + )? + )?; + object.init({ Self { - instance + instance, + exports: dict.to_object(py), } }); Ok(()) } + #[getter] + fn exports(&self) -> PyResult<&PyDict> { + let gil = Python::acquire_gil(); + let py = gil.python(); + + Ok(self.exports.cast_as::(py)?) + } + // fn call(&self, function_name: &str, function_arguments: Value) -> PyResult { // /* // let function_arguments: Vec = From a62cd983b04f771db4a84f20be0629a0ec139e13 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Sat, 13 Apr 2019 13:38:55 -0700 Subject: [PATCH 03/15] !temp --- Cargo.lock | 38 ++++++++-------- Cargo.toml | 3 +- examples/simple.py | 7 +-- src/instance.rs | 105 ++++++++++++++++++++++++++++++++------------- 4 files changed, 98 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 83a2d3a5..6ae31bbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1059,13 +1059,14 @@ name = "wasmer" version = "0.1.4" dependencies = [ "pyo3 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-runtime 0.2.1 (git+https://github.com/wasmerio/wasmer)", + "wasmer-runtime 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-runtime-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasmer-clif-backend" -version = "0.2.0" -source = "git+https://github.com/wasmerio/wasmer#870faf983868b5c017537df49d2fc5ab9c0e1d28" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "cranelift-codegen 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1081,27 +1082,27 @@ dependencies = [ "serde_bytes 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "target-lexicon 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-runtime-core 0.2.1 (git+https://github.com/wasmerio/wasmer)", - "wasmer-win-exception-handler 0.2.0 (git+https://github.com/wasmerio/wasmer)", + "wasmer-runtime-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-win-exception-handler 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasmer-runtime" -version = "0.2.1" -source = "git+https://github.com/wasmerio/wasmer#870faf983868b5c017537df49d2fc5ab9c0e1d28" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-backend 0.2.0 (git+https://github.com/wasmerio/wasmer)", - "wasmer-runtime-core 0.2.1 (git+https://github.com/wasmerio/wasmer)", + "wasmer-clif-backend 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-runtime-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasmer-runtime-core" -version = "0.2.1" -source = "git+https://github.com/wasmerio/wasmer#870faf983868b5c017537df49d2fc5ab9c0e1d28" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "blake2b_simd 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1114,6 +1115,7 @@ dependencies = [ "nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "page_size 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde-bench 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "serde_bytes 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1124,14 +1126,14 @@ dependencies = [ [[package]] name = "wasmer-win-exception-handler" -version = "0.2.0" -source = "git+https://github.com/wasmerio/wasmer#870faf983868b5c017537df49d2fc5ab9c0e1d28" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bindgen 0.46.0 (registry+https://github.com/rust-lang/crates.io-index)", "cmake 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-runtime-core 0.2.1 (git+https://github.com/wasmerio/wasmer)", + "wasmer-runtime-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1330,10 +1332,10 @@ dependencies = [ "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum wasmer-clif-backend 0.2.0 (git+https://github.com/wasmerio/wasmer)" = "" -"checksum wasmer-runtime 0.2.1 (git+https://github.com/wasmerio/wasmer)" = "" -"checksum wasmer-runtime-core 0.2.1 (git+https://github.com/wasmerio/wasmer)" = "" -"checksum wasmer-win-exception-handler 0.2.0 (git+https://github.com/wasmerio/wasmer)" = "" +"checksum wasmer-clif-backend 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0afacbd83b3897e59ac7686ebeed7c39bfae20bcf150869eb695e55afe19e91a" +"checksum wasmer-runtime 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "272c74d8494e4c59dba5df3a256e7661d1684ad7d8b425b80e9fe80124def568" +"checksum wasmer-runtime-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b9fd8e1f3c4eac86563394354aee2b8dfc59a8b9f7051478dfb27bff568b668" +"checksum wasmer-win-exception-handler 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46e37084fc382f419fa06e09ffd388aa1e2eaeeb6bfa3b8edd311f3f857751e9" "checksum wasmparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5e01c420bc7d36e778bd242e1167b079562ba8b34087122cc9057187026d060" "checksum wasmparser 0.29.2 (registry+https://github.com/rust-lang/crates.io-index)" = "981a8797cf89762e0233ec45fae731cb79a4dfaee12d9f0fe6cee01e4ac58d00" "checksum which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b57acb10231b9493c8472b20cb57317d0679a49e0bdbee44b3b803a6473af164" diff --git a/Cargo.toml b/Cargo.toml index c92ac8d0..eb97c047 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ name = "wasmer" crate-type = ["cdylib"] [dependencies] -wasmer-runtime = { git = "https://github.com/wasmerio/wasmer", branch = "master" } +wasmer-runtime = "0.3.0" +wasmer-runtime-core = "0.3.0" pyo3 = { version = "0.6.0", features = ["extension-module"] } [package.metadata.pyo3-pack] diff --git a/examples/simple.py b/examples/simple.py index 202cdfaf..ba0320c4 100644 --- a/examples/simple.py +++ b/examples/simple.py @@ -5,11 +5,8 @@ wasm_bytes = open(__dir__ + '/simple.wasm', 'rb').read() instance = Instance(wasm_bytes) -memory = instance.uint8_memory_view() -print(len(memory)) -print(memory[42]) -print(memory.BYTES_PER_ELEMENT) -print(Uint8MemoryView.BYTES_PER_ELEMENT) +print(instance.exports) +print(instance.exports['sum'](1, 4)) #result = instance.call('sum', [Value.i32(5), Value.i32(37)]) #print(result) # 42! diff --git a/src/instance.rs b/src/instance.rs index 62ece94c..4955a395 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -7,10 +7,10 @@ // Shell, //}; //use cpython::{PyBytes, PyObject, PyResult, Python}; -use crate::memory_view; +use crate::{memory_view, value::Value}; use pyo3::{ prelude::*, - types::{PyAny, PyBytes, PyTuple, PyDict}, + types::{PyAny, PyBytes, PyTuple, PyDict, PyLong, PyFloat}, PyTryFrom, exceptions::RuntimeError, PyNativeType, @@ -22,7 +22,9 @@ use wasmer_runtime::{ instantiate, Export, Memory, + Value as WasmValue, }; +use wasmer_runtime_core::types::Type; #[pyclass] pub struct ExportedFunction { @@ -32,12 +34,76 @@ pub struct ExportedFunction { #[pymethods] impl ExportedFunction { - #[call] - #[args(args="*")] - fn __call__(&self, args: &PyTuple) -> PyResult { - println!("exported function has been called {:?}", args); - Ok(self.function_name.clone()) - } + #[call] + #[args(arguments="*")] + fn __call__(&self, py: Python, arguments: &PyTuple) -> PyResult { + let function = match self.instance.dyn_func(&self.function_name) { + Ok(function) => function, + Err(_) => return Err(RuntimeError::py_err(format!("Function `{}` does not exist.", self.function_name))) + }; + + let signature = function.signature(); + let parameters = signature.params(); + let number_of_parameters = parameters.len() as isize; + let number_of_arguments = arguments.len() as isize; + let diff: isize = number_of_parameters - number_of_arguments; + + if diff > 0 { + return Err( + RuntimeError::py_err( + format!( + "Missing {} argument(s) when calling `{}`: Expect {} argument(s), given {}.", + diff, + self.function_name, + number_of_parameters, + number_of_arguments + ) + ) + ); + } else if diff < 0 { + return Err( + RuntimeError::py_err( + format!( + "Given {} extra argument(s) when calling `{}`: Expect {} argument(s), given {}.", + diff.abs(), + self.function_name, + number_of_parameters, + number_of_arguments + ) + ) + ); + } + + let mut function_arguments = Vec::::with_capacity(number_of_parameters as usize); + + for (parameter, argument) in parameters.iter().zip(arguments.iter()) { + let value = match argument.downcast_ref::() { + Ok(value) => value.value.clone(), + Err(_) => match parameter { + Type::I32 => WasmValue::I32(argument.downcast_ref::()?.extract::()?), + Type::I64 => WasmValue::I64(argument.downcast_ref::()?.extract::()?), + Type::F32 => WasmValue::F32(argument.downcast_ref::()?.extract::()?), + Type::F64 => WasmValue::F64(argument.downcast_ref::()?.extract::()?), + }, + }; + + function_arguments.push(value); + } + + let results = match function.call(function_arguments.as_slice()) { + Ok(results) => results, + Err(e) => return Err(RuntimeError::py_err(format!("{}", e))), + }; + + Ok( + match results[0] { + WasmValue::I32(result) => result.to_object(py), + WasmValue::I64(result) => result.to_object(py), + WasmValue::F32(result) => result.to_object(py), + WasmValue::F64(result) => result.to_object(py), + } + ) + } } #[pyclass] @@ -90,29 +156,6 @@ impl Instance { Ok(self.exports.cast_as::(py)?) } -// fn call(&self, function_name: &str, function_arguments: Value) -> PyResult { -// /* -// let function_arguments: Vec = -// function_arguments -// .into_iter() -// .map(|value_object| value_object.value) -// .collect(); -// -// let instance = self.instance; -// let function = match instance.dyn_func(function_name) { -// Ok(function) => function, -// Err(_) => return Err(RuntimeError::py_err(format!("Function `{}` does not exist.", function_name))) -// }; -// -// let results = match function.call(function_arguments.as_slice()) { -// Ok(results) => results, -// Err(e) => return Err(RuntimeError::py_err(format!("{}", e))) -// }; -// */ -// -// Ok(42) //wasm_value_into_python_object(py, &results[0])) -// } - #[args(offset=0)] fn uint8_memory_view(&self, py: Python, offset: usize) -> PyResult> { get_instance_memory(&self) From 609c945f1443525f339206161e4b4de765d0ae8c Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Sat, 13 Apr 2019 13:43:41 -0700 Subject: [PATCH 04/15] !temp --- examples/simple.py | 6 ++---- src/instance.rs | 27 +++++++++++++++------------ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/examples/simple.py b/examples/simple.py index ba0320c4..e1ba7e7e 100644 --- a/examples/simple.py +++ b/examples/simple.py @@ -5,8 +5,6 @@ wasm_bytes = open(__dir__ + '/simple.wasm', 'rb').read() instance = Instance(wasm_bytes) -print(instance.exports) -print(instance.exports['sum'](1, 4)) -#result = instance.call('sum', [Value.i32(5), Value.i32(37)]) +result = instance.exports['sum'](5, 37) -#print(result) # 42! +print(result) # 42! diff --git a/src/instance.rs b/src/instance.rs index 4955a395..5b5b4b58 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -124,19 +124,22 @@ impl Instance { }; let py = object.py(); - let dict = PyDict::new(py); - let function_name = String::from("sum"); - dict.set_item( - function_name.clone(), - Py::new( - py, - ExportedFunction { - function_name, - instance: instance.clone() - } - )? - )?; + + for (export_name, export) in instance.exports() { + if let Export::Function { .. } = export { + dict.set_item( + export_name.clone(), + Py::new( + py, + ExportedFunction { + function_name: export_name, + instance: instance.clone() + } + )? + )?; + } + } object.init({ Self { From 9727d6e5dda8d16fe81e99f31580e33b7acfd18a Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Sat, 13 Apr 2019 13:57:25 -0700 Subject: [PATCH 05/15] !temp --- examples/memory.py | 2 +- tests/instance.py | 43 +++++++---------------- tests/memory_view.py | 82 ++++++++++++++++++++++---------------------- 3 files changed, 54 insertions(+), 73 deletions(-) diff --git a/examples/memory.py b/examples/memory.py index 6a55f71e..35d90a3d 100644 --- a/examples/memory.py +++ b/examples/memory.py @@ -5,7 +5,7 @@ wasm_bytes = open(__dir__ + '/memory.wasm', 'rb').read() instance = Instance(wasm_bytes) -pointer = instance.call('return_hello') +pointer = instance.exports['return_hello']() memory = instance.uint8_memory_view(pointer) nth = 0; diff --git a/tests/instance.py b/tests/instance.py index 7dd6699d..8c4e1927 100644 --- a/tests/instance.py +++ b/tests/instance.py @@ -25,85 +25,66 @@ def test_failed_to_instantiate(self): ) def test_function_does_not_exist(self): - with self.assertRaises(RuntimeError) as context_manager: - Instance(TEST_BYTES).call("foo") + with self.assertRaises(KeyError) as context_manager: + Instance(TEST_BYTES).exports['foo'] exception = context_manager.exception self.assertEqual( str(exception), - 'Function `foo` does not exist.' + "'foo'" ) def test_basic_sum(self): self.assertEqual( - Instance(TEST_BYTES) - .call( - 'sum', - [ - Value.i32(1), - Value.i32(2) - ] - ), + Instance(TEST_BYTES).exports['sum'](1, 2), 3 ) def test_call_arity_0(self): self.assertEqual( - Instance(TEST_BYTES).call('arity_0'), + Instance(TEST_BYTES).exports['arity_0'](), 42 ) def test_call_i32_i32(self): self.assertEqual( - Instance(TEST_BYTES).call('i32_i32', [Value.i32(7)]), + Instance(TEST_BYTES).exports['i32_i32'](7), 7 ) def test_call_i64_i64(self): self.assertEqual( - Instance(TEST_BYTES).call('i64_i64', [Value.i64(7)]), + Instance(TEST_BYTES).exports['i64_i64'](7), 7 ) def test_call_f32_f32(self): self.assertEqual( - Instance(TEST_BYTES).call('f32_f32', [Value.f32(7.)]), + Instance(TEST_BYTES).exports['f32_f32'](7.), 7. ) def test_call_f64_f64(self): self.assertEqual( - Instance(TEST_BYTES).call('f64_f64', [Value.f64(7.)]), + Instance(TEST_BYTES).exports['f64_f64'](7.), 7. ) def test_call_i32_i64_f32_f64_f64(self): self.assertEqual( - round( - Instance(TEST_BYTES) - .call( - 'i32_i64_f32_f64_f64', - [ - Value.i32(1), - Value.i64(2), - Value.f32(3.4), - Value.f64(5.6) - ] - ), - 6 - ), + round(Instance(TEST_BYTES).exports['i32_i64_f32_f64_f64'](1, 2, 3.4, 5.6), 6), 1 + 2 + 3.4 + 5.6 ) def test_call_bool_casted_to_i32(self): self.assertEqual( - Instance(TEST_BYTES).call('bool_casted_to_i32'), + Instance(TEST_BYTES).exports['bool_casted_to_i32'](), 1 ) def test_call_string(self): self.assertEqual( - Instance(TEST_BYTES).call('string'), + Instance(TEST_BYTES).exports['string'](), 1048576 ) diff --git a/tests/memory_view.py b/tests/memory_view.py index e8eadf57..0a664aac 100644 --- a/tests/memory_view.py +++ b/tests/memory_view.py @@ -16,12 +16,12 @@ def test_is_a_class(self): self.assertTrue(inspect.isclass(Int32MemoryView)) def test_bytes_per_element(self): - self.assertEqual(Uint8MemoryView.BYTES_PER_ELEMENT, 1) - self.assertEqual(Int8MemoryView.BYTES_PER_ELEMENT, 1) - self.assertEqual(Uint16MemoryView.BYTES_PER_ELEMENT, 2) - self.assertEqual(Int16MemoryView.BYTES_PER_ELEMENT, 2) - self.assertEqual(Uint32MemoryView.BYTES_PER_ELEMENT, 4) - self.assertEqual(Int32MemoryView.BYTES_PER_ELEMENT, 4) + self.assertEqual(Instance(TEST_BYTES).uint8_memory_view().bytes_per_element, 1) + self.assertEqual(Instance(TEST_BYTES).int8_memory_view().bytes_per_element, 1) + self.assertEqual(Instance(TEST_BYTES).uint16_memory_view().bytes_per_element, 2) + self.assertEqual(Instance(TEST_BYTES).int16_memory_view().bytes_per_element, 2) + self.assertEqual(Instance(TEST_BYTES).uint32_memory_view().bytes_per_element, 4) + self.assertEqual(Instance(TEST_BYTES).int32_memory_view().bytes_per_element, 4) @unittest.expectedFailure def test_cannot_construct(self): @@ -33,16 +33,16 @@ def test_length(self): 1114112 ) - def test_get(self): - memory = Instance(TEST_BYTES).uint8_memory_view() - index = 7 - value = 42 - memory[index] = value + #def test_get(self): + # memory = Instance(TEST_BYTES).uint8_memory_view() + # index = 7 + # value = 42 + # memory[index] = value - self.assertEqual(memory[index], value) + # self.assertEqual(memory[index], value) def test_get_out_of_range(self): - with self.assertRaises(RuntimeError) as context_manager: + with self.assertRaises(IndexError) as context_manager: memory = Instance(TEST_BYTES).uint8_memory_view() memory[len(memory) + 1] @@ -52,20 +52,20 @@ def test_get_out_of_range(self): 'Out of bound: Absolute index 1114113 is larger than the memory size 1114112.' ) - def test_set_out_of_range(self): - with self.assertRaises(RuntimeError) as context_manager: - memory = Instance(TEST_BYTES).uint8_memory_view() - memory[len(memory) + 1] = 42 + #def test_set_out_of_range(self): + # with self.assertRaises(IndexError) as context_manager: + # memory = Instance(TEST_BYTES).uint8_memory_view() + # memory[len(memory) + 1] = 42 - exception = context_manager.exception - self.assertEqual( - str(exception), - 'Out of bound: Absolute index 1114113 is larger than the memory size 1114112.' - ) + # exception = context_manager.exception + # self.assertEqual( + # str(exception), + # 'Out of bound: Absolute index 1114113 is larger than the memory size 1114112.' + # ) def test_hello_world(self): instance = Instance(TEST_BYTES) - pointer = instance.call('string') + pointer = instance.exports['string']() memory = instance.uint8_memory_view(pointer) nth = 0 string = '' @@ -76,21 +76,21 @@ def test_hello_world(self): self.assertEqual(string, 'Hello, World!') - def test_memory_views_share_the_same_buffer(self): - instance = Instance(TEST_BYTES) - int8 = instance.int8_memory_view() - int16 = instance.int16_memory_view() - int32 = instance.int32_memory_view() - - int8[0] = 0b00000001 - int8[1] = 0b00000100 - int8[2] = 0b00010000 - int8[3] = 0b01000000 - - self.assertEqual(int8[0], 0b00000001) - self.assertEqual(int8[1], 0b00000100) - self.assertEqual(int8[2], 0b00010000) - self.assertEqual(int8[3], 0b01000000) - self.assertEqual(int16[0], 0b00000100_00000001) - self.assertEqual(int16[1], 0b01000000_00010000) - self.assertEqual(int32[0], 0b01000000_00010000_00000100_00000001) + #def test_memory_views_share_the_same_buffer(self): + # instance = Instance(TEST_BYTES) + # int8 = instance.int8_memory_view() + # int16 = instance.int16_memory_view() + # int32 = instance.int32_memory_view() + + # int8[0] = 0b00000001 + # int8[1] = 0b00000100 + # int8[2] = 0b00010000 + # int8[3] = 0b01000000 + + # self.assertEqual(int8[0], 0b00000001) + # self.assertEqual(int8[1], 0b00000100) + # self.assertEqual(int8[2], 0b00010000) + # self.assertEqual(int8[3], 0b01000000) + # self.assertEqual(int16[0], 0b00000100_00000001) + # self.assertEqual(int16[1], 0b01000000_00010000) + # self.assertEqual(int32[0], 0b01000000_00010000_00000100_00000001) From 06c429bf3dbe588d5f9ec807263e0d4523fbb8b2 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Sat, 13 Apr 2019 14:03:46 -0700 Subject: [PATCH 06/15] doc(readme) Update the export functions API. --- README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 785073ad..62b21266 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ from wasmer import Instance, Value wasm_bytes = open('simple.wasm', 'rb').read() instance = Instance(wasm_bytes) -result = instance.call('sum', [Value.i32(5), Value.i32(37)]) +result = instance.exports['sum'](5, 37) print(result) # 42! ``` @@ -88,11 +88,19 @@ wasm_bytes = open('my_program.wasm', 'rb').read() instance = Instance(wasm_bytes) # Call a function on it. -result = instance.call('sum', [Value.i32(1), Value.i32(2)]) +result = instance.exports['sum'](1, 2) print(result) # 3 ``` +All exported functions are accessible in the `exports` +dictionnary. Each value of this dictionnary is a function. Arguments +of these functions are automatically casted to WebAssembly value. If +one wants to explicitely pass a value of a particular type, it is +possible to use the `Value` class, +e.g. `instance.exports['sum'](Value.i32(1), Value.i32(2))`. Note that +for most usecases, this is not necessary. + ### The `Value` class Builds WebAssembly values with the correct types: @@ -141,7 +149,8 @@ All these classes share the same implementation. Taking the example of ```python class Uint8MemoryView: - BYTES_PER_ELEMENT = 1 + @property + def bytes_per_element() def __len__() def __getitem__(index) @@ -160,7 +169,7 @@ wasm_bytes = open('my_program.wasm', 'rb').read() instance = Instance(wasm_bytes) # Call a function that returns a pointer to a string for instance. -pointer = instance.call('return_string') +pointer = instance.exports['return_string']() # Get the memory view, with the offset set to `pointer` (default is 0). memory = instance.uint8_memory_view(pointer) From c57b45562c8441596a63b2b2650e62a8d2731a75 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Sat, 13 Apr 2019 14:21:34 -0700 Subject: [PATCH 07/15] chore(fmt) Run `rustfmt`. --- src/instance.rs | 222 ++++++++++++++++++++++----------------------- src/lib.rs | 9 +- src/memory_view.rs | 44 ++++----- src/value.rs | 21 +++-- 4 files changed, 144 insertions(+), 152 deletions(-) diff --git a/src/instance.rs b/src/instance.rs index 5b5b4b58..ec9af40d 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,29 +1,14 @@ //! The `Instance` Python object to build WebAssembly instances. -//use crate::{ -// error::new_runtime_error, -// memory_view, -// value::{get_wasm_value, wasm_value_into_python_object, Value}, -// Shell, -//}; -//use cpython::{PyBytes, PyObject, PyResult, Python}; use crate::{memory_view, value::Value}; use pyo3::{ - prelude::*, - types::{PyAny, PyBytes, PyTuple, PyDict, PyLong, PyFloat}, - PyTryFrom, exceptions::RuntimeError, - PyNativeType, + prelude::*, + types::{PyAny, PyBytes, PyDict, PyFloat, PyLong, PyTuple}, + PyNativeType, PyTryFrom, }; use std::rc::Rc; -use wasmer_runtime::{ - self as runtime, - imports, - instantiate, - Export, - Memory, - Value as WasmValue, -}; +use wasmer_runtime::{self as runtime, imports, instantiate, Export, Memory, Value as WasmValue}; use wasmer_runtime_core::types::Type; #[pyclass] @@ -35,11 +20,16 @@ pub struct ExportedFunction { #[pymethods] impl ExportedFunction { #[call] - #[args(arguments="*")] + #[args(arguments = "*")] fn __call__(&self, py: Python, arguments: &PyTuple) -> PyResult { let function = match self.instance.dyn_func(&self.function_name) { Ok(function) => function, - Err(_) => return Err(RuntimeError::py_err(format!("Function `{}` does not exist.", self.function_name))) + Err(_) => { + return Err(RuntimeError::py_err(format!( + "Function `{}` does not exist.", + self.function_name + ))) + } }; let signature = function.signature(); @@ -49,29 +39,18 @@ impl ExportedFunction { let diff: isize = number_of_parameters - number_of_arguments; if diff > 0 { - return Err( - RuntimeError::py_err( - format!( - "Missing {} argument(s) when calling `{}`: Expect {} argument(s), given {}.", - diff, - self.function_name, - number_of_parameters, - number_of_arguments - ) - ) - ); + return Err(RuntimeError::py_err(format!( + "Missing {} argument(s) when calling `{}`: Expect {} argument(s), given {}.", + diff, self.function_name, number_of_parameters, number_of_arguments + ))); } else if diff < 0 { - return Err( - RuntimeError::py_err( - format!( - "Given {} extra argument(s) when calling `{}`: Expect {} argument(s), given {}.", - diff.abs(), - self.function_name, - number_of_parameters, - number_of_arguments - ) - ) - ); + return Err(RuntimeError::py_err(format!( + "Given {} extra argument(s) when calling `{}`: Expect {} argument(s), given {}.", + diff.abs(), + self.function_name, + number_of_parameters, + number_of_arguments + ))); } let mut function_arguments = Vec::::with_capacity(number_of_parameters as usize); @@ -80,10 +59,18 @@ impl ExportedFunction { let value = match argument.downcast_ref::() { Ok(value) => value.value.clone(), Err(_) => match parameter { - Type::I32 => WasmValue::I32(argument.downcast_ref::()?.extract::()?), - Type::I64 => WasmValue::I64(argument.downcast_ref::()?.extract::()?), - Type::F32 => WasmValue::F32(argument.downcast_ref::()?.extract::()?), - Type::F64 => WasmValue::F64(argument.downcast_ref::()?.extract::()?), + Type::I32 => { + WasmValue::I32(argument.downcast_ref::()?.extract::()?) + } + Type::I64 => { + WasmValue::I64(argument.downcast_ref::()?.extract::()?) + } + Type::F32 => { + WasmValue::F32(argument.downcast_ref::()?.extract::()?) + } + Type::F64 => { + WasmValue::F64(argument.downcast_ref::()?.extract::()?) + } }, }; @@ -95,14 +82,12 @@ impl ExportedFunction { Err(e) => return Err(RuntimeError::py_err(format!("{}", e))), }; - Ok( - match results[0] { - WasmValue::I32(result) => result.to_object(py), - WasmValue::I64(result) => result.to_object(py), - WasmValue::F32(result) => result.to_object(py), - WasmValue::F64(result) => result.to_object(py), - } - ) + Ok(match results[0] { + WasmValue::I32(result) => result.to_object(py), + WasmValue::I64(result) => result.to_object(py), + WasmValue::F32(result) => result.to_object(py), + WasmValue::F64(result) => result.to_object(py), + }) } } @@ -120,7 +105,12 @@ impl Instance { let imports = imports! {}; let instance = match instantiate(bytes, &imports) { Ok(instance) => Rc::new(instance), - Err(e) => return Err(RuntimeError::py_err(format!("Failed to instantiate the module:\n {}", e))), + Err(e) => { + return Err(RuntimeError::py_err(format!( + "Failed to instantiate the module:\n {}", + e + ))) + } }; let py = object.py(); @@ -134,9 +124,9 @@ impl Instance { py, ExportedFunction { function_name: export_name, - instance: instance.clone() - } - )? + instance: instance.clone(), + }, + )?, )?; } } @@ -159,70 +149,76 @@ impl Instance { Ok(self.exports.cast_as::(py)?) } - #[args(offset=0)] - fn uint8_memory_view(&self, py: Python, offset: usize) -> PyResult> { - get_instance_memory(&self) - .map_or_else( - || Err(RuntimeError::py_err("No memory exported.")), - |memory| { - Py::new(py, memory_view::Uint8MemoryView { memory, offset }) - } - ) + #[args(offset = 0)] + fn uint8_memory_view( + &self, + py: Python, + offset: usize, + ) -> PyResult> { + get_instance_memory(&self).map_or_else( + || Err(RuntimeError::py_err("No memory exported.")), + |memory| Py::new(py, memory_view::Uint8MemoryView { memory, offset }), + ) } - #[args(offset=0)] - fn int8_memory_view(&self, py: Python, offset: usize) -> PyResult> { - get_instance_memory(&self) - .map_or_else( - || Err(RuntimeError::py_err("No memory exported.")), - |memory| { - Py::new(py, memory_view::Int8MemoryView { memory, offset }) - } - ) + #[args(offset = 0)] + fn int8_memory_view( + &self, + py: Python, + offset: usize, + ) -> PyResult> { + get_instance_memory(&self).map_or_else( + || Err(RuntimeError::py_err("No memory exported.")), + |memory| Py::new(py, memory_view::Int8MemoryView { memory, offset }), + ) } - #[args(offset=0)] - fn uint16_memory_view(&self, py: Python, offset: usize) -> PyResult> { - get_instance_memory(&self) - .map_or_else( - || Err(RuntimeError::py_err("No memory exported.")), - |memory| { - Py::new(py, memory_view::Uint16MemoryView { memory, offset }) - } - ) + #[args(offset = 0)] + fn uint16_memory_view( + &self, + py: Python, + offset: usize, + ) -> PyResult> { + get_instance_memory(&self).map_or_else( + || Err(RuntimeError::py_err("No memory exported.")), + |memory| Py::new(py, memory_view::Uint16MemoryView { memory, offset }), + ) } - #[args(offset=0)] - fn int16_memory_view(&self, py: Python, offset: usize) -> PyResult> { - get_instance_memory(&self) - .map_or_else( - || Err(RuntimeError::py_err("No memory exported.")), - |memory| { - Py::new(py, memory_view::Int16MemoryView { memory, offset }) - } - ) + #[args(offset = 0)] + fn int16_memory_view( + &self, + py: Python, + offset: usize, + ) -> PyResult> { + get_instance_memory(&self).map_or_else( + || Err(RuntimeError::py_err("No memory exported.")), + |memory| Py::new(py, memory_view::Int16MemoryView { memory, offset }), + ) } - #[args(offset=0)] - fn uint32_memory_view(&self, py: Python, offset: usize) -> PyResult> { - get_instance_memory(&self) - .map_or_else( - || Err(RuntimeError::py_err("No memory exported.")), - |memory| { - Py::new(py, memory_view::Uint32MemoryView { memory, offset }) - } - ) + #[args(offset = 0)] + fn uint32_memory_view( + &self, + py: Python, + offset: usize, + ) -> PyResult> { + get_instance_memory(&self).map_or_else( + || Err(RuntimeError::py_err("No memory exported.")), + |memory| Py::new(py, memory_view::Uint32MemoryView { memory, offset }), + ) } - #[args(offset=0)] - fn int32_memory_view(&self, py: Python, offset: usize) -> PyResult> { - get_instance_memory(&self) - .map_or_else( - || Err(RuntimeError::py_err("No memory exported.")), - |memory| { - Py::new(py, memory_view::Int32MemoryView { memory, offset }) - } - ) + #[args(offset = 0)] + fn int32_memory_view( + &self, + py: Python, + offset: usize, + ) -> PyResult> { + get_instance_memory(&self).map_or_else( + || Err(RuntimeError::py_err("No memory exported.")), + |memory| Py::new(py, memory_view::Int32MemoryView { memory, offset }), + ) } } diff --git a/src/lib.rs b/src/lib.rs index 3409da29..21132bf0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,9 @@ -//#![deny(warnings)] +#![deny(warnings)] use pyo3::{ prelude::*, - PyTryFrom, - wrap_pyfunction, types::{PyAny, PyBytes}, + wrap_pyfunction, PyTryFrom, }; use wasmer_runtime::validate as wasm_validate; @@ -31,14 +30,14 @@ fn wasmer(_py: Python, module: &PyModule) -> PyResult<()> { Ok(()) } -#[pyfunction] /// validate(bytes, /) /// -- /// /// Check a WebAssembly module is valid. +#[pyfunction] pub fn validate(bytes: &PyAny) -> PyResult { match ::try_from(bytes) { Ok(bytes) => Ok(wasm_validate(bytes.as_bytes())), - _ => Ok(false) + _ => Ok(false), } } diff --git a/src/memory_view.rs b/src/memory_view.rs index bdec2ca2..eba3e39c 100644 --- a/src/memory_view.rs +++ b/src/memory_view.rs @@ -1,11 +1,7 @@ //! The `Buffer` Python object to build WebAssembly values. +use pyo3::{class::PySequenceProtocol, exceptions::IndexError, prelude::*}; use std::mem::size_of; -use pyo3::{ - prelude::*, - class::PySequenceProtocol, - exceptions::IndexError, -}; use wasmer_runtime::memory::Memory; macro_rules! memory_view { @@ -35,21 +31,19 @@ macro_rules! memory_view { let view = self.memory.view::<$wasm_type>(); if index < 0 { - return Err(IndexError::py_err("Out of bound: Index cannot be negative.")) + return Err(IndexError::py_err( + "Out of bound: Index cannot be negative.", + )); } let index = index as usize; if view.len() <= offset + index { - Err( - IndexError::py_err( - format!( - "Out of bound: Absolute index {} is larger than the memory size {}.", - offset + index, - view.len() - ) - ) - ) + Err(IndexError::py_err(format!( + "Out of bound: Absolute index {} is larger than the memory size {}.", + offset + index, + view.len() + ))) } else { Ok(view[offset + index].get()) } @@ -61,21 +55,19 @@ macro_rules! memory_view { let view = self.memory.view::(); if index < 0 { - return Err(IndexError::py_err("Out of bound: Index cannot be negative.")) + return Err(IndexError::py_err( + "Out of bound: Index cannot be negative.", + )); } let index = index as usize; if view.len() <= offset + index { - Err( - IndexError::py_err( - format!( - "Out of bound: Absolute index {} is larger than the memory size {}.", - offset + index, - view.len() - ) - ) - ) + Err(IndexError::py_err(format!( + "Out of bound: Absolute index {} is larger than the memory size {}.", + offset + index, + view.len() + ))) } else { view[offset + index].set(value); @@ -84,7 +76,7 @@ macro_rules! memory_view { } */ } - } + }; } memory_view!(Uint8MemoryView over u8|1); diff --git a/src/value.rs b/src/value.rs index 510146f6..4220ea0a 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,9 +1,6 @@ //! The `Value` Python class to build WebAssembly values. -use pyo3::{ - prelude::*, - class::basic::PyObjectProtocol, -}; +use pyo3::{class::basic::PyObjectProtocol, prelude::*}; use wasmer_runtime::Value as WasmValue; #[pyclass] @@ -16,22 +13,30 @@ pub struct Value { impl Value { #[staticmethod] fn i32(value: i32) -> PyResult { - Ok(Self { value: WasmValue::I32(value) }) + Ok(Self { + value: WasmValue::I32(value), + }) } #[staticmethod] fn i64(value: i64) -> PyResult { - Ok(Self { value: WasmValue::I64(value) }) + Ok(Self { + value: WasmValue::I64(value), + }) } #[staticmethod] fn f32(value: f32) -> PyResult { - Ok(Self { value: WasmValue::F32(value) }) + Ok(Self { + value: WasmValue::F32(value), + }) } #[staticmethod] fn f64(value: f64) -> PyResult { - Ok(Self { value: WasmValue::F64(value) }) + Ok(Self { + value: WasmValue::F64(value), + }) } } From 21ef8a7dfafb7e0b5dde6deff9a1b80d69c9196c Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Sat, 13 Apr 2019 14:25:43 -0700 Subject: [PATCH 08/15] fix(extension) Use mapping protocol on `*MemoryView` classes. By replacing the `PySequenceProtocol` by `PyMappingProtocol`, the `__setitem__` method finally compiles. --- src/memory_view.rs | 6 ++-- tests/memory_view.py | 66 ++++++++++++++++++++++---------------------- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/memory_view.rs b/src/memory_view.rs index eba3e39c..334019ce 100644 --- a/src/memory_view.rs +++ b/src/memory_view.rs @@ -1,6 +1,6 @@ //! The `Buffer` Python object to build WebAssembly values. -use pyo3::{class::PySequenceProtocol, exceptions::IndexError, prelude::*}; +use pyo3::{class::PyMappingProtocol, exceptions::IndexError, prelude::*}; use std::mem::size_of; use wasmer_runtime::memory::Memory; @@ -21,7 +21,7 @@ macro_rules! memory_view { } #[pyproto] - impl PySequenceProtocol for $class_name { + impl PyMappingProtocol for $class_name { fn __len__(&self) -> PyResult { Ok(self.memory.view::<$wasm_type>()[self.offset..].len() / size_of::<$wasm_type>()) } @@ -49,7 +49,6 @@ macro_rules! memory_view { } } - /* fn __setitem__(&mut self, index: isize, value: u8) -> PyResult<()> { let offset = self.offset; let view = self.memory.view::(); @@ -74,7 +73,6 @@ macro_rules! memory_view { Ok(()) } } - */ } }; } diff --git a/tests/memory_view.py b/tests/memory_view.py index 0a664aac..7495f314 100644 --- a/tests/memory_view.py +++ b/tests/memory_view.py @@ -33,13 +33,13 @@ def test_length(self): 1114112 ) - #def test_get(self): - # memory = Instance(TEST_BYTES).uint8_memory_view() - # index = 7 - # value = 42 - # memory[index] = value + def test_get(self): + memory = Instance(TEST_BYTES).uint8_memory_view() + index = 7 + value = 42 + memory[index] = value - # self.assertEqual(memory[index], value) + self.assertEqual(memory[index], value) def test_get_out_of_range(self): with self.assertRaises(IndexError) as context_manager: @@ -52,16 +52,16 @@ def test_get_out_of_range(self): 'Out of bound: Absolute index 1114113 is larger than the memory size 1114112.' ) - #def test_set_out_of_range(self): - # with self.assertRaises(IndexError) as context_manager: - # memory = Instance(TEST_BYTES).uint8_memory_view() - # memory[len(memory) + 1] = 42 + def test_set_out_of_range(self): + with self.assertRaises(IndexError) as context_manager: + memory = Instance(TEST_BYTES).uint8_memory_view() + memory[len(memory) + 1] = 42 - # exception = context_manager.exception - # self.assertEqual( - # str(exception), - # 'Out of bound: Absolute index 1114113 is larger than the memory size 1114112.' - # ) + exception = context_manager.exception + self.assertEqual( + str(exception), + 'Out of bound: Absolute index 1114113 is larger than the memory size 1114112.' + ) def test_hello_world(self): instance = Instance(TEST_BYTES) @@ -76,21 +76,21 @@ def test_hello_world(self): self.assertEqual(string, 'Hello, World!') - #def test_memory_views_share_the_same_buffer(self): - # instance = Instance(TEST_BYTES) - # int8 = instance.int8_memory_view() - # int16 = instance.int16_memory_view() - # int32 = instance.int32_memory_view() - - # int8[0] = 0b00000001 - # int8[1] = 0b00000100 - # int8[2] = 0b00010000 - # int8[3] = 0b01000000 - - # self.assertEqual(int8[0], 0b00000001) - # self.assertEqual(int8[1], 0b00000100) - # self.assertEqual(int8[2], 0b00010000) - # self.assertEqual(int8[3], 0b01000000) - # self.assertEqual(int16[0], 0b00000100_00000001) - # self.assertEqual(int16[1], 0b01000000_00010000) - # self.assertEqual(int32[0], 0b01000000_00010000_00000100_00000001) + def test_memory_views_share_the_same_buffer(self): + instance = Instance(TEST_BYTES) + int8 = instance.int8_memory_view() + int16 = instance.int16_memory_view() + int32 = instance.int32_memory_view() + + int8[0] = 0b00000001 + int8[1] = 0b00000100 + int8[2] = 0b00010000 + int8[3] = 0b01000000 + + self.assertEqual(int8[0], 0b00000001) + self.assertEqual(int8[1], 0b00000100) + self.assertEqual(int8[2], 0b00010000) + self.assertEqual(int8[3], 0b01000000) + self.assertEqual(int16[0], 0b00000100_00000001) + self.assertEqual(int16[1], 0b01000000_00010000) + self.assertEqual(int32[0], 0b01000000_00010000_00000100_00000001) From 561a5672a61653573255435054daf5b7ac562fb4 Mon Sep 17 00:00:00 2001 From: Syrus Date: Sat, 13 Apr 2019 14:53:19 -0700 Subject: [PATCH 09/15] Improved tests by using pytest --- justfile | 21 +++++---- tests/init.py | 18 ------- tests/instance.py | 98 --------------------------------------- tests/test_instance.py | 68 +++++++++++++++++++++++++++ tests/test_memory_view.py | 92 ++++++++++++++++++++++++++++++++++++ tests/test_value.py | 28 +++++++++++ tests/value.py | 29 ------------ 7 files changed, 199 insertions(+), 155 deletions(-) delete mode 100644 tests/init.py delete mode 100644 tests/instance.py create mode 100644 tests/test_instance.py create mode 100644 tests/test_memory_view.py create mode 100644 tests/test_value.py delete mode 100644 tests/value.py diff --git a/justfile b/justfile index f2dc2a8b..432667ad 100644 --- a/justfile +++ b/justfile @@ -1,9 +1,10 @@ # Install the environment to develop the extension. prelude: - pip3 install pyo3-pack - cargo install pyo3-pack - pip3 install virtualenv virtualenv -p $(which python3) .env + source .env/bin/activate + + pip3 install pyo3-pack pytest pytest-benchmark + pip3 install virtualenv # Setup the environment to develop the extension. wakeup: @@ -14,22 +15,22 @@ sleep: deactivate # Compile and install the Rust library. -rust: +rust: wakeup export PYTHON_SYS_EXECUTABLE=$(which python3) cargo check pyo3-pack develop --binding_crate pyo3 --release --strip # Run Python. -python-run file='': - .env/bin/python {{file}} +python-run file='': wakeup + python {{file}} # Run the tests. -test: - @.env/bin/python tests/init.py +test: wakeup + py.test tests # Inspect the `python-ext-wasm` extension. -inspect: - .env/bin/python -c "help('wasmer')" +inspect: wakeup + python -c "help('wasmer')" # Local Variables: # mode: makefile diff --git a/tests/init.py b/tests/init.py deleted file mode 100644 index 24d5d9d4..00000000 --- a/tests/init.py +++ /dev/null @@ -1,18 +0,0 @@ -import instance -import memory_view -import unittest -import value - -def add_tests_from(suite, test_case): - suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(test_case)) - -def suite(): - suite = unittest.TestSuite() - add_tests_from(suite, instance.TestWasmInstance) - add_tests_from(suite, memory_view.TestWasmMemoryView) - add_tests_from(suite, value.TestWasmValue) - return suite - -if __name__ == '__main__': - runner = unittest.TextTestRunner(verbosity=2); - runner.run(suite()) diff --git a/tests/instance.py b/tests/instance.py deleted file mode 100644 index 8c4e1927..00000000 --- a/tests/instance.py +++ /dev/null @@ -1,98 +0,0 @@ -from wasmer import Instance, Uint8MemoryView, Value, validate -import inspect -import os -import unittest - -here = os.path.dirname(os.path.realpath(__file__)) -TEST_BYTES = open(here + '/tests.wasm', 'rb').read() -INVALID_TEST_BYTES = open(here + '/invalid.wasm', 'rb').read() - -class TestWasmInstance(unittest.TestCase): - def test_is_a_class(self): - self.assertTrue(inspect.isclass(Instance)) - - def test_can_construct(self): - self.assertIsInstance(Instance(TEST_BYTES), Instance) - - def test_failed_to_instantiate(self): - with self.assertRaises(RuntimeError) as context_manager: - Instance(INVALID_TEST_BYTES) - - exception = context_manager.exception - self.assertEqual( - str(exception), - 'Failed to instantiate the module:\n compile error: Validation error "Invalid type"' - ) - - def test_function_does_not_exist(self): - with self.assertRaises(KeyError) as context_manager: - Instance(TEST_BYTES).exports['foo'] - - exception = context_manager.exception - self.assertEqual( - str(exception), - "'foo'" - ) - - def test_basic_sum(self): - self.assertEqual( - Instance(TEST_BYTES).exports['sum'](1, 2), - 3 - ) - - def test_call_arity_0(self): - self.assertEqual( - Instance(TEST_BYTES).exports['arity_0'](), - 42 - ) - - def test_call_i32_i32(self): - self.assertEqual( - Instance(TEST_BYTES).exports['i32_i32'](7), - 7 - ) - - def test_call_i64_i64(self): - self.assertEqual( - Instance(TEST_BYTES).exports['i64_i64'](7), - 7 - ) - - def test_call_f32_f32(self): - self.assertEqual( - Instance(TEST_BYTES).exports['f32_f32'](7.), - 7. - ) - - def test_call_f64_f64(self): - self.assertEqual( - Instance(TEST_BYTES).exports['f64_f64'](7.), - 7. - ) - - def test_call_i32_i64_f32_f64_f64(self): - self.assertEqual( - round(Instance(TEST_BYTES).exports['i32_i64_f32_f64_f64'](1, 2, 3.4, 5.6), 6), - 1 + 2 + 3.4 + 5.6 - ) - - def test_call_bool_casted_to_i32(self): - self.assertEqual( - Instance(TEST_BYTES).exports['bool_casted_to_i32'](), - 1 - ) - - def test_call_string(self): - self.assertEqual( - Instance(TEST_BYTES).exports['string'](), - 1048576 - ) - - def test_validate(self): - self.assertTrue(validate(TEST_BYTES)) - - def test_validate_invalid(self): - self.assertFalse(validate(INVALID_TEST_BYTES)) - - def test_memory_view(self): - self.assertIsInstance(Instance(TEST_BYTES).uint8_memory_view(), Uint8MemoryView) diff --git a/tests/test_instance.py b/tests/test_instance.py new file mode 100644 index 00000000..e684e2ca --- /dev/null +++ b/tests/test_instance.py @@ -0,0 +1,68 @@ +from wasmer import Instance, Uint8MemoryView, Value, validate +import inspect +import os +import pytest + +here = os.path.dirname(os.path.realpath(__file__)) +TEST_BYTES = open(here + '/tests.wasm', 'rb').read() +INVALID_TEST_BYTES = open(here + '/invalid.wasm', 'rb').read() + +def test_is_a_class(): + assert inspect.isclass(Instance) + +def test_can_construct(): + assert isinstance(Instance(TEST_BYTES), Instance) + +def test_failed_to_instantiate(): + with pytest.raises(RuntimeError) as context_manager: + Instance(INVALID_TEST_BYTES) + + exception = context_manager.value + assert str(exception) == ( + 'Failed to instantiate the module:\n compile error: Validation error "Invalid type"' + ) + +def test_function_does_not_exist(): + with pytest.raises(KeyError) as context_manager: + Instance(TEST_BYTES).exports['foo'] + + exception = context_manager.value + assert str(exception) == "'foo'" + +def test_basic_sum(): + assert Instance(TEST_BYTES).exports['sum'](1, 2) == 3 + +def test_call_arity_0(): + assert Instance(TEST_BYTES).exports['arity_0']() == 42 + +def test_call_i32_i32(): + assert Instance(TEST_BYTES).exports['i32_i32'](7) == 7 + +def test_call_i64_i64(): + assert Instance(TEST_BYTES).exports['i64_i64'](7) == 7 + +def test_call_f32_f32(): + assert Instance(TEST_BYTES).exports['f32_f32'](7.) == 7. + +def test_call_f64_f64(): + assert Instance(TEST_BYTES).exports['f64_f64'](7.) == 7. + +def test_call_i32_i64_f32_f64_f64(): + assert round(Instance(TEST_BYTES).exports['i32_i64_f32_f64_f64'](1, 2, 3.4, 5.6), 6) == ( + 1 + 2 + 3.4 + 5.6 + ) + +def test_call_bool_casted_to_i32(): + assert Instance(TEST_BYTES).exports['bool_casted_to_i32']() == 1 + +def test_call_string(): + assert Instance(TEST_BYTES).exports['string']() == 1048576 + +def test_validate(): + assert validate(TEST_BYTES) + +def test_validate_invalid(): + assert not validate(INVALID_TEST_BYTES) + +def test_memory_view(): + assert isinstance(Instance(TEST_BYTES).uint8_memory_view(), Uint8MemoryView) diff --git a/tests/test_memory_view.py b/tests/test_memory_view.py new file mode 100644 index 00000000..41b5cf70 --- /dev/null +++ b/tests/test_memory_view.py @@ -0,0 +1,92 @@ +from wasmer import Instance, Uint8MemoryView, Int8MemoryView, Uint16MemoryView, Int16MemoryView, Uint32MemoryView, Int32MemoryView +import inspect +import os +import pytest + +here = os.path.dirname(os.path.realpath(__file__)) +TEST_BYTES = open(here + '/tests.wasm', 'rb').read() + +def test_is_a_class(): + assert inspect.isclass(Uint8MemoryView) + assert inspect.isclass(Int8MemoryView) + assert inspect.isclass(Uint16MemoryView) + assert inspect.isclass(Int16MemoryView) + assert inspect.isclass(Uint32MemoryView) + assert inspect.isclass(Int32MemoryView) + +def test_bytes_per_element(): + assert Instance(TEST_BYTES).uint8_memory_view().bytes_per_element == 1 + assert Instance(TEST_BYTES).int8_memory_view().bytes_per_element == 1 + assert Instance(TEST_BYTES).uint16_memory_view().bytes_per_element == 2 + assert Instance(TEST_BYTES).int16_memory_view().bytes_per_element == 2 + assert Instance(TEST_BYTES).uint32_memory_view().bytes_per_element == 4 + assert Instance(TEST_BYTES).int32_memory_view().bytes_per_element == 4 + +@pytest.mark.xfail() +def test_cannot_construct(): + assert isinstance(Uint8MemoryView(0), Uint8MemoryView) + +def test_length(): + assert len(Instance(TEST_BYTES).uint8_memory_view()) == ( + 1114112 + ) + +#def test_get(self): +# memory = Instance(TEST_BYTES).uint8_memory_view() +# index = 7 +# value = 42 +# memory[index] = value + +# assert memory[index] == value + +def test_get_out_of_range(): + with pytest.raises(IndexError) as context_manager: + memory = Instance(TEST_BYTES).uint8_memory_view() + memory[len(memory) + 1] + + exception = context_manager.value + assert str(exception) == ( + 'Out of bound: Absolute index 1114113 is larger than the memory size 1114112.' + ) + +#def test_set_out_of_range(self): +# with self.assertRaises(IndexError) as context_manager: +# memory = Instance(TEST_BYTES).uint8_memory_view() +# memory[len(memory) + 1] = 42 + +# exception = context_manager.value +# assert str(exception) == ( +# 'Out of bound: Absolute index 1114113 is larger than the memory size 1114112.' +# ) + +def test_hello_world(): + instance = Instance(TEST_BYTES) + pointer = instance.exports['string']() + memory = instance.uint8_memory_view(pointer) + nth = 0 + string = '' + + while (0 != memory[nth]): + string += chr(memory[nth]) + nth += 1 + + assert string, 'Hello == World!' + +def test_memory_views_share_the_same_buffer(): + instance = Instance(TEST_BYTES) + int8 = instance.int8_memory_view() + int16 = instance.int16_memory_view() + int32 = instance.int32_memory_view() + + int8[0] = 0b00000001 + int8[1] = 0b00000100 + int8[2] = 0b00010000 + int8[3] = 0b01000000 + + assert int8[0] == 0b00000001 + assert int8[1] == 0b00000100 + assert int8[2] == 0b00010000 + assert int8[3] == 0b01000000 + assert int16[0] == 0b00000100_00000001 + assert int16[1] == 0b01000000_00010000 + assert int32[0] == 0b01000000_00010000_00000100_00000001 diff --git a/tests/test_value.py b/tests/test_value.py new file mode 100644 index 00000000..65f03b16 --- /dev/null +++ b/tests/test_value.py @@ -0,0 +1,28 @@ +from wasmer import Value +import inspect +import pytest + +def test_is_a_class(): + assert inspect.isclass(Value) + +@pytest.mark.xfail() +def test_cannot_construct(): + Value() + +def test_i32(): + assert repr(Value.i32(42)) == 'I32(42)' + +def test_i64(): + assert repr(Value.i64(42)) == 'I64(42)' + +def test_f32(): + assert repr(Value.f32(4.2)) == 'F32(4.2)' + +def test_f32_auto_cast(): + assert repr(Value.f32(42)) == 'F32(42.0)' + +def test_f64(): + assert repr(Value.f64(4.2)) == 'F64(4.2)' + +def test_f64_auto_cast(): + assert repr(Value.f64(42)) == 'F64(42.0)' diff --git a/tests/value.py b/tests/value.py deleted file mode 100644 index 8e8acff9..00000000 --- a/tests/value.py +++ /dev/null @@ -1,29 +0,0 @@ -from wasmer import Value -import inspect -import unittest - -class TestWasmValue(unittest.TestCase): - def test_is_a_class(self): - self.assertTrue(inspect.isclass(Value)) - - @unittest.expectedFailure - def test_cannot_construct(self): - Value() - - def test_i32(self): - self.assertEqual(repr(Value.i32(42)), 'I32(42)') - - def test_i64(self): - self.assertEqual(repr(Value.i64(42)), 'I64(42)') - - def test_f32(self): - self.assertEqual(repr(Value.f32(4.2)), 'F32(4.2)') - - def test_f32_auto_cast(self): - self.assertEqual(repr(Value.f32(42)), 'F32(42.0)') - - def test_f64(self): - self.assertEqual(repr(Value.f64(4.2)), 'F64(4.2)') - - def test_f64_auto_cast(self): - self.assertEqual(repr(Value.f64(42)), 'F64(42.0)') From 9325c28b143cdd79a54375098a8ce6835fba06ab Mon Sep 17 00:00:00 2001 From: Syrus Date: Sat, 13 Apr 2019 15:03:48 -0700 Subject: [PATCH 10/15] Remove old test --- tests/memory_view.py | 96 -------------------------------------------- 1 file changed, 96 deletions(-) delete mode 100644 tests/memory_view.py diff --git a/tests/memory_view.py b/tests/memory_view.py deleted file mode 100644 index 7495f314..00000000 --- a/tests/memory_view.py +++ /dev/null @@ -1,96 +0,0 @@ -from wasmer import Instance, Uint8MemoryView, Int8MemoryView, Uint16MemoryView, Int16MemoryView, Uint32MemoryView, Int32MemoryView -import inspect -import os -import unittest - -here = os.path.dirname(os.path.realpath(__file__)) -TEST_BYTES = open(here + '/tests.wasm', 'rb').read() - -class TestWasmMemoryView(unittest.TestCase): - def test_is_a_class(self): - self.assertTrue(inspect.isclass(Uint8MemoryView)) - self.assertTrue(inspect.isclass(Int8MemoryView)) - self.assertTrue(inspect.isclass(Uint16MemoryView)) - self.assertTrue(inspect.isclass(Int16MemoryView)) - self.assertTrue(inspect.isclass(Uint32MemoryView)) - self.assertTrue(inspect.isclass(Int32MemoryView)) - - def test_bytes_per_element(self): - self.assertEqual(Instance(TEST_BYTES).uint8_memory_view().bytes_per_element, 1) - self.assertEqual(Instance(TEST_BYTES).int8_memory_view().bytes_per_element, 1) - self.assertEqual(Instance(TEST_BYTES).uint16_memory_view().bytes_per_element, 2) - self.assertEqual(Instance(TEST_BYTES).int16_memory_view().bytes_per_element, 2) - self.assertEqual(Instance(TEST_BYTES).uint32_memory_view().bytes_per_element, 4) - self.assertEqual(Instance(TEST_BYTES).int32_memory_view().bytes_per_element, 4) - - @unittest.expectedFailure - def test_cannot_construct(self): - self.assertIsInstance(Uint8MemoryView(0), Uint8MemoryView) - - def test_length(self): - self.assertEqual( - len(Instance(TEST_BYTES).uint8_memory_view()), - 1114112 - ) - - def test_get(self): - memory = Instance(TEST_BYTES).uint8_memory_view() - index = 7 - value = 42 - memory[index] = value - - self.assertEqual(memory[index], value) - - def test_get_out_of_range(self): - with self.assertRaises(IndexError) as context_manager: - memory = Instance(TEST_BYTES).uint8_memory_view() - memory[len(memory) + 1] - - exception = context_manager.exception - self.assertEqual( - str(exception), - 'Out of bound: Absolute index 1114113 is larger than the memory size 1114112.' - ) - - def test_set_out_of_range(self): - with self.assertRaises(IndexError) as context_manager: - memory = Instance(TEST_BYTES).uint8_memory_view() - memory[len(memory) + 1] = 42 - - exception = context_manager.exception - self.assertEqual( - str(exception), - 'Out of bound: Absolute index 1114113 is larger than the memory size 1114112.' - ) - - def test_hello_world(self): - instance = Instance(TEST_BYTES) - pointer = instance.exports['string']() - memory = instance.uint8_memory_view(pointer) - nth = 0 - string = '' - - while (0 != memory[nth]): - string += chr(memory[nth]) - nth += 1 - - self.assertEqual(string, 'Hello, World!') - - def test_memory_views_share_the_same_buffer(self): - instance = Instance(TEST_BYTES) - int8 = instance.int8_memory_view() - int16 = instance.int16_memory_view() - int32 = instance.int32_memory_view() - - int8[0] = 0b00000001 - int8[1] = 0b00000100 - int8[2] = 0b00010000 - int8[3] = 0b01000000 - - self.assertEqual(int8[0], 0b00000001) - self.assertEqual(int8[1], 0b00000100) - self.assertEqual(int8[2], 0b00010000) - self.assertEqual(int8[3], 0b01000000) - self.assertEqual(int16[0], 0b00000100_00000001) - self.assertEqual(int16[1], 0b01000000_00010000) - self.assertEqual(int32[0], 0b01000000_00010000_00000100_00000001) From fd46ab4fd4591937e4c0619f06e8477d272f910d Mon Sep 17 00:00:00 2001 From: Syrus Date: Sat, 13 Apr 2019 15:03:55 -0700 Subject: [PATCH 11/15] Added example benchmark --- tests/test_benchmarks.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/test_benchmarks.py diff --git a/tests/test_benchmarks.py b/tests/test_benchmarks.py new file mode 100644 index 00000000..32d53ab4 --- /dev/null +++ b/tests/test_benchmarks.py @@ -0,0 +1,16 @@ +from wasmer import Instance, Uint8MemoryView, Value, validate +import inspect +import os +import pytest + +here = os.path.dirname(os.path.realpath(__file__)) +TEST_BYTES = open(here + '/tests.wasm', 'rb').read() + +def test_sum_benchmark(benchmark): + instance = Instance(TEST_BYTES) + sum_func = instance.exports['sum'] + + def bench(): + return sum_func(1, 2) + + assert benchmark(bench) == 3 From 6efae3adcc415d13c92c33a42c72c1b5025a5967 Mon Sep 17 00:00:00 2001 From: Syrus Date: Sat, 13 Apr 2019 15:04:52 -0700 Subject: [PATCH 12/15] Fixed just prelude --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index 432667ad..8d6ba537 100644 --- a/justfile +++ b/justfile @@ -1,10 +1,10 @@ # Install the environment to develop the extension. prelude: + pip3 install virtualenv virtualenv -p $(which python3) .env source .env/bin/activate pip3 install pyo3-pack pytest pytest-benchmark - pip3 install virtualenv # Setup the environment to develop the extension. wakeup: From e40f9de8b3767e57476025f0198723200ace9c92 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Sat, 13 Apr 2019 15:30:51 -0700 Subject: [PATCH 13/15] feat(extension) Replace `instance.exports['sum'](1, 2)` by `instance.exports.sum(1, 2)`. --- README.md | 19 ++++++------- examples/memory.py | 2 +- examples/simple.py | 3 +- src/instance.rs | 66 +++++++++++++++++++++++++++++--------------- tests/instance.py | 24 ++++++++-------- tests/memory_view.py | 2 +- 6 files changed, 69 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 62b21266..a3f798fa 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ from wasmer import Instance, Value wasm_bytes = open('simple.wasm', 'rb').read() instance = Instance(wasm_bytes) -result = instance.exports['sum'](5, 37) +result = instance.exports.sum(5, 37) print(result) # 42! ``` @@ -88,18 +88,17 @@ wasm_bytes = open('my_program.wasm', 'rb').read() instance = Instance(wasm_bytes) # Call a function on it. -result = instance.exports['sum'](1, 2) +result = instance.exports.sum(1, 2) print(result) # 3 ``` -All exported functions are accessible in the `exports` -dictionnary. Each value of this dictionnary is a function. Arguments -of these functions are automatically casted to WebAssembly value. If -one wants to explicitely pass a value of a particular type, it is -possible to use the `Value` class, -e.g. `instance.exports['sum'](Value.i32(1), Value.i32(2))`. Note that -for most usecases, this is not necessary. +All exported functions are accessible on the `exports` getter. +Arguments of these functions are automatically casted to WebAssembly +values. If one wants to explicitely pass a value of a particular type, +it is possible to use the `Value` class, +e.g. `instance.exports.sum(Value.i32(1), Value.i32(2))`. Note that for +most usecases, this is not necessary. ### The `Value` class @@ -169,7 +168,7 @@ wasm_bytes = open('my_program.wasm', 'rb').read() instance = Instance(wasm_bytes) # Call a function that returns a pointer to a string for instance. -pointer = instance.exports['return_string']() +pointer = instance.exports.return_string() # Get the memory view, with the offset set to `pointer` (default is 0). memory = instance.uint8_memory_view(pointer) diff --git a/examples/memory.py b/examples/memory.py index 35d90a3d..9414adc8 100644 --- a/examples/memory.py +++ b/examples/memory.py @@ -5,7 +5,7 @@ wasm_bytes = open(__dir__ + '/memory.wasm', 'rb').read() instance = Instance(wasm_bytes) -pointer = instance.exports['return_hello']() +pointer = instance.exports.return_hello() memory = instance.uint8_memory_view(pointer) nth = 0; diff --git a/examples/simple.py b/examples/simple.py index e1ba7e7e..30faeff4 100644 --- a/examples/simple.py +++ b/examples/simple.py @@ -5,6 +5,7 @@ wasm_bytes = open(__dir__ + '/simple.wasm', 'rb').read() instance = Instance(wasm_bytes) -result = instance.exports['sum'](5, 37) + +result = instance.exports.sum(1, 2) print(result) # 42! diff --git a/src/instance.rs b/src/instance.rs index ec9af40d..885fa95b 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -2,10 +2,11 @@ use crate::{memory_view, value::Value}; use pyo3::{ - exceptions::RuntimeError, + class::basic::PyObjectProtocol, + exceptions::{LookupError, RuntimeError}, prelude::*, - types::{PyAny, PyBytes, PyDict, PyFloat, PyLong, PyTuple}, - PyNativeType, PyTryFrom, + types::{PyAny, PyBytes, PyFloat, PyLong, PyTuple}, + PyNativeType, PyTryFrom, ToPyObject, }; use std::rc::Rc; use wasmer_runtime::{self as runtime, imports, instantiate, Export, Memory, Value as WasmValue}; @@ -91,10 +92,37 @@ impl ExportedFunction { } } +#[pyclass] +pub struct ExportedFunctions { + instance: Rc, + functions: Vec, +} + +#[pyproto] +impl PyObjectProtocol for ExportedFunctions { + fn __getattr__(&self, key: String) -> PyResult { + if self.functions.contains(&key) { + Ok(ExportedFunction { + function_name: key, + instance: self.instance.clone(), + }) + } else { + Err(LookupError::py_err(format!( + "Function `{}` does not exist.", + key + ))) + } + } + + fn __repr__(&self) -> PyResult { + Ok(format!("{:?}", self.functions)) + } +} + #[pyclass] pub struct Instance { instance: Rc, - exports: PyObject, + exports: Py, } #[pymethods] @@ -114,27 +142,24 @@ impl Instance { }; let py = object.py(); - let dict = PyDict::new(py); + let mut exported_functions = Vec::new(); for (export_name, export) in instance.exports() { if let Export::Function { .. } = export { - dict.set_item( - export_name.clone(), - Py::new( - py, - ExportedFunction { - function_name: export_name, - instance: instance.clone(), - }, - )?, - )?; + exported_functions.push(export_name); } } object.init({ Self { - instance, - exports: dict.to_object(py), + instance: instance.clone(), + exports: Py::new( + py, + ExportedFunctions { + instance: instance.clone(), + functions: exported_functions, + }, + )?, } }); @@ -142,11 +167,8 @@ impl Instance { } #[getter] - fn exports(&self) -> PyResult<&PyDict> { - let gil = Python::acquire_gil(); - let py = gil.python(); - - Ok(self.exports.cast_as::(py)?) + fn exports(&self) -> PyResult<&Py> { + Ok(&self.exports) } #[args(offset = 0)] diff --git a/tests/instance.py b/tests/instance.py index 8c4e1927..b133301d 100644 --- a/tests/instance.py +++ b/tests/instance.py @@ -25,66 +25,66 @@ def test_failed_to_instantiate(self): ) def test_function_does_not_exist(self): - with self.assertRaises(KeyError) as context_manager: - Instance(TEST_BYTES).exports['foo'] + with self.assertRaises(LookupError) as context_manager: + Instance(TEST_BYTES).exports.foo exception = context_manager.exception self.assertEqual( str(exception), - "'foo'" + "Function `foo` does not exist." ) def test_basic_sum(self): self.assertEqual( - Instance(TEST_BYTES).exports['sum'](1, 2), + Instance(TEST_BYTES).exports.sum(1, 2), 3 ) def test_call_arity_0(self): self.assertEqual( - Instance(TEST_BYTES).exports['arity_0'](), + Instance(TEST_BYTES).exports.arity_0(), 42 ) def test_call_i32_i32(self): self.assertEqual( - Instance(TEST_BYTES).exports['i32_i32'](7), + Instance(TEST_BYTES).exports.i32_i32(7), 7 ) def test_call_i64_i64(self): self.assertEqual( - Instance(TEST_BYTES).exports['i64_i64'](7), + Instance(TEST_BYTES).exports.i64_i64(7), 7 ) def test_call_f32_f32(self): self.assertEqual( - Instance(TEST_BYTES).exports['f32_f32'](7.), + Instance(TEST_BYTES).exports.f32_f32(7.), 7. ) def test_call_f64_f64(self): self.assertEqual( - Instance(TEST_BYTES).exports['f64_f64'](7.), + Instance(TEST_BYTES).exports.f64_f64(7.), 7. ) def test_call_i32_i64_f32_f64_f64(self): self.assertEqual( - round(Instance(TEST_BYTES).exports['i32_i64_f32_f64_f64'](1, 2, 3.4, 5.6), 6), + round(Instance(TEST_BYTES).exports.i32_i64_f32_f64_f64(1, 2, 3.4, 5.6), 6), 1 + 2 + 3.4 + 5.6 ) def test_call_bool_casted_to_i32(self): self.assertEqual( - Instance(TEST_BYTES).exports['bool_casted_to_i32'](), + Instance(TEST_BYTES).exports.bool_casted_to_i32(), 1 ) def test_call_string(self): self.assertEqual( - Instance(TEST_BYTES).exports['string'](), + Instance(TEST_BYTES).exports.string(), 1048576 ) diff --git a/tests/memory_view.py b/tests/memory_view.py index 7495f314..e8ef0ef4 100644 --- a/tests/memory_view.py +++ b/tests/memory_view.py @@ -65,7 +65,7 @@ def test_set_out_of_range(self): def test_hello_world(self): instance = Instance(TEST_BYTES) - pointer = instance.exports['string']() + pointer = instance.exports.string() memory = instance.uint8_memory_view(pointer) nth = 0 string = '' From e154e19ce707c572d8288073294a71ee8fb816b6 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Sat, 13 Apr 2019 15:44:49 -0700 Subject: [PATCH 14/15] fix(test) Update the last merge. --- justfile | 3 +-- tests/test_benchmarks.py | 4 ++-- tests/test_instance.py | 24 ++++++++++++------------ tests/test_memory_view.py | 30 +++++++++++++++--------------- 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/justfile b/justfile index 8d6ba537..01894b8b 100644 --- a/justfile +++ b/justfile @@ -3,7 +3,6 @@ prelude: pip3 install virtualenv virtualenv -p $(which python3) .env source .env/bin/activate - pip3 install pyo3-pack pytest pytest-benchmark # Setup the environment to develop the extension. @@ -26,7 +25,7 @@ python-run file='': wakeup # Run the tests. test: wakeup - py.test tests + @py.test tests # Inspect the `python-ext-wasm` extension. inspect: wakeup diff --git a/tests/test_benchmarks.py b/tests/test_benchmarks.py index 32d53ab4..677e6552 100644 --- a/tests/test_benchmarks.py +++ b/tests/test_benchmarks.py @@ -8,9 +8,9 @@ def test_sum_benchmark(benchmark): instance = Instance(TEST_BYTES) - sum_func = instance.exports['sum'] + sum = instance.exports.sum def bench(): - return sum_func(1, 2) + return sum(1, 2) assert benchmark(bench) == 3 diff --git a/tests/test_instance.py b/tests/test_instance.py index e684e2ca..261d10d5 100644 --- a/tests/test_instance.py +++ b/tests/test_instance.py @@ -23,40 +23,40 @@ def test_failed_to_instantiate(): ) def test_function_does_not_exist(): - with pytest.raises(KeyError) as context_manager: - Instance(TEST_BYTES).exports['foo'] + with pytest.raises(LookupError) as context_manager: + Instance(TEST_BYTES).exports.foo exception = context_manager.value - assert str(exception) == "'foo'" + assert str(exception) == 'Function `foo` does not exist.' def test_basic_sum(): - assert Instance(TEST_BYTES).exports['sum'](1, 2) == 3 + assert Instance(TEST_BYTES).exports.sum(1, 2) == 3 def test_call_arity_0(): - assert Instance(TEST_BYTES).exports['arity_0']() == 42 + assert Instance(TEST_BYTES).exports.arity_0() == 42 def test_call_i32_i32(): - assert Instance(TEST_BYTES).exports['i32_i32'](7) == 7 + assert Instance(TEST_BYTES).exports.i32_i32(7) == 7 def test_call_i64_i64(): - assert Instance(TEST_BYTES).exports['i64_i64'](7) == 7 + assert Instance(TEST_BYTES).exports.i64_i64(7) == 7 def test_call_f32_f32(): - assert Instance(TEST_BYTES).exports['f32_f32'](7.) == 7. + assert Instance(TEST_BYTES).exports.f32_f32(7.) == 7. def test_call_f64_f64(): - assert Instance(TEST_BYTES).exports['f64_f64'](7.) == 7. + assert Instance(TEST_BYTES).exports.f64_f64(7.) == 7. def test_call_i32_i64_f32_f64_f64(): - assert round(Instance(TEST_BYTES).exports['i32_i64_f32_f64_f64'](1, 2, 3.4, 5.6), 6) == ( + assert round(Instance(TEST_BYTES).exports.i32_i64_f32_f64_f64(1, 2, 3.4, 5.6), 6) == ( 1 + 2 + 3.4 + 5.6 ) def test_call_bool_casted_to_i32(): - assert Instance(TEST_BYTES).exports['bool_casted_to_i32']() == 1 + assert Instance(TEST_BYTES).exports.bool_casted_to_i32() == 1 def test_call_string(): - assert Instance(TEST_BYTES).exports['string']() == 1048576 + assert Instance(TEST_BYTES).exports.string() == 1048576 def test_validate(): assert validate(TEST_BYTES) diff --git a/tests/test_memory_view.py b/tests/test_memory_view.py index 41b5cf70..2ac8c6cb 100644 --- a/tests/test_memory_view.py +++ b/tests/test_memory_view.py @@ -31,13 +31,13 @@ def test_length(): 1114112 ) -#def test_get(self): -# memory = Instance(TEST_BYTES).uint8_memory_view() -# index = 7 -# value = 42 -# memory[index] = value +def test_get(): + memory = Instance(TEST_BYTES).uint8_memory_view() + index = 7 + value = 42 + memory[index] = value -# assert memory[index] == value + assert memory[index] == value def test_get_out_of_range(): with pytest.raises(IndexError) as context_manager: @@ -49,19 +49,19 @@ def test_get_out_of_range(): 'Out of bound: Absolute index 1114113 is larger than the memory size 1114112.' ) -#def test_set_out_of_range(self): -# with self.assertRaises(IndexError) as context_manager: -# memory = Instance(TEST_BYTES).uint8_memory_view() -# memory[len(memory) + 1] = 42 +def test_set_out_of_range(): + with pytest.raises(IndexError) as context_manager: + memory = Instance(TEST_BYTES).uint8_memory_view() + memory[len(memory) + 1] = 42 -# exception = context_manager.value -# assert str(exception) == ( -# 'Out of bound: Absolute index 1114113 is larger than the memory size 1114112.' -# ) + exception = context_manager.value + assert str(exception) == ( + 'Out of bound: Absolute index 1114113 is larger than the memory size 1114112.' + ) def test_hello_world(): instance = Instance(TEST_BYTES) - pointer = instance.exports['string']() + pointer = instance.exports.string() memory = instance.uint8_memory_view(pointer) nth = 0 string = '' From af9b2ef7d1f051722bfaf6a5ce7b163df65c23e3 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Sat, 13 Apr 2019 15:47:54 -0700 Subject: [PATCH 15/15] chore(justfile) Add some `@`. --- justfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/justfile b/justfile index 01894b8b..063e07f1 100644 --- a/justfile +++ b/justfile @@ -14,22 +14,22 @@ sleep: deactivate # Compile and install the Rust library. -rust: wakeup +rust: export PYTHON_SYS_EXECUTABLE=$(which python3) cargo check pyo3-pack develop --binding_crate pyo3 --release --strip # Run Python. -python-run file='': wakeup - python {{file}} +python-run file='': + @python {{file}} # Run the tests. -test: wakeup +test: @py.test tests # Inspect the `python-ext-wasm` extension. -inspect: wakeup - python -c "help('wasmer')" +inspect: + @python -c "help('wasmer')" # Local Variables: # mode: makefile