diff --git a/Cargo.lock b/Cargo.lock index 315d6ce0b2e..a3a69a80a69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -527,7 +527,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "which", + "which 3.1.1", ] [[package]] @@ -910,7 +910,7 @@ checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" dependencies = [ "glob 0.3.0", "libc", - "libloading", + "libloading 0.5.2", ] [[package]] @@ -1056,7 +1056,16 @@ version = "0.67.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f065f6889758f817f61a230220d1811ba99a9762af2fb69ae23048314f75ff2" dependencies = [ - "cranelift-entity", + "cranelift-entity 0.67.0", +] + +[[package]] +name = "cranelift-bforest" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9221545c0507dc08a62b2d8b5ffe8e17ac580b0a74d1813b496b8d70b070fbd0" +dependencies = [ + "cranelift-entity 0.68.0", ] [[package]] @@ -1066,27 +1075,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "510aa2ab4307644100682b94e449940a0ea15c5887f1d4b9678b8dd5ef31e736" dependencies = [ "byteorder", - "cranelift-bforest", - "cranelift-codegen-meta", - "cranelift-codegen-shared", - "cranelift-entity", + "cranelift-bforest 0.67.0", + "cranelift-codegen-meta 0.67.0", + "cranelift-codegen-shared 0.67.0", + "cranelift-entity 0.67.0", "gimli 0.21.0", "log", - "regalloc", + "regalloc 0.0.30", "serde", "smallvec", "target-lexicon 0.11.1", "thiserror", ] +[[package]] +name = "cranelift-codegen" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9936ea608b6cd176f107037f6adbb4deac933466fc7231154f96598b2d3ab1" +dependencies = [ + "byteorder", + "cranelift-bforest 0.68.0", + "cranelift-codegen-meta 0.68.0", + "cranelift-codegen-shared 0.68.0", + "cranelift-entity 0.68.0", + "gimli 0.22.0", + "log", + "regalloc 0.0.31", + "smallvec", + "target-lexicon 0.11.1", + "thiserror", +] + [[package]] name = "cranelift-codegen-meta" version = "0.67.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4cb0c7e87c60d63b35f9670c15479ee4a5e557dd127efab88b2f9b2ca83c9a0" dependencies = [ - "cranelift-codegen-shared", - "cranelift-entity", + "cranelift-codegen-shared 0.67.0", + "cranelift-entity 0.67.0", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef2b2768568306540f4c8db3acce9105534d34c4a1e440529c1e702d7f8c8d7" +dependencies = [ + "cranelift-codegen-shared 0.68.0", + "cranelift-entity 0.68.0", ] [[package]] @@ -1095,6 +1133,12 @@ version = "0.67.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60636227098693e06de8d6d88beea2a7d32ecf8a8030dacdb57c68e06f381826" +[[package]] +name = "cranelift-codegen-shared" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6759012d6d19c4caec95793f052613e9d4113e925e7f14154defbac0f1d4c938" + [[package]] name = "cranelift-entity" version = "0.67.0" @@ -1104,13 +1148,34 @@ dependencies = [ "serde", ] +[[package]] +name = "cranelift-entity" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86badbce14e15f52a45b666b38abe47b204969dd7f8fb7488cb55dd46b361fa6" +dependencies = [ + "serde", +] + [[package]] name = "cranelift-frontend" version = "0.67.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e09cd158c9a820a4cc14a34076811da225cce1d31dc6d03c5ef85b91aef560b9" dependencies = [ - "cranelift-codegen", + "cranelift-codegen 0.67.0", + "log", + "smallvec", + "target-lexicon 0.11.1", +] + +[[package]] +name = "cranelift-frontend" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b608bb7656c554d0a4cf8f50c7a10b857e80306f6ff829ad6d468a7e2323c8d8" +dependencies = [ + "cranelift-codegen 0.68.0", "log", "smallvec", "target-lexicon 0.11.1", @@ -1122,7 +1187,7 @@ version = "0.67.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7054533ae1fc2048c1a6110bdf8f4314b77c60329ec6a7df79d2cfb84e3dcc1c" dependencies = [ - "cranelift-codegen", + "cranelift-codegen 0.67.0", "raw-cpuid", "target-lexicon 0.11.1", ] @@ -1133,9 +1198,9 @@ version = "0.67.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7aee0e0b68eba99f99a4923212d97aca9e44655ca8246f07fffe11236109b0d0" dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", + "cranelift-codegen 0.67.0", + "cranelift-entity 0.67.0", + "cranelift-frontend 0.67.0", "log", "serde", "thiserror", @@ -1568,6 +1633,27 @@ dependencies = [ "syn 1.0.60", ] +[[package]] +name = "enumset" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e97cdfe38fa12fc11118af2979cc4c4b243d475e4daf691dcf5687d90efd28c" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3946e239d2862f82f1d105d2193f8026c2c5215cc68b3cebd18c37c4c1b5035" +dependencies = [ + "darling", + "proc-macro2 1.0.24", + "quote 1.0.8", + "syn 1.0.60", +] + [[package]] name = "env_logger" version = "0.7.1" @@ -2031,6 +2117,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "gimli" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + [[package]] name = "gimli" version = "0.23.0" @@ -2631,6 +2728,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + [[package]] name = "librocksdb-sys" version = "6.11.4" @@ -2666,7 +2773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b3ef22989d6e83232ef628dc739c0a3f39a556c4b53d1943e6731bc35b7eea9" dependencies = [ "capstone", - "cranelift-codegen", + "cranelift-codegen 0.67.0", "derive_more", "dynasm 0.5.2", "dynasmrt 0.5.2", @@ -3527,8 +3634,11 @@ dependencies = [ "parity-wasm", "pwasm-utils", "wabt", + "wasmer", + "wasmer-compiler-singlepass", "wasmer-runtime-core-near", "wasmer-runtime-near", + "wasmer-types", "wasmtime", ] @@ -3751,6 +3861,16 @@ dependencies = [ "wasmparser 0.57.0", ] +[[package]] +name = "object" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" +dependencies = [ + "crc32fast", + "indexmap", +] + [[package]] name = "object" version = "0.23.0" @@ -4571,6 +4691,17 @@ dependencies = [ "smallvec", ] +[[package]] +name = "regalloc" +version = "0.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" +dependencies = [ + "log", + "rustc-hash", + "smallvec", +] + [[package]] name = "regex" version = "1.4.3" @@ -6187,6 +6318,168 @@ version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64" +[[package]] +name = "wasmer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70cfae554988d904d64ca17ab0e7cd652ee5c8a0807094819c1ea93eb9d6866" +dependencies = [ + "cfg-if 0.1.10", + "indexmap", + "more-asserts", + "target-lexicon 0.11.1", + "thiserror", + "wasmer-compiler", + "wasmer-compiler-cranelift", + "wasmer-derive", + "wasmer-engine", + "wasmer-engine-jit", + "wasmer-engine-native", + "wasmer-types", + "wasmer-vm", + "wat", + "winapi", +] + +[[package]] +name = "wasmer-compiler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7732a9cab472bd921d5a0c422f45b3d03f62fa2c40a89e0770cef6d47e383e" +dependencies = [ + "enumset", + "serde", + "serde_bytes", + "smallvec", + "target-lexicon 0.11.1", + "thiserror", + "wasmer-types", + "wasmer-vm", + "wasmparser 0.65.0", +] + +[[package]] +name = "wasmer-compiler-cranelift" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb9395f094e1d81534f4c5e330ed4cdb424e8df870d29ad585620284f5fddb" +dependencies = [ + "cranelift-codegen 0.68.0", + "cranelift-frontend 0.68.0", + "gimli 0.22.0", + "more-asserts", + "rayon", + "serde", + "smallvec", + "tracing", + "wasmer-compiler", + "wasmer-types", + "wasmer-vm", +] + +[[package]] +name = "wasmer-compiler-singlepass" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426ae6ef0f606ca815510f3e2ef6f520e217514bfb7a664defe180b9a9e75d07" +dependencies = [ + "byteorder", + "dynasm 1.0.1", + "dynasmrt 1.0.1", + "lazy_static", + "more-asserts", + "rayon", + "serde", + "smallvec", + "wasmer-compiler", + "wasmer-types", + "wasmer-vm", +] + +[[package]] +name = "wasmer-derive" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b86dcd2c3efdb8390728a2b56f762db07789aaa5aa872a9dc776ba3a7912ed" +dependencies = [ + "proc-macro-error", + "proc-macro2 1.0.24", + "quote 1.0.8", + "syn 1.0.60", +] + +[[package]] +name = "wasmer-engine" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efe4667d6bd888f26ae8062a63a9379fa697415b4b4e380f33832e8418fd71b5" +dependencies = [ + "backtrace", + "bincode", + "lazy_static", + "memmap2", + "more-asserts", + "rustc-demangle", + "serde", + "serde_bytes", + "target-lexicon 0.11.1", + "thiserror", + "wasmer-compiler", + "wasmer-types", + "wasmer-vm", +] + +[[package]] +name = "wasmer-engine-jit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26770be802888011b4a3072f2a282fc2faa68aa48c71b3db6252a3937a85f3da" +dependencies = [ + "bincode", + "cfg-if 0.1.10", + "region", + "serde", + "serde_bytes", + "wasmer-compiler", + "wasmer-engine", + "wasmer-types", + "wasmer-vm", + "winapi", +] + +[[package]] +name = "wasmer-engine-native" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb4083a6c69f2cd4b000b82a80717f37c6cc2e536aee3a8ffe9af3edc276a8b" +dependencies = [ + "bincode", + "cfg-if 0.1.10", + "leb128", + "libloading 0.6.7", + "serde", + "tempfile", + "tracing", + "wasmer-compiler", + "wasmer-engine", + "wasmer-object", + "wasmer-types", + "wasmer-vm", + "which 4.0.2", +] + +[[package]] +name = "wasmer-object" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf8e0c12b82ff81ebecd30d7e118be5fec871d6de885a90eeb105df0a769a7b" +dependencies = [ + "object 0.22.0", + "thiserror", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-runtime-core-near" version = "0.17.1" @@ -6249,6 +6542,37 @@ dependencies = [ "wasmer-runtime-core-near", ] +[[package]] +name = "wasmer-types" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f4ac28c2951cd792c18332f03da523ed06b170f5cf6bb5b1bdd7e36c2a8218" +dependencies = [ + "cranelift-entity 0.68.0", + "serde", + "thiserror", +] + +[[package]] +name = "wasmer-vm" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7635ba0b6d2fd325f588d69a950ad9fa04dddbf6ad08b6b2a183146319bf6ae" +dependencies = [ + "backtrace", + "cc", + "cfg-if 0.1.10", + "indexmap", + "libc", + "memoffset 0.6.1", + "more-asserts", + "region", + "serde", + "thiserror", + "wasmer-types", + "winapi", +] + [[package]] name = "wasmparser" version = "0.51.4" @@ -6267,6 +6591,12 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a950e6a618f62147fd514ff445b2a0b53120d382751960797f85f058c7eda9b9" +[[package]] +name = "wasmparser" +version = "0.65.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc2fe6350834b4e528ba0901e7aa405d78b89dc1fa3145359eb4de0e323fcf" + [[package]] name = "wasmtime" version = "0.20.0" @@ -6299,9 +6629,9 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c01df908e54d40bed80326ade122825d464888991beafd950d186f1be309c2" dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", + "cranelift-codegen 0.67.0", + "cranelift-entity 0.67.0", + "cranelift-frontend 0.67.0", "cranelift-wasm", "wasmtime-environ", ] @@ -6330,8 +6660,8 @@ checksum = "9c0d7401bf253b7b1f426afd70d285bb23ea9a1c7605d6af788c95db5084edf5" dependencies = [ "anyhow", "cfg-if 0.1.10", - "cranelift-codegen", - "cranelift-entity", + "cranelift-codegen 0.67.0", + "cranelift-entity 0.67.0", "cranelift-wasm", "gimli 0.21.0", "indexmap", @@ -6350,9 +6680,9 @@ checksum = "8c838a108318e7c5a2201addb3d3b27a6ef3d142f0eb0addc815b9c2541e5db5" dependencies = [ "anyhow", "cfg-if 0.1.10", - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", + "cranelift-codegen 0.67.0", + "cranelift-entity 0.67.0", + "cranelift-frontend 0.67.0", "cranelift-native", "cranelift-wasm", "gimli 0.21.0", @@ -6380,7 +6710,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9db30040e0c99999706b4baf7d49b9e0e793672c4e988f5855194d08ac2dbd52" dependencies = [ - "cranelift-codegen", + "cranelift-codegen 0.67.0", "lightbeam", "wasmparser 0.59.0", "wasmtime-environ", @@ -6437,6 +6767,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "wast" +version = "33.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d04fe175c7f78214971293e7d8875673804e736092206a3a4544dbc12811c1b" +dependencies = [ + "leb128", +] + +[[package]] +name = "wat" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ec9c6ee01ae07a26adadcdfed22c7a97e0b8cbee9c06e0e96076ece5aeb5cfe" +dependencies = [ + "wast", +] + [[package]] name = "web-sys" version = "0.3.47" @@ -6475,6 +6823,16 @@ dependencies = [ "libc", ] +[[package]] +name = "which" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c14ef7e1b8b8ecfc75d5eca37949410046e66f15d185c01d70824f1f8111ef" +dependencies = [ + "libc", + "thiserror", +] + [[package]] name = "widestring" version = "0.4.3" diff --git a/Cargo.toml b/Cargo.toml index 4c5838a03e0..be9cb3bfdfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,6 +108,16 @@ delay_detector = ["neard/delay_detector"] rosetta_rpc = ["neard/rosetta_rpc"] protocol_feature_forward_chunk_parts = ["neard/protocol_feature_forward_chunk_parts"] nightly_protocol = ["near-primitives/nightly_protocol", "near-jsonrpc/nightly_protocol"] +# enable this to build neard with wasmer 1.0 runner +# now if none of wasmer0_default, wasmer1_default or wasmtime_default is enabled, wasmer1 would be default +wasmer1_default = ["node-runtime/wasmer1_default"] +wasmer0_default = ["node-runtime/wasmer0_default"] +wasmtime_default = ["node-runtime/wasmtime_default"] +# enable some of these to run runtime tests with wasmer 1.0, 0.x and wasmtime vm enabled +# but would not change default runner used by neard +wasmer1_vm = ["node-runtime/wasmer1_vm"] +wasmer0_vm = ["node-runtime/wasmer0_vm"] +wasmtime_vm = ["node-runtime/wasmtime_vm"] nightly_protocol_features = ["nightly_protocol", "neard/nightly_protocol_features", "protocol_feature_evm", "protocol_feature_block_header_v3"] protocol_feature_evm = ["neard/protocol_feature_evm", "testlib/protocol_feature_evm", "runtime-params-estimator/protocol_feature_evm"] protocol_feature_block_header_v3 = ["near-primitives/protocol_feature_block_header_v3", "near-chain/protocol_feature_block_header_v3", "neard/protocol_feature_block_header_v3"] diff --git a/deny.toml b/deny.toml index e5edbcac33d..6861b6e815c 100644 --- a/deny.toml +++ b/deny.toml @@ -127,6 +127,21 @@ skip = [ # actix 0.11 and actix-rt 2.0 use conflicting versions { name = "actix-macros", version = "=0.1.3" }, + # wasmer 1.0 use some newer version of crates than wasmer 0.17 & wasmtime, old ones are skipped here + { name = "cranelift-bforest", version = "=0.67.0" }, + { name = "cranelift-codegen", version = "=0.67.0" }, + { name = "cranelift-codegen-meta", version = "=0.67.0" }, + { name = "cranelift-codegen-shared", version = "=0.67.0" }, + { name = "cranelift-entity", version = "=0.67.0" }, + { name = "cranelift-frontend", version = "=0.67.0" }, + { name = "gimli", version = "=0.21.0" }, + { name = "libloading", version = "=0.5.2" }, + { name = "regalloc", version = "=0.0.30" }, + { name = "strsim", version = "=0.8.0" }, + { name = "wasmparser", version = "=0.57.0" }, + { name = "which", version = "=3.1.1" }, + { name = "object", version = "=0.23.0" }, + # hashbrown uses an older version { name = "ahash", version = "=0.4.7" }, ] diff --git a/runtime/near-vm-errors/src/lib.rs b/runtime/near-vm-errors/src/lib.rs index 60a475160e3..eb182fbf47c 100644 --- a/runtime/near-vm-errors/src/lib.rs +++ b/runtime/near-vm-errors/src/lib.rs @@ -34,6 +34,12 @@ pub enum FunctionCallError { WasmUnknownError, HostError(HostError), EvmError(EvmError), + /// An error message when wasmer 1.0 returns a wasmer::RuntimeError + WasmerRuntimeError(String), + /// A trap in Wasmer 1.0, not same as WasmTrap above, String is a machine readable form like "stk_ovf" + /// String is used instead of wasmer internal enum is because of BorshSerializable. + /// It can be convert back by wasmer_vm::TrapCode::from_str + Wasmer1Trap(String), } #[derive( Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize, RpcError, @@ -277,6 +283,8 @@ pub enum VMLogicError { EvmError(EvmError), } +impl std::error::Error for VMLogicError {} + /// An error that is caused by an operation on an inconsistent state. /// E.g. a deserialization error or an integer overflow. #[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize)] @@ -306,6 +314,19 @@ impl From for VMError { } } +impl From<&VMLogicError> for VMError { + fn from(err: &VMLogicError) -> Self { + match err { + VMLogicError::HostError(h) => { + VMError::FunctionCallError(FunctionCallError::HostError(h.clone())) + } + VMLogicError::ExternalError(s) => VMError::ExternalError(s.clone()), + VMLogicError::InconsistentStateError(e) => VMError::InconsistentStateError(e.clone()), + VMLogicError::EvmError(_) => unreachable!("Wasm can't return EVM error"), + } + } +} + impl fmt::Display for VMLogicError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { write!(f, "{:?}", self) @@ -341,6 +362,8 @@ impl fmt::Display for FunctionCallError { write!(f, "Unknown error during Wasm contract execution") } FunctionCallError::EvmError(e) => write!(f, "EVM: {:?}", e), + FunctionCallError::WasmerRuntimeError(e) => write!(f, "Wasmer Runtime: {}", e), + FunctionCallError::Wasmer1Trap(e) => write!(f, "Wasmer 1.0 trap: {}", e), } } } diff --git a/runtime/near-vm-logic/Cargo.toml b/runtime/near-vm-logic/Cargo.toml index e29a8b997c1..9f0daa3e84a 100644 --- a/runtime/near-vm-logic/Cargo.toml +++ b/runtime/near-vm-logic/Cargo.toml @@ -31,8 +31,9 @@ serde_json = {version= "1", features= ["preserve_order"]} [features] default = [] protocol_feature_evm = ["near-primitives-core/protocol_feature_evm"] +wasmer0_default = [] wasmtime_default = [] - +wasmer1_default = [] # Use this feature to enable counting of fees and costs applied. costs_counting = ["near-primitives-core/costs_counting"] diff --git a/runtime/near-vm-logic/src/config.rs b/runtime/near-vm-logic/src/config.rs index c0ca1d6cc9d..90f07944389 100644 --- a/runtime/near-vm-logic/src/config.rs +++ b/runtime/near-vm-logic/src/config.rs @@ -4,25 +4,58 @@ use std::hash::Hash; #[derive(Clone, Copy, Debug, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub enum VMKind { - /// Wasmer VM. - Wasmer, + /// Wasmer 0.17.x VM. + Wasmer0, /// Wasmtime VM. Wasmtime, + /// Wasmer 1.x VM. + Wasmer1, } impl Default for VMKind { - #[cfg(feature = "wasmer_default")] + #[cfg(all( + feature = "wasmer0_default", + not(feature = "wasmer1_default"), + not(feature = "wasmtime_default") + ))] fn default() -> Self { - VMKind::Wasmer + VMKind::Wasmer0 } - #[cfg(feature = "wasmtime_default")] + #[cfg(all( + not(feature = "wasmer0_default"), + feature = "wasmer1_default", + not(feature = "wasmtime_default") + ))] + fn default() -> Self { + VMKind::Wasmer1 + } + + #[cfg(all( + not(feature = "wasmer0_default"), + not(feature = "wasmer1_default"), + feature = "wasmtime_default" + ))] fn default() -> Self { VMKind::Wasmtime } - #[cfg(all(not(feature = "wasmer_default"), not(feature = "wasmtime_default")))] + #[cfg(all( + not(feature = "wasmer0_default"), + not(feature = "wasmer1_default"), + not(feature = "wasmtime_default") + ))] + fn default() -> Self { + VMKind::Wasmer0 + } + + // These features should be mutually exclusive, but implement this to work around CI cargo check --all-features + #[cfg(all( + feature = "wasmer0_default", + feature = "wasmer1_default", + feature = "wasmtime_default" + ))] fn default() -> Self { - VMKind::Wasmer + VMKind::Wasmer0 } } diff --git a/runtime/near-vm-runner-standalone/Cargo.toml b/runtime/near-vm-runner-standalone/Cargo.toml index fa6127043e4..e20cb42ad47 100644 --- a/runtime/near-vm-runner-standalone/Cargo.toml +++ b/runtime/near-vm-runner-standalone/Cargo.toml @@ -26,7 +26,7 @@ strum = "0.20" num-rational = { version = "0.3" } near-vm-logic = { path = "../near-vm-logic", version = "3.0.0", features = ["costs_counting"]} -near-vm-runner = { path = "../near-vm-runner", version = "3.0.0", features = ["wasmtime_vm"] } +near-vm-runner = { path = "../near-vm-runner", version = "3.0.0", features = ["wasmtime_vm", "wasmer1_vm"] } near-primitives-core = { path = "../../core/primitives-core", version = "0.1.0" } [features] diff --git a/runtime/near-vm-runner-standalone/src/main.rs b/runtime/near-vm-runner-standalone/src/main.rs index 60ec089a6cc..04ec2a2156a 100644 --- a/runtime/near-vm-runner-standalone/src/main.rs +++ b/runtime/near-vm-runner-standalone/src/main.rs @@ -185,7 +185,8 @@ fn main() { let vm_kind: VMKind = match matches.value_of("vm-kind") { Some(value) => match value { "wasmtime" => VMKind::Wasmtime, - "wasmer" => VMKind::Wasmer, + "wasmer" => VMKind::Wasmer0, + "wasmer1" => VMKind::Wasmer1, _ => VMKind::default(), }, None => VMKind::default(), diff --git a/runtime/near-vm-runner/Cargo.toml b/runtime/near-vm-runner/Cargo.toml index fae12028f03..57c7ef03d43 100644 --- a/runtime/near-vm-runner/Cargo.toml +++ b/runtime/near-vm-runner/Cargo.toml @@ -14,8 +14,12 @@ This crate implements the specification of the interface that Near blockchain ex [dependencies] borsh = "0.8.1" -wasmer-runtime = { version="0.17.1", features = ["default-backend-singlepass"], default-features = false, package = "wasmer-runtime-near" } +wasmer-runtime = { version="0.17.1", features = ["default-backend-singlepass"], default-features = false, package = "wasmer-runtime-near", optional = true } +# Always used even for wasmer 1.0 for validating wasm, will be replaced when refactor prepare.rs wasmer-runtime-core = {version = "0.17.1", package = "wasmer-runtime-core-near" } +wasmer = { version = "1.0.2", optional = true } +wasmer-types = { version = "1.0.2", optional = true } +wasmer-compiler-singlepass = { version = "1.0.2", optional = true } pwasm-utils = "0.12" parity-wasm = "0.41" wasmtime = { version = "0.20.0", default-features = false, optional = true } @@ -33,11 +37,12 @@ wabt = "0.9" bencher = "^0.1.5" [features] -default = [ "wasmer_default" ] +# all vms enabled for tests, but only one default vm, specified by runtime crate +default = ["wasmer0_vm", "wasmtime_vm", "wasmer1_vm"] +wasmer0_vm = [ "wasmer-runtime" ] wasmtime_vm = [ "wasmtime", "anyhow"] +wasmer1_vm = [ "wasmer", "wasmer-types", "wasmer-compiler-singlepass" ] lightbeam = ["wasmtime/lightbeam"] -wasmer_default = [] -wasmtime_default = ["wasmtime_vm"] no_cpu_compatibility_checks = [] protocol_feature_evm = ["near-primitives/protocol_feature_evm", "near-evm-runner/protocol_feature_evm"] diff --git a/runtime/near-vm-runner/src/cache.rs b/runtime/near-vm-runner/src/cache.rs index de4e0ba0bc6..bcecfc40e22 100644 --- a/runtime/near-vm-runner/src/cache.rs +++ b/runtime/near-vm-runner/src/cache.rs @@ -9,17 +9,6 @@ use near_vm_errors::CacheError::{DeserializationError, ReadError, SerializationE use near_vm_errors::{CacheError, VMError}; use near_vm_logic::{VMConfig, VMKind}; use std::convert::TryFrom; -use wasmer_runtime::{compiler_for_backend, Backend}; -use wasmer_runtime_core::cache::Artifact; -use wasmer_runtime_core::load_cache_with; - -pub(crate) fn compile_module( - code: &[u8], - config: &VMConfig, -) -> Result { - let prepared_code = prepare::prepare_contract(code, config)?; - wasmer_runtime::compile(&prepared_code).map_err(|err| err.into_vm_error()) -} #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] enum ContractCacheKey { @@ -55,95 +44,192 @@ fn cache_error(error: VMError, key: &CryptoHash, cache: &dyn CompiledContractCac } } -pub(crate) fn compile_and_serialize_wasmer( - wasm_code: &[u8], - config: &VMConfig, - key: &CryptoHash, - cache: &dyn CompiledContractCache, -) -> Result { - let module = compile_module(wasm_code, config).map_err(|e| cache_error(e, &key, cache))?; - let artifact = - module.cache().map_err(|_e| VMError::CacheError(SerializationError { hash: (key.0).0 }))?; - let code = artifact - .serialize() - .map_err(|_e| VMError::CacheError(SerializationError { hash: (key.0).0 }))?; - let serialized = CacheRecord::Code(code).try_to_vec().unwrap(); - cache.put(key.as_ref(), &serialized).map_err(|_e| VMError::CacheError(WriteError))?; - Ok(module) -} +#[cfg(feature = "wasmer0_vm")] +pub mod wasmer0_cache { + use super::*; + use wasmer_runtime::{compiler_for_backend, Backend}; + use wasmer_runtime_core::cache::Artifact; + use wasmer_runtime_core::load_cache_with; -/// Deserializes contract or error from the binary data. Signature means that we could either -/// return module or cached error, which both considered to be `Ok()`, or encounter an error during -/// the deserialization process. -fn deserialize_wasmer( - serialized: &[u8], -) -> Result, CacheError> { - let record = CacheRecord::try_from_slice(serialized).map_err(|_e| DeserializationError)?; - let serialized_artifact = match record { - CacheRecord::Error(err) => return Ok(Err(err)), - CacheRecord::Code(code) => code, - }; - let artifact = Artifact::deserialize(serialized_artifact.as_slice()) - .map_err(|_e| CacheError::DeserializationError)?; - unsafe { - let compiler = compiler_for_backend(Backend::Singlepass).unwrap(); - match load_cache_with(artifact, compiler.as_ref()) { - Ok(module) => Ok(Ok(module)), - Err(_) => Err(CacheError::DeserializationError), - } + pub(crate) fn compile_module( + code: &[u8], + config: &VMConfig, + ) -> Result { + let prepared_code = prepare::prepare_contract(code, config)?; + wasmer_runtime::compile(&prepared_code).map_err(|err| err.into_vm_error()) } -} -fn compile_module_cached_wasmer_impl( - key: CryptoHash, - wasm_code: &[u8], - config: &VMConfig, - cache: Option<&dyn CompiledContractCache>, -) -> Result { - if cache.is_none() { - return compile_module(wasm_code, config); + pub(crate) fn compile_and_serialize_wasmer( + wasm_code: &[u8], + config: &VMConfig, + key: &CryptoHash, + cache: &dyn CompiledContractCache, + ) -> Result { + let module = compile_module(wasm_code, config).map_err(|e| cache_error(e, &key, cache))?; + let artifact = module + .cache() + .map_err(|_e| VMError::CacheError(SerializationError { hash: (key.0).0 }))?; + let code = artifact + .serialize() + .map_err(|_e| VMError::CacheError(SerializationError { hash: (key.0).0 }))?; + let serialized = CacheRecord::Code(code).try_to_vec().unwrap(); + cache.put(key.as_ref(), &serialized).map_err(|_e| VMError::CacheError(WriteError))?; + Ok(module) } - let cache = cache.unwrap(); - match cache.get(&(key.0).0) { - Ok(serialized) => match serialized { - Some(serialized) => { - deserialize_wasmer(serialized.as_slice()).map_err(VMError::CacheError)? + + /// Deserializes contract or error from the binary data. Signature means that we could either + /// return module or cached error, which both considered to be `Ok()`, or encounter an error during + /// the deserialization process. + fn deserialize_wasmer( + serialized: &[u8], + ) -> Result, CacheError> { + let record = CacheRecord::try_from_slice(serialized).map_err(|_e| DeserializationError)?; + let serialized_artifact = match record { + CacheRecord::Error(err) => return Ok(Err(err)), + CacheRecord::Code(code) => code, + }; + let artifact = Artifact::deserialize(serialized_artifact.as_slice()) + .map_err(|_e| CacheError::DeserializationError)?; + unsafe { + let compiler = compiler_for_backend(Backend::Singlepass).unwrap(); + match load_cache_with(artifact, compiler.as_ref()) { + Ok(module) => Ok(Ok(module)), + Err(_) => Err(CacheError::DeserializationError), } - None => compile_and_serialize_wasmer(wasm_code, config, &key, cache), - }, - Err(_) => Err(VMError::CacheError(ReadError)), + } } -} -#[cfg(not(feature = "no_cache"))] -const CACHE_SIZE: usize = 128; + fn compile_module_cached_wasmer_impl( + key: CryptoHash, + wasm_code: &[u8], + config: &VMConfig, + cache: Option<&dyn CompiledContractCache>, + ) -> Result { + if cache.is_none() { + return compile_module(wasm_code, config); + } -#[cfg(not(feature = "no_cache"))] -cached_key! { - MODULES: SizedCache> - = SizedCache::with_size(CACHE_SIZE); - Key = { - key - }; + let cache = cache.unwrap(); + match cache.get(&(key.0).0) { + Ok(serialized) => match serialized { + Some(serialized) => { + deserialize_wasmer(serialized.as_slice()).map_err(VMError::CacheError)? + } + None => compile_and_serialize_wasmer(wasm_code, config, &key, cache), + }, + Err(_) => Err(VMError::CacheError(ReadError)), + } + } - fn memcache_compile_module_cached_wasmer( - key: CryptoHash, + #[cfg(not(feature = "no_cache"))] + const CACHE_SIZE: usize = 128; + + #[cfg(not(feature = "no_cache"))] + cached_key! { + MODULES: SizedCache> + = SizedCache::with_size(CACHE_SIZE); + Key = { + key + }; + + fn memcache_compile_module_cached_wasmer( + key: CryptoHash, + wasm_code: &[u8], + config: &VMConfig, + cache: Option<&dyn CompiledContractCache>) -> Result = { + compile_module_cached_wasmer_impl(key, wasm_code, config, cache) + } + } + + pub(crate) fn compile_module_cached_wasmer( + wasm_code_hash: &[u8], wasm_code: &[u8], config: &VMConfig, - cache: Option<&dyn CompiledContractCache>) -> Result = { - compile_module_cached_wasmer_impl(key, wasm_code, config, cache) + cache: Option<&dyn CompiledContractCache>, + ) -> Result { + let key = get_key(wasm_code_hash, wasm_code, VMKind::Wasmer0, config); + #[cfg(not(feature = "no_cache"))] + return memcache_compile_module_cached_wasmer(key, wasm_code, config, cache); + #[cfg(feature = "no_cache")] + return compile_module_cached_wasmer_impl(key, wasm_code, config, cache); } } -pub(crate) fn compile_module_cached_wasmer( - wasm_code_hash: &[u8], - wasm_code: &[u8], - config: &VMConfig, - cache: Option<&dyn CompiledContractCache>, -) -> Result { - let key = get_key(wasm_code_hash, wasm_code, VMKind::Wasmer, config); - #[cfg(not(feature = "no_cache"))] - return memcache_compile_module_cached_wasmer(key, wasm_code, config, cache); - #[cfg(feature = "no_cache")] - return compile_module_cached_wasmer_impl(key, wasm_code, config, cache); +#[cfg(feature = "wasmer1_vm")] +pub mod wasmer1_cache { + use super::*; + + pub(crate) fn compile_module_cached_wasmer1( + wasm_code_hash: &[u8], + wasm_code: &[u8], + config: &VMConfig, + cache: Option<&dyn CompiledContractCache>, + store: &wasmer::Store, + ) -> Result { + let key = get_key(wasm_code_hash, wasm_code, VMKind::Wasmer1, config); + return compile_module_cached_wasmer1_impl(key, wasm_code, config, cache, store); + } + + fn compile_module_wasmer1( + code: &[u8], + config: &VMConfig, + store: &wasmer::Store, + ) -> Result { + let prepared_code = prepare::prepare_contract(code, config)?; + wasmer::Module::new(&store, prepared_code).map_err(|err| err.into_vm_error()) + } + + pub(crate) fn compile_and_serialize_wasmer1( + wasm_code: &[u8], + key: &CryptoHash, + config: &VMConfig, + cache: &dyn CompiledContractCache, + store: &wasmer::Store, + ) -> Result { + let module = compile_module_wasmer1(wasm_code, config, store) + .map_err(|e| cache_error(e, &key, cache))?; + let code = module + .serialize() + .map_err(|_e| VMError::CacheError(SerializationError { hash: (key.0).0 }))?; + let serialized = CacheRecord::Code(code).try_to_vec().unwrap(); + cache.put(key.as_ref(), &serialized).map_err(|_e| VMError::CacheError(WriteError))?; + Ok(module) + } + + fn deserialize_wasmer1( + serialized: &[u8], + store: &wasmer::Store, + ) -> Result, CacheError> { + let record = CacheRecord::try_from_slice(serialized).map_err(|_e| DeserializationError)?; + let serialized_module = match record { + CacheRecord::Error(err) => return Ok(Err(err)), + CacheRecord::Code(code) => code, + }; + unsafe { + Ok(Ok(wasmer::Module::deserialize(store, serialized_module.as_slice()) + .map_err(|_e| CacheError::DeserializationError)?)) + } + } + + fn compile_module_cached_wasmer1_impl( + key: CryptoHash, + wasm_code: &[u8], + config: &VMConfig, + cache: Option<&dyn CompiledContractCache>, + store: &wasmer::Store, + ) -> Result { + if cache.is_none() { + return compile_module_wasmer1(wasm_code, config, store); + } + + let cache = cache.unwrap(); + match cache.get(&(key.0).0) { + Ok(serialized) => match serialized { + Some(serialized) => deserialize_wasmer1(serialized.as_slice(), store) + .map_err(VMError::CacheError)?, + None => compile_and_serialize_wasmer1(wasm_code, &key, config, cache, store), + }, + Err(_) => Err(VMError::CacheError(ReadError)), + } + } } diff --git a/runtime/near-vm-runner/src/imports.rs b/runtime/near-vm-runner/src/imports.rs index a5433b68660..d3d95ecfa9d 100644 --- a/runtime/near-vm-runner/src/imports.rs +++ b/runtime/near-vm-runner/src/imports.rs @@ -3,10 +3,21 @@ use near_vm_logic::VMLogic; use std::ffi::c_void; -struct ImportReference(*mut c_void); +#[derive(Clone)] +pub struct ImportReference(pub *mut c_void); unsafe impl Send for ImportReference {} unsafe impl Sync for ImportReference {} +#[cfg(feature = "wasmer1_vm")] +use wasmer::{Memory, WasmerEnv}; + +#[derive(WasmerEnv, Clone)] +#[cfg(feature = "wasmer1_vm")] +pub struct NearWasmerEnv { + pub memory: Memory, + pub logic: ImportReference, +} + // Wasm has only i32/i64 types, so Wasmtime 0.17 only accepts // external functions taking i32/i64 type. // Remove, once using version with https://github.com/bytecodealliance/wasmtime/issues/1829 @@ -26,6 +37,7 @@ macro_rules! rust2wasm { macro_rules! wrapped_imports { ( $($(#[$feature_name:tt, $feature:ident])* $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] >, )* ) => { + #[cfg(feature = "wasmer0_vm")] pub mod wasmer_ext { use near_vm_logic::VMLogic; use wasmer_runtime::Ctx; @@ -40,6 +52,22 @@ macro_rules! wrapped_imports { )* } + #[cfg(feature = "wasmer1_vm")] + pub mod wasmer1_ext { + use near_vm_logic::VMLogic; + use crate::imports::NearWasmerEnv; + + type VMResult = ::std::result::Result; + $( + #[allow(unused_parens)] + $(#[cfg(feature = $feature_name)])* + pub fn $func(env: &NearWasmerEnv, $( $arg_name: $arg_type ),* ) -> VMResult<($( $returns ),*)> { + let logic: &mut VMLogic = unsafe { &mut *(env.logic.0 as *mut VMLogic<'_>) }; + logic.$func( $( $arg_name, )* ) + } + )* + } + #[cfg(feature = "wasmtime_vm")] pub mod wasmtime_ext { use near_vm_logic::{VMLogic, VMLogicError}; @@ -79,6 +107,7 @@ macro_rules! wrapped_imports { } #[allow(unused_variables)] + #[cfg(feature = "wasmer0_vm")] pub(crate) fn build_wasmer( memory: wasmer_runtime::memory::Memory, logic: &mut VMLogic<'_>, @@ -104,6 +133,23 @@ macro_rules! wrapped_imports { import_object } + #[cfg(feature = "wasmer1_vm")] + pub(crate) fn build_wasmer1(store: &wasmer::Store, memory: wasmer::Memory, logic: &mut VMLogic<'_>) -> + wasmer::ImportObject { + let env = NearWasmerEnv {logic: ImportReference(logic as * mut _ as * mut c_void), memory: memory.clone()}; + let mut import_object = wasmer::ImportObject::new(); + let mut namespace = wasmer::Exports::new(); + namespace.insert("memory", memory); + $({ + $(#[cfg(feature = $feature_name)])* + if true $(&& near_primitives::checked_feature!($feature_name, $feature, protocol_version))* { + namespace.insert(stringify!($func), wasmer::Function::new_native_with_env(&store, env.clone(), wasmer1_ext::$func)); + } + })* + import_object.register("env", namespace); + import_object + } + #[cfg(feature = "wasmtime_vm")] #[allow(unused_variables)] pub(crate) fn link_wasmtime( diff --git a/runtime/near-vm-runner/src/lib.rs b/runtime/near-vm-runner/src/lib.rs index 0bec706e2bc..ac81bb4aa57 100644 --- a/runtime/near-vm-runner/src/lib.rs +++ b/runtime/near-vm-runner/src/lib.rs @@ -1,12 +1,21 @@ mod cache; mod errors; mod imports; +#[cfg(feature = "wasmer0_vm")] mod memory; + pub mod prepare; mod runner; + +#[cfg(feature = "wasmer0_vm")] mod wasmer_runner; + #[cfg(feature = "wasmtime_vm")] mod wasmtime_runner; + +#[cfg(feature = "wasmer1_vm")] +mod wasmer1_runner; + pub use near_vm_errors::VMError; pub use runner::compile_module; pub use runner::run; diff --git a/runtime/near-vm-runner/src/runner.rs b/runtime/near-vm-runner/src/runner.rs index de750834560..aacee847c8f 100644 --- a/runtime/near-vm-runner/src/runner.rs +++ b/runtime/near-vm-runner/src/runner.rs @@ -76,11 +76,18 @@ pub fn run_vm<'a>( current_protocol_version: ProtocolVersion, cache: Option<&'a dyn CompiledContractCache>, ) -> (Option, Option) { + #[cfg(feature = "wasmer0_vm")] use crate::wasmer_runner::run_wasmer; + #[cfg(feature = "wasmtime_vm")] use crate::wasmtime_runner::wasmtime_runner::run_wasmtime; + + #[cfg(feature = "wasmer1_vm")] + use crate::wasmer1_runner::run_wasmer1; + match vm_kind { - VMKind::Wasmer => run_wasmer( + #[cfg(feature = "wasmer0_vm")] + VMKind::Wasmer0 => run_wasmer( code_hash, code, method_name, @@ -93,6 +100,8 @@ pub fn run_vm<'a>( current_protocol_version, cache, ), + #[cfg(not(feature = "wasmer0_vm"))] + VMKind::Wasmer0 => panic!("Wasmer0 is not supported, compile with '--features wasmer0_vm'"), #[cfg(feature = "wasmtime_vm")] VMKind::Wasmtime => run_wasmtime( code_hash, @@ -111,6 +120,22 @@ pub fn run_vm<'a>( VMKind::Wasmtime => { panic!("Wasmtime is not supported, compile with '--features wasmtime_vm'") } + #[cfg(feature = "wasmer1_vm")] + VMKind::Wasmer1 => run_wasmer1( + code_hash, + code, + method_name, + ext, + context, + wasm_config, + fees_config, + promise_results, + None, + current_protocol_version, + cache, + ), + #[cfg(not(feature = "wasmer1_vm"))] + VMKind::Wasmer1 => panic!("Wasmer1 is not supported, compile with '--features wasmer1_vm'"), } } @@ -128,11 +153,17 @@ pub fn run_vm_profiled<'a>( current_protocol_version: ProtocolVersion, cache: Option<&'a dyn CompiledContractCache>, ) -> (Option, Option) { + #[cfg(feature = "wasmer0_vm")] use crate::wasmer_runner::run_wasmer; + #[cfg(feature = "wasmtime_vm")] use crate::wasmtime_runner::wasmtime_runner::run_wasmtime; + + #[cfg(feature = "wasmer1_vm")] + use crate::wasmer1_runner::run_wasmer1; let (outcome, error) = match vm_kind { - VMKind::Wasmer => run_wasmer( + #[cfg(feature = "wasmer0_vm")] + VMKind::Wasmer0 => run_wasmer( code_hash, code, method_name, @@ -145,6 +176,8 @@ pub fn run_vm_profiled<'a>( current_protocol_version, cache, ), + #[cfg(not(feature = "wasmer0_vm"))] + VMKind::Wasmer0 => panic!("Wasmer0 is not supported, compile with '--features wasmer0_vm'"), #[cfg(feature = "wasmtime_vm")] VMKind::Wasmtime => run_wasmtime( code_hash, @@ -163,6 +196,22 @@ pub fn run_vm_profiled<'a>( VMKind::Wasmtime => { panic!("Wasmtime is not supported, compile with '--features wasmtime_vm'") } + #[cfg(feature = "wasmer1_vm")] + VMKind::Wasmer1 => run_wasmer1( + code_hash, + code, + method_name, + ext, + context, + wasm_config, + fees_config, + promise_results, + Some(profile.clone()), + current_protocol_version, + cache, + ), + #[cfg(not(feature = "wasmer1_vm"))] + VMKind::Wasmer1 => panic!("Wasmer1 is not supported, compile with '--features wasmer1_vm'"), }; match &outcome { Some(VMOutcome { burnt_gas, .. }) => profile.set_burnt_gas(*burnt_gas), @@ -182,12 +231,35 @@ pub fn precompile<'a>( cache: &'a dyn CompiledContractCache, vm_kind: VMKind, ) -> Option { - use crate::cache::compile_and_serialize_wasmer; match vm_kind { - VMKind::Wasmer => { - let result = compile_and_serialize_wasmer(code, wasm_config, code_hash, cache); + #[cfg(not(feature = "wasmer0_vm"))] + VMKind::Wasmer0 => panic!("Wasmer0 is not supported, compile with '--features wasmer0_vm'"), + #[cfg(feature = "wasmer0_vm")] + VMKind::Wasmer0 => { + let result = crate::cache::wasmer0_cache::compile_and_serialize_wasmer( + code, + wasm_config, + code_hash, + cache, + ); + result.err() + } + #[cfg(feature = "wasmer1_vm")] + VMKind::Wasmer1 => { + let engine = + wasmer::JIT::new(wasmer_compiler_singlepass::Singlepass::default()).engine(); + let store = wasmer::Store::new(&engine); + let result = crate::cache::wasmer1_cache::compile_and_serialize_wasmer1( + code, + code_hash, + wasm_config, + cache, + &store, + ); result.err() } + #[cfg(not(feature = "wasmer1_vm"))] + VMKind::Wasmer1 => panic!("Wasmer1 is not supported, compile with '--features wasmer1_vm'"), VMKind::Wasmtime => Some(VMError::FunctionCallError(FunctionCallError::CompilationError( CompilationError::UnsupportedCompiler { msg: "Precompilation not supported in Wasmtime yet".to_string(), @@ -197,18 +269,26 @@ pub fn precompile<'a>( } pub fn with_vm_variants(runner: fn(VMKind) -> ()) { - runner(VMKind::Wasmer); + #[cfg(feature = "wasmer0_vm")] + runner(VMKind::Wasmer0); + #[cfg(feature = "wasmtime_vm")] runner(VMKind::Wasmtime); + + #[cfg(feature = "wasmer1_vm")] + runner(VMKind::Wasmer1); } /// Used for testing cost of compiling a module pub fn compile_module(vm_kind: VMKind, code: &Vec) -> bool { match vm_kind { - VMKind::Wasmer => { + #[cfg(feature = "wasmer0_vm")] + VMKind::Wasmer0 => { use crate::wasmer_runner::compile_module; compile_module(code) } + #[cfg(not(feature = "wasmer0_vm"))] + VMKind::Wasmer0 => panic!("Wasmer0 is not supported, compile with '--features wasmer0_vm'"), #[cfg(feature = "wasmtime_vm")] VMKind::Wasmtime => { use crate::wasmtime_runner::compile_module; @@ -218,6 +298,13 @@ pub fn compile_module(vm_kind: VMKind, code: &Vec) -> bool { VMKind::Wasmtime => { panic!("Wasmtime is not supported, compile with '--features wasmtime_vm'") } + #[cfg(feature = "wasmer1_vm")] + VMKind::Wasmer1 => { + use crate::wasmer1_runner::compile_module; + compile_module(code) + } + #[cfg(not(feature = "wasmer1_vm"))] + VMKind::Wasmer1 => panic!("Wasmer1 is not supported, compile with '--features wasmer1_vm'"), }; false } diff --git a/runtime/near-vm-runner/src/wasmer1_runner.rs b/runtime/near-vm-runner/src/wasmer1_runner.rs new file mode 100644 index 00000000000..b76b36f8679 --- /dev/null +++ b/runtime/near-vm-runner/src/wasmer1_runner.rs @@ -0,0 +1,253 @@ +use crate::errors::IntoVMError; +use crate::{cache, imports}; +use near_primitives::runtime::fees::RuntimeFeesConfig; +use near_primitives::{profile::ProfileData, types::CompiledContractCache}; +use near_vm_errors::{ + CompilationError, FunctionCallError, MethodResolveError, PrepareError, VMError, +}; +use near_vm_logic::types::{PromiseResult, ProtocolVersion}; +use near_vm_logic::{External, MemoryLike, VMConfig, VMContext, VMLogic, VMLogicError, VMOutcome}; +use wasmer::{Bytes, Instance, Memory, MemoryType, Module, Pages, Store, JIT}; +use wasmer_compiler_singlepass::Singlepass; + +pub struct Wasmer1Memory(Memory); + +impl Wasmer1Memory { + pub fn new( + store: &Store, + initial_memory_pages: u32, + max_memory_pages: u32, + ) -> Result { + Ok(Wasmer1Memory( + Memory::new( + &store, + MemoryType::new(Pages(initial_memory_pages), Some(Pages(max_memory_pages)), false), + ) + .expect("TODO creating memory cannot fail"), + )) + } + + pub fn clone(&self) -> Memory { + self.0.clone() + } +} + +impl MemoryLike for Wasmer1Memory { + fn fits_memory(&self, offset: u64, len: u64) -> bool { + match offset.checked_add(len) { + None => false, + Some(end) => self.0.size().bytes() >= Bytes(end as usize), + } + } + + fn read_memory(&self, offset: u64, buffer: &mut [u8]) { + let offset = offset as usize; + for (i, cell) in self.0.view()[offset..(offset + buffer.len())].iter().enumerate() { + buffer[i] = cell.get(); + } + } + + fn read_memory_u8(&self, offset: u64) -> u8 { + self.0.view()[offset as usize].get() + } + + fn write_memory(&mut self, offset: u64, buffer: &[u8]) { + let offset = offset as usize; + self.0.view()[offset..(offset + buffer.len())] + .iter() + .zip(buffer.iter()) + .for_each(|(cell, v)| cell.set(*v)); + } +} + +impl IntoVMError for wasmer::CompileError { + fn into_vm_error(self) -> VMError { + VMError::FunctionCallError(FunctionCallError::CompilationError( + CompilationError::WasmerCompileError { msg: self.to_string() }, + )) + } +} + +impl IntoVMError for wasmer::InstantiationError { + fn into_vm_error(self) -> VMError { + match self { + wasmer::InstantiationError::Link(e) => { + VMError::FunctionCallError(FunctionCallError::LinkError { msg: e.to_string() }) + } + wasmer::InstantiationError::Start(e) => e.into_vm_error(), + wasmer::InstantiationError::HostEnvInitialization(_) => { + VMError::FunctionCallError(FunctionCallError::CompilationError( + CompilationError::PrepareError(PrepareError::Instantiate), + )) + } + } + } +} + +impl IntoVMError for wasmer::RuntimeError { + fn into_vm_error(self) -> VMError { + // These vars are not used in every cases, however, downcast below use Arc::try_unwrap + // so we cannot clone self + let error_msg = self.message(); + let trap_code = self.clone().to_trap(); + match self.downcast::() { + Ok(e) => (&e).into(), + _ => { + if let Some(trap_code) = trap_code { + // A trap + VMError::FunctionCallError(FunctionCallError::Wasmer1Trap( + trap_code.to_string(), + )) + } else { + // A general error + VMError::FunctionCallError(FunctionCallError::WasmerRuntimeError(error_msg)) + } + } + } + } +} + +impl IntoVMError for wasmer::ExportError { + fn into_vm_error(self) -> VMError { + match self { + wasmer::ExportError::IncompatibleType => VMError::FunctionCallError( + FunctionCallError::MethodResolveError(MethodResolveError::MethodInvalidSignature), + ), + wasmer::ExportError::Missing(_) => VMError::FunctionCallError( + FunctionCallError::MethodResolveError(MethodResolveError::MethodNotFound), + ), + } + } +} + +fn check_method(module: &Module, method_name: &str) -> Result<(), VMError> { + let info = module.info(); + use wasmer_types::ExportIndex::Function; + if let Some(Function(index)) = info.exports.get(method_name) { + let func = info.functions.get(index.clone()).unwrap(); + let sig = info.signatures.get(func.clone()).unwrap(); + if sig.params().is_empty() && sig.results().is_empty() { + Ok(()) + } else { + Err(VMError::FunctionCallError(FunctionCallError::MethodResolveError( + MethodResolveError::MethodInvalidSignature, + ))) + } + } else { + Err(VMError::FunctionCallError(FunctionCallError::MethodResolveError( + MethodResolveError::MethodNotFound, + ))) + } +} + +pub fn run_wasmer1<'a>( + code_hash: Vec, + code: &[u8], + method_name: &[u8], + ext: &mut dyn External, + context: VMContext, + wasm_config: &'a VMConfig, + fees_config: &'a RuntimeFeesConfig, + promise_results: &'a [PromiseResult], + profile: Option, + current_protocol_version: ProtocolVersion, + cache: Option<&'a dyn CompiledContractCache>, +) -> (Option, Option) { + // NaN behavior is deterministic as of now: https://github.com/wasmerio/wasmer/issues/1269 + // So doesn't require x86. However, when it is on x86, AVX is required: + // https://github.com/wasmerio/wasmer/issues/1567 + #[cfg(not(feature = "no_cpu_compatibility_checks"))] + if (cfg!(target_arch = "x86") || !cfg!(target_arch = "x86_64")) + && !is_x86_feature_detected!("avx") + { + panic!("AVX support is required in order to run Wasmer VM Singlepass backend."); + } + + if method_name.is_empty() { + return ( + None, + Some(VMError::FunctionCallError(FunctionCallError::MethodResolveError( + MethodResolveError::MethodEmptyName, + ))), + ); + } + + let engine = JIT::new(Singlepass::default()).engine(); + let store = Store::new(&engine); + let module = match cache::wasmer1_cache::compile_module_cached_wasmer1( + &code_hash, + &code, + wasm_config, + cache, + &store, + ) { + Ok(x) => x, + Err(err) => return (None, Some(err)), + }; + + let mut memory = Wasmer1Memory::new( + &store, + wasm_config.limit_config.initial_memory_pages, + wasm_config.limit_config.max_memory_pages, + ) + .expect("Cannot create memory for a contract call"); + // Note that we don't clone the actual backing memory, just increase the RC. + let memory_copy = memory.clone(); + + let mut logic = VMLogic::new_with_protocol_version( + ext, + context, + wasm_config, + fees_config, + promise_results, + &mut memory, + profile, + current_protocol_version, + ); + + if logic.add_contract_compile_fee(code.len() as u64).is_err() { + return ( + Some(logic.outcome()), + Some(VMError::FunctionCallError(FunctionCallError::HostError( + near_vm_errors::HostError::GasExceeded, + ))), + ); + } + let import_object = imports::build_wasmer1(&store, memory_copy, &mut logic); + + let method_name = match std::str::from_utf8(method_name) { + Ok(x) => x, + Err(_) => { + return ( + None, + Some(VMError::FunctionCallError(FunctionCallError::MethodResolveError( + MethodResolveError::MethodUTF8Error, + ))), + ) + } + }; + + if let Err(e) = check_method(&module, method_name) { + return (None, Some(e)); + } + + match Instance::new(&module, &import_object) { + Ok(instance) => match instance.exports.get_function(&method_name) { + Ok(f) => match f.native::<(), ()>() { + Ok(f) => match f.call() { + Ok(_) => (Some(logic.outcome()), None), + Err(e) => (Some(logic.outcome()), Some(e.into_vm_error())), + }, + Err(e) => (Some(logic.outcome()), Some(e.into_vm_error())), + }, + Err(e) => (Some(logic.outcome()), Some(e.into_vm_error())), + }, + Err(e) => (Some(logic.outcome()), Some(e.into_vm_error())), + } +} + +pub fn compile_module(code: &[u8]) -> bool { + let engine = JIT::new(Singlepass::default()).engine(); + let store = Store::new(&engine); + Module::new(&store, code).is_ok() +} diff --git a/runtime/near-vm-runner/src/wasmer_runner.rs b/runtime/near-vm-runner/src/wasmer_runner.rs index 98709cd6324..183a3069ff6 100644 --- a/runtime/near-vm-runner/src/wasmer_runner.rs +++ b/runtime/near-vm-runner/src/wasmer_runner.rs @@ -155,16 +155,7 @@ impl IntoVMError for wasmer_runtime::error::RuntimeError { } RuntimeError::User(data) => { if let Some(err) = data.downcast_ref::() { - match err { - VMLogicError::HostError(h) => { - VMError::FunctionCallError(FunctionCallError::HostError(h.clone())) - } - VMLogicError::ExternalError(s) => VMError::ExternalError(s.clone()), - VMLogicError::InconsistentStateError(e) => { - VMError::InconsistentStateError(e.clone()) - } - VMLogicError::EvmError(_) => unreachable!("Wasm can't return EVM error"), - } + err.into() } else { panic!( "Bad error case! Output is non-deterministic {:?} {:?}", @@ -210,7 +201,12 @@ pub fn run_wasmer<'a>( } // TODO: consider using get_module() here, once we'll go via deployment path. - let module = match cache::compile_module_cached_wasmer(&code_hash, code, wasm_config, cache) { + let module = match cache::wasmer0_cache::compile_module_cached_wasmer( + &code_hash, + code, + wasm_config, + cache, + ) { Ok(x) => x, Err(err) => return (None, Some(err)), }; diff --git a/runtime/near-vm-runner/tests/test_error_cases.rs b/runtime/near-vm-runner/tests/test_error_cases.rs index 67d3f2b30b5..b753acd0b87 100644 --- a/runtime/near-vm-runner/tests/test_error_cases.rs +++ b/runtime/near-vm-runner/tests/test_error_cases.rs @@ -150,13 +150,25 @@ fn trap_contract() -> Vec { #[test] fn test_trap_contract() { // See the comment is test_stack_overflow. - assert_eq!( - make_simple_contract_call_vm(&trap_contract(), b"hello", VMKind::Wasmer), - ( - Some(vm_outcome_with_gas(47105334)), - Some(VMError::FunctionCallError(FunctionCallError::WasmUnknownError)) - ) - ); + with_vm_variants(|vm_kind: VMKind| match vm_kind { + VMKind::Wasmtime => return, + VMKind::Wasmer0 => assert_eq!( + make_simple_contract_call_vm(&trap_contract(), b"hello", vm_kind), + ( + Some(vm_outcome_with_gas(47105334)), + Some(VMError::FunctionCallError(FunctionCallError::WasmUnknownError)) + ) + ), + VMKind::Wasmer1 => assert_eq!( + make_simple_contract_call_vm(&trap_contract(), b"hello", vm_kind), + ( + Some(vm_outcome_with_gas(47105334)), + Some(VMError::FunctionCallError(FunctionCallError::Wasmer1Trap( + "unreachable".to_string() + ))) + ) + ), + }) } fn trap_initializer() -> Vec { @@ -175,13 +187,25 @@ fn trap_initializer() -> Vec { #[test] fn test_trap_initializer() { // See the comment is test_stack_overflow. - assert_eq!( - make_simple_contract_call_vm(&trap_initializer(), b"hello", VMKind::Wasmer), - ( - Some(vm_outcome_with_gas(47755584)), - Some(VMError::FunctionCallError(FunctionCallError::WasmUnknownError)) - ) - ); + with_vm_variants(|vm_kind: VMKind| match vm_kind { + VMKind::Wasmtime => return, + VMKind::Wasmer0 => assert_eq!( + make_simple_contract_call_vm(&trap_initializer(), b"hello", vm_kind), + ( + Some(vm_outcome_with_gas(47755584)), + Some(VMError::FunctionCallError(FunctionCallError::WasmUnknownError)) + ) + ), + VMKind::Wasmer1 => assert_eq!( + make_simple_contract_call_vm(&trap_initializer(), b"hello", vm_kind), + ( + Some(vm_outcome_with_gas(47755584)), + Some(VMError::FunctionCallError(FunctionCallError::Wasmer1Trap( + "unreachable".to_string() + ))) + ) + ), + }); } fn wrong_signature_contract() -> Vec { @@ -279,15 +303,29 @@ fn stack_overflow() -> Vec { #[test] fn test_stack_overflow() { - // We only test trapping tests on Wasmer, as of version 0.17, when tests executed in parallel, - // Wasmer signal handlers may catch signals thrown from the Wasmtime, and produce fake failing tests. - assert_eq!( - make_simple_contract_call_vm(&stack_overflow(), b"hello", VMKind::Wasmer), - ( - Some(vm_outcome_with_gas(63226248177)), - Some(VMError::FunctionCallError(FunctionCallError::WasmUnknownError)) - ) - ); + with_vm_variants(|vm_kind: VMKind| { + // We only test trapping tests on Wasmer, as of version 0.17, when tests executed in parallel, + // Wasmer signal handlers may catch signals thrown from the Wasmtime, and produce fake failing tests. + match vm_kind { + VMKind::Wasmer0 => assert_eq!( + make_simple_contract_call_vm(&stack_overflow(), b"hello", vm_kind), + ( + Some(vm_outcome_with_gas(63226248177)), + Some(VMError::FunctionCallError(FunctionCallError::WasmUnknownError)) + ) + ), + VMKind::Wasmer1 => assert_eq!( + make_simple_contract_call_vm(&stack_overflow(), b"hello", vm_kind), + ( + Some(vm_outcome_with_gas(63226248177)), + Some(VMError::FunctionCallError(FunctionCallError::Wasmer1Trap( + "unreachable".to_string() + ))) + ) + ), + _ => {} + } + }); } fn memory_grow() -> Vec { @@ -366,7 +404,7 @@ fn test_bad_import_1() { CompilationError::PrepareError(PrepareError::Instantiate) ))) ) - ); + ) }); } @@ -389,8 +427,9 @@ fn test_bad_import_2() { fn test_bad_import_3() { with_vm_variants(|vm_kind: VMKind| { let msg = match vm_kind { - VMKind::Wasmer => "link error: Incorrect import type, namespace: env, name: input, expected type: global, found type: function", + VMKind::Wasmer0 => "link error: Incorrect import type, namespace: env, name: input, expected type: global, found type: function", VMKind::Wasmtime => "\"incompatible import type for `env::input` specified\\ndesired signature was: Global(GlobalType { content: I32, mutability: Const })\\nsignatures available:\\n\\n * Func(FuncType { params: [I64], results: [] })\\n\"", + VMKind::Wasmer1 => "Error while importing \"env\".\"input\": incompatible import type. Expected Global(GlobalType { ty: I32, mutability: Const }) but received Function(FunctionType { params: [I64], results: [] })" }.to_string(); assert_eq!( make_simple_contract_call_vm(&bad_import_global("env"), b"hello", vm_kind), @@ -406,8 +445,9 @@ fn test_bad_import_3() { fn test_bad_import_4() { with_vm_variants(|vm_kind: VMKind| { let msg = match vm_kind { - VMKind::Wasmer => "link error: Import not found, namespace: env, name: wtf", + VMKind::Wasmer0 => "link error: Import not found, namespace: env, name: wtf", VMKind::Wasmtime => "\"unknown import: `env::wtf` has not been defined\"", + VMKind::Wasmer1 => "Error while importing \"env\".\"wtf\": unknown import. Expected Function(FunctionType { params: [], results: [] })", } .to_string(); assert_eq!( @@ -532,16 +572,24 @@ fn test_external_call_error() { ); }); } + #[test] fn test_contract_error_caching() { - let mut cache = MockCompiledContractCache { store: Arc::new(Mutex::new(HashMap::new())) }; - let code = [42; 1000]; - let terragas = 1000000000000u64; - assert_eq!(cache.store.lock().unwrap().len(), 0); - let err1 = - make_cached_contract_call_vm(&mut cache, &code, b"method_name1", terragas, VMKind::Wasmer); - assert_eq!(cache.store.lock().unwrap().len(), 1); - let err2 = - make_cached_contract_call_vm(&mut cache, &code, b"method_name2", terragas, VMKind::Wasmer); - assert_eq!(err1, err2); + with_vm_variants(|vm_kind: VMKind| { + match vm_kind { + VMKind::Wasmtime => return, + _ => {} + } + let mut cache = MockCompiledContractCache { store: Arc::new(Mutex::new(HashMap::new())) }; + let code = [42; 1000]; + let terragas = 1000000000000u64; + assert_eq!(cache.store.lock().unwrap().len(), 0); + let err1 = + make_cached_contract_call_vm(&mut cache, &code, b"method_name1", terragas, vm_kind); + println!("{:?}", cache.store.lock().unwrap()); + assert_eq!(cache.store.lock().unwrap().len(), 1); + let err2 = + make_cached_contract_call_vm(&mut cache, &code, b"method_name2", terragas, vm_kind); + assert_eq!(err1, err2); + }) } diff --git a/runtime/near-vm-runner/tests/test_rs_contract.rs b/runtime/near-vm-runner/tests/test_rs_contract.rs index 14c26ae76c2..5ff9f8fa52e 100644 --- a/runtime/near-vm-runner/tests/test_rs_contract.rs +++ b/runtime/near-vm-runner/tests/test_rs_contract.rs @@ -223,27 +223,44 @@ def_test_ext!( #[test] pub fn test_out_of_memory() { - // TODO: currently we only run this test on Wasmer. - let code = &TEST_CONTRACT; - let mut fake_external = MockedExternal::new(); + with_vm_variants(|vm_kind: VMKind| { + // TODO: currently we only run this test on Wasmer. + match vm_kind { + VMKind::Wasmtime => return, + _ => {} + } - let context = create_context(&[]); - let config = VMConfig::free(); - let fees = RuntimeFeesConfig::free(); + let code = &TEST_CONTRACT; + let mut fake_external = MockedExternal::new(); - let promise_results = vec![]; - let result = run_vm( - vec![], - &code, - b"out_of_memory", - &mut fake_external, - context, - &config, - &fees, - &promise_results, - VMKind::Wasmer, - LATEST_PROTOCOL_VERSION, - None, - ); - assert_eq!(result.1, Some(VMError::FunctionCallError(FunctionCallError::WasmUnknownError))); + let context = create_context(&[]); + let config = VMConfig::free(); + let fees = RuntimeFeesConfig::free(); + + let promise_results = vec![]; + let result = run_vm( + vec![], + &code, + b"out_of_memory", + &mut fake_external, + context, + &config, + &fees, + &promise_results, + vm_kind, + LATEST_PROTOCOL_VERSION, + None, + ); + assert_eq!( + result.1, + match vm_kind { + VMKind::Wasmer0 => + Some(VMError::FunctionCallError(FunctionCallError::WasmUnknownError)), + VMKind::Wasmer1 => Some(VMError::FunctionCallError( + FunctionCallError::Wasmer1Trap("unreachable".to_string()) + )), + _ => unreachable!(), + } + ); + }) } diff --git a/runtime/runtime-params-estimator/src/main.rs b/runtime/runtime-params-estimator/src/main.rs index 27b7268d2b9..2dea62936e2 100644 --- a/runtime/runtime-params-estimator/src/main.rs +++ b/runtime/runtime-params-estimator/src/main.rs @@ -92,9 +92,9 @@ fn main() { other => panic!("Unknown metric {}", other), }; let vm_kind = match matches.value_of("vm-kind") { - Some("wasmer") => VMKind::Wasmer, + Some("wasmer") => VMKind::Wasmer0, Some("wasmtime") => VMKind::Wasmtime, - _ => VMKind::Wasmer, + _ => VMKind::Wasmer0, }; let disable_measure_action_creation = matches.is_present("action-creation"); let disable_measure_transaction = matches.is_present("transaction"); diff --git a/runtime/runtime-params-estimator/src/vm_estimator.rs b/runtime/runtime-params-estimator/src/vm_estimator.rs index 7a4ec8f3e17..f695f31c62a 100644 --- a/runtime/runtime-params-estimator/src/vm_estimator.rs +++ b/runtime/runtime-params-estimator/src/vm_estimator.rs @@ -157,7 +157,7 @@ pub fn cost_to_compile( println!( "About to compile {}", match vm_kind { - VMKind::Wasmer => "wasmer", + VMKind::Wasmer0 => "wasmer", VMKind::Wasmtime => { if USING_LIGHTBEAM { "wasmtime-lightbeam" @@ -165,6 +165,7 @@ pub fn cost_to_compile( "wasmtime" } } + VMKind::Wasmer1 => "wasmer1", } ); }; diff --git a/runtime/runtime/Cargo.toml b/runtime/runtime/Cargo.toml index 0731f142828..9177aaaa725 100644 --- a/runtime/runtime/Cargo.toml +++ b/runtime/runtime/Cargo.toml @@ -33,6 +33,12 @@ near-evm-runner = { path = "../../runtime/near-evm-runner", optional = true } default = [] dump_errors_schema = ["near-vm-errors/dump_errors_schema"] protocol_feature_evm = ["near-evm-runner/protocol_feature_evm", "near-primitives/protocol_feature_evm", "near-vm-runner/protocol_feature_evm"] +wasmer1_vm = ["near-vm-runner/wasmer1_vm"] +wasmer0_vm = ["near-vm-runner/wasmer0_vm"] +wasmtime_vm = ["near-vm-runner/wasmtime_vm"] +wasmer1_default = ["wasmer1_vm", "near-vm-logic/wasmer1_default"] +wasmer0_default = ["wasmer0_vm", "near-vm-logic/wasmer0_default"] +wasmtime_default = ["wasmtime_vm", "near-vm-logic/wasmtime_default"] # Use this feature to enable counting of fees and costs applied. costs_counting = ["near-vm-logic/costs_counting", "near-vm-runner/costs_counting"]