From 06de91968ea53c50141c77fe32c5af65df6616a3 Mon Sep 17 00:00:00 2001 From: vms Date: Fri, 12 Jun 2020 01:41:57 +0300 Subject: [PATCH] move node implementation to a separate folder --- examples/ipfs_node/src/imports.rs | 216 ------------------ examples/ipfs_node/src/lib.rs | 8 +- examples/ipfs_node/src/main.rs | 5 - examples/ipfs_node/src/{ => node}/config.rs | 2 +- examples/ipfs_node/src/{ => node}/errors.rs | 0 examples/ipfs_node/src/node/imports.rs | 100 ++++++++ examples/ipfs_node/src/node/mod.rs | 27 +++ examples/ipfs_node/src/{ => node}/node.rs | 82 +------ .../src/{ => node}/node_public_interface.rs | 0 examples/ipfs_node/src/node/utils.rs | 171 ++++++++++++++ fce/src/lib.rs | 1 + 11 files changed, 306 insertions(+), 306 deletions(-) delete mode 100644 examples/ipfs_node/src/imports.rs rename examples/ipfs_node/src/{ => node}/config.rs (99%) rename examples/ipfs_node/src/{ => node}/errors.rs (100%) create mode 100644 examples/ipfs_node/src/node/imports.rs create mode 100644 examples/ipfs_node/src/node/mod.rs rename examples/ipfs_node/src/{ => node}/node.rs (52%) rename examples/ipfs_node/src/{ => node}/node_public_interface.rs (100%) create mode 100644 examples/ipfs_node/src/node/utils.rs diff --git a/examples/ipfs_node/src/imports.rs b/examples/ipfs_node/src/imports.rs deleted file mode 100644 index dda98ee3..00000000 --- a/examples/ipfs_node/src/imports.rs +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright 2020 Fluence Labs Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use wasmer_core::vm::Ctx; -use wasmer_core::typed_func::DynamicFunc; -use wasmer_core::typed_func::WasmTypeList; -use wasmer_core::types::Value; -use wasmer_core::types::Type; -use wasmer_core::types::FuncSig; -use wasmer_runtime::Func; -use wasmer_runtime::error::ResolveError; -use wasmer_core::backend::SigRegistry; -use wasmer_runtime::types::LocalOrImport; -use wasmer_core::module::ExportIndex; - -use std::collections::HashMap; -use cmd_lib::run_fun; - -const ALLOCATE_FUNC_NAME: &str = "allocate"; -const SET_PTR_FUNC_NAME: &str = "set_result_ptr"; -const SET_SIZE_FUNC_NAME: &str = "set_result_size"; - -pub(super) fn log_utf8_string(ctx: &mut Ctx, offset: i32, size: i32) { - use wasmer_core::memory::ptr::{Array, WasmPtr}; - - let wasm_ptr = WasmPtr::::new(offset as _); - match wasm_ptr.get_utf8_string(ctx.memory(0), size as _) { - Some(msg) => print!("{}", msg), - None => println!("ipfs node logger: incorrect UTF8 string's been supplied to logger"), - } -} - -// based on Wasmer: https://github.com/wasmerio/wasmer/blob/081f6250e69b98b9f95a8f62ad6d8386534f3279/lib/runtime-core/src/instance.rs#L863 -unsafe fn get_export_func_by_name<'a, Args, Rets>( - ctx: &'a mut Ctx, - name: &str, -) -> Result, ResolveError> -where - Args: WasmTypeList, - Rets: WasmTypeList, -{ - let module_inner = &(*ctx.module); - - let export_index = - module_inner - .info - .exports - .get(name) - .ok_or_else(|| ResolveError::ExportNotFound { - name: name.to_string(), - })?; - - let export_func_index = match export_index { - ExportIndex::Func(func_index) => func_index, - _ => { - return Err(ResolveError::ExportWrongType { - name: name.to_string(), - }) - } - }; - - let export_func_signature_idx = *module_inner - .info - .func_assoc - .get(*export_func_index) - .expect("broken invariant, incorrect func index"); - - let export_func_signature = &module_inner.info.signatures[export_func_signature_idx]; - let export_func_signature_ref = SigRegistry.lookup_signature_ref(export_func_signature); - - if export_func_signature_ref.params() != Args::types() - || export_func_signature_ref.returns() != Rets::types() - { - return Err(ResolveError::Signature { - expected: (*export_func_signature).clone(), - found: Args::types().to_vec(), - }); - } - - let func_wasm_inner = module_inner - .runnable_module - .get_trampoline(&module_inner.info, export_func_signature_idx) - .unwrap(); - - let export_func_ptr = match export_func_index.local_or_import(&module_inner.info) { - LocalOrImport::Local(local_func_index) => module_inner - .runnable_module - .get_func(&module_inner.info, local_func_index) - .unwrap(), - _ => { - return Err(ResolveError::ExportNotFound { - name: name.to_string(), - }) - } - }; - - let typed_func: Func = - Func::from_raw_parts(func_wasm_inner, export_func_ptr, None, ctx as _); - - Ok(typed_func) -} - -#[allow(dead_code)] -fn to_full_path(cmd: S, mapped_dirs: &HashMap) -> String -where - S: Into, -{ - use std::str::pattern::Pattern; - - fn find_start_at<'a, P: Pattern<'a>>(slice: &'a str, at: usize, pat: P) -> Option { - slice[at..].find(pat).map(|i| at + i) - } - - let cmd = cmd.into(); - - if cmd.is_empty() || mapped_dirs.is_empty() { - return cmd; - } - - // assume that string is started with / - let from_dir = if let Some(found_pos) = find_start_at(&cmd, 1, '/') { - // it is safe because we are splitting on the found position - cmd.split_at(found_pos) - } else { - (cmd.as_str(), "") - }; - - match mapped_dirs.get(from_dir.0) { - Some(to_dir) => { - let ret = format!("{}/{}", to_dir, from_dir.1); - println!("ret is {}", ret); - ret - } - None => cmd, - } -} - -fn write_to_mem(context: &mut Ctx, address: usize, value: &[u8]) { - let memory = context.memory(0); - - for (byte_id, cell) in memory.view::()[address as usize..(address + value.len())] - .iter() - .enumerate() - { - cell.set(value[byte_id]); - } -} - -pub(super) fn create_host_import_func(host_cmd: S) -> DynamicFunc<'static> -where - S: Into, -{ - /* - let mut allocate_func: Option> = None; - let mut set_result_ptr: Option> = None; - let mut set_result_size: Option> = None; - */ - - let host_cmd = host_cmd.into(); - - let func = move |ctx: &mut Ctx, inputs: &[Value]| -> Vec { - use wasmer_core::memory::ptr::{Array, WasmPtr}; - - let array_ptr = inputs[0].to_u128() as i32; - let array_size = inputs[1].to_u128() as i32; - - let wasm_ptr = WasmPtr::::new(array_ptr as _); - let result = match wasm_ptr.get_utf8_string(ctx.memory(0), array_size as _) { - Some(arg_value) => { - // let arg_value = " add -Q /Users/mike/dev/work/fluence/wasm/tmp/ipfs_rpc_file"; - let output = run_fun!("{} {}", host_cmd, arg_value).unwrap(); - output - } - None => return vec![Value::I32(1)], - }; - - unsafe { - let mem_address = match get_export_func_by_name::(ctx, ALLOCATE_FUNC_NAME) { - Ok(func) => func.call(result.len() as i32).unwrap(), - Err(_) => return vec![Value::I32(2)], - }; - - write_to_mem(ctx, mem_address as usize, result.as_bytes()); - - match get_export_func_by_name::(ctx, SET_PTR_FUNC_NAME) { - Ok(func) => func.call(mem_address as i32).unwrap(), - Err(_) => return vec![Value::I32(3)], - }; - - match get_export_func_by_name::(ctx, SET_SIZE_FUNC_NAME) { - Ok(func) => func.call(result.len() as i32).unwrap(), - Err(_) => return vec![Value::I32(4)], - }; - - vec![Value::I32(0)] - } - }; - - DynamicFunc::new( - std::sync::Arc::new(FuncSig::new(vec![Type::I32, Type::I32], vec![Type::I32])), - func, - ) -} diff --git a/examples/ipfs_node/src/lib.rs b/examples/ipfs_node/src/lib.rs index d8b2357d..3af6831c 100644 --- a/examples/ipfs_node/src/lib.rs +++ b/examples/ipfs_node/src/lib.rs @@ -16,13 +16,7 @@ #![feature(pattern)] mod node; -mod errors; -mod config; -mod imports; -mod node_public_interface; pub use fce::IValue; -pub use node::IpfsNode; -pub use node_public_interface::NodePublicInterface; -pub use node_public_interface::NodeModulePublicInterface; +pub use node::*; diff --git a/examples/ipfs_node/src/main.rs b/examples/ipfs_node/src/main.rs index 8a95b846..009ad510 100644 --- a/examples/ipfs_node/src/main.rs +++ b/examples/ipfs_node/src/main.rs @@ -13,13 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#![feature(pattern)] mod node; -mod errors; -mod config; -mod imports; -mod node_public_interface; use fce::IValue; use node::IpfsNode; diff --git a/examples/ipfs_node/src/config.rs b/examples/ipfs_node/src/node/config.rs similarity index 99% rename from examples/ipfs_node/src/config.rs rename to examples/ipfs_node/src/node/config.rs index 02b56605..101de79c 100644 --- a/examples/ipfs_node/src/config.rs +++ b/examples/ipfs_node/src/node/config.rs @@ -14,7 +14,7 @@ * limitations under the License. */ -use crate::errors::NodeError; +use super::errors::NodeError; use serde_derive::Deserialize; use toml::from_slice; diff --git a/examples/ipfs_node/src/errors.rs b/examples/ipfs_node/src/node/errors.rs similarity index 100% rename from examples/ipfs_node/src/errors.rs rename to examples/ipfs_node/src/node/errors.rs diff --git a/examples/ipfs_node/src/node/imports.rs b/examples/ipfs_node/src/node/imports.rs new file mode 100644 index 00000000..762286d9 --- /dev/null +++ b/examples/ipfs_node/src/node/imports.rs @@ -0,0 +1,100 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::utils::get_export_func_by_name; + +use wasmer_core::vm::Ctx; +use wasmer_core::typed_func::DynamicFunc; +use wasmer_core::types::Value; +use wasmer_core::types::Type; +use wasmer_core::types::FuncSig; + +const ALLOCATE_FUNC_NAME: &str = "allocate"; +const SET_PTR_FUNC_NAME: &str = "set_result_ptr"; +const SET_SIZE_FUNC_NAME: &str = "set_result_size"; + +pub(super) fn log_utf8_string(ctx: &mut Ctx, offset: i32, size: i32) { + use wasmer_core::memory::ptr::{Array, WasmPtr}; + + let wasm_ptr = WasmPtr::::new(offset as _); + match wasm_ptr.get_utf8_string(ctx.memory(0), size as _) { + Some(msg) => print!("{}", msg), + None => println!("ipfs node logger: incorrect UTF8 string's been supplied to logger"), + } +} + +fn write_to_mem(context: &mut Ctx, address: usize, value: &[u8]) { + let memory = context.memory(0); + + for (byte_id, cell) in memory.view::()[address as usize..(address + value.len())] + .iter() + .enumerate() + { + cell.set(value[byte_id]); + } +} + +pub(super) fn create_host_import_func(host_cmd: S) -> DynamicFunc<'static> +where + S: Into, +{ + /* + let mut allocate_func: Option> = None; + let mut set_result_ptr: Option> = None; + let mut set_result_size: Option> = None; + */ + + let host_cmd = host_cmd.into(); + + let func = move |ctx: &mut Ctx, inputs: &[Value]| -> Vec { + use wasmer_core::memory::ptr::{Array, WasmPtr}; + + let array_ptr = inputs[0].to_u128() as i32; + let array_size = inputs[1].to_u128() as i32; + + let wasm_ptr = WasmPtr::::new(array_ptr as _); + let result = match wasm_ptr.get_utf8_string(ctx.memory(0), array_size as _) { + Some(arg_value) => cmd_lib::run_fun!("{} {}", host_cmd, arg_value).unwrap(), + None => return vec![Value::I32(1)], + }; + + unsafe { + let mem_address = match get_export_func_by_name::(ctx, ALLOCATE_FUNC_NAME) { + Ok(func) => func.call(result.len() as i32).unwrap(), + Err(_) => return vec![Value::I32(2)], + }; + + write_to_mem(ctx, mem_address as usize, result.as_bytes()); + + match get_export_func_by_name::(ctx, SET_PTR_FUNC_NAME) { + Ok(func) => func.call(mem_address as i32).unwrap(), + Err(_) => return vec![Value::I32(3)], + }; + + match get_export_func_by_name::(ctx, SET_SIZE_FUNC_NAME) { + Ok(func) => func.call(result.len() as i32).unwrap(), + Err(_) => return vec![Value::I32(4)], + }; + + vec![Value::I32(0)] + } + }; + + DynamicFunc::new( + std::sync::Arc::new(FuncSig::new(vec![Type::I32, Type::I32], vec![Type::I32])), + func, + ) +} diff --git a/examples/ipfs_node/src/node/mod.rs b/examples/ipfs_node/src/node/mod.rs new file mode 100644 index 00000000..f7cb11b2 --- /dev/null +++ b/examples/ipfs_node/src/node/mod.rs @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod config; +mod errors; +mod imports; +mod node; +mod node_public_interface; +mod utils; + +pub use node::IpfsNode; +pub use errors::NodeError; +pub use node_public_interface::NodePublicInterface; +pub use node_public_interface::NodeModulePublicInterface; diff --git a/examples/ipfs_node/src/node.rs b/examples/ipfs_node/src/node/node.rs similarity index 52% rename from examples/ipfs_node/src/node.rs rename to examples/ipfs_node/src/node/node.rs index 62103820..b5d968de 100644 --- a/examples/ipfs_node/src/node.rs +++ b/examples/ipfs_node/src/node/node.rs @@ -15,18 +15,14 @@ */ use super::errors::NodeError; -use crate::config::ModuleConfig; -use crate::node_public_interface::NodePublicInterface; -use crate::node_public_interface::NodeModulePublicInterface; +use super::node_public_interface::NodePublicInterface; +use super::node_public_interface::NodeModulePublicInterface; use fce::FCE; use fce::WasmProcess; use fce::IValue; use fce::FCEModuleConfig; -use wasmer_core::import::ImportObject; -use wasmer_runtime::func; -use std::collections::HashMap; use std::fs; use std::path::PathBuf; @@ -45,7 +41,7 @@ impl IpfsNode { let mut wasm_process = FCE::new(); let mut module_names = Vec::new(); let mut core_modules_config = - crate::config::parse_config_from_file(config_file_path.into())?; + super::config::parse_config_from_file(config_file_path.into())?; for entry in fs::read_dir(core_modules_dir.into())? { let path = entry?.path(); @@ -62,7 +58,7 @@ impl IpfsNode { let module_bytes = fs::read(path.clone())?; - let core_module_config = Self::make_wasm_process_config( + let core_module_config = super::utils::make_wasm_process_config( core_modules_config.modules_config.remove(&module_name), )?; wasm_process.load_module(module_name.clone(), &module_bytes, core_module_config)?; @@ -70,7 +66,7 @@ impl IpfsNode { } let rpc_module_config = - Self::make_wasm_process_config(core_modules_config.rpc_module_config)?; + super::utils::make_wasm_process_config(core_modules_config.rpc_module_config)?; Ok(Self { process: wasm_process, @@ -109,72 +105,4 @@ impl IpfsNode { NodePublicInterface { modules } } - - fn make_wasm_process_config( - config: Option, - ) -> Result { - use crate::imports::create_host_import_func; - use crate::imports::log_utf8_string; - use wasmer_core::import::Namespace; - - let mut wasm_module_config = FCEModuleConfig::default(); - - let module_config = match config { - Some(config) => config, - None => return Ok(wasm_module_config), - }; - - if let Some(mem_pages_count) = module_config.mem_pages_count { - wasm_module_config.mem_pages_count = mem_pages_count; - } - - let mut namespace = Namespace::new(); - - if let Some(logger_enabled) = module_config.logger_enabled { - if logger_enabled { - namespace.insert("log_utf8_string", func!(log_utf8_string)); - } - } - - if let Some(wasi) = module_config.wasi { - if let Some(envs) = wasi.envs { - wasm_module_config.wasi_envs = envs; - } - - if let Some(preopened_files) = wasi.preopened_files { - wasm_module_config.wasi_preopened_files = preopened_files - .iter() - .map(PathBuf::from) - .collect::>(); - } - - if let Some(mapped_dirs) = wasi.mapped_dirs { - wasm_module_config.wasi_mapped_dirs = mapped_dirs - .into_iter() - .map(|(from, to)| (from, PathBuf::from(to))) - .collect::>(); - } - }; - - let _mapped_dirs = wasm_module_config - .wasi_mapped_dirs - .iter() - .map(|(from, to)| (from.clone(), to.as_path().to_str().unwrap().to_string())) - .collect::>(); - - if let Some(imports) = module_config.imports { - for (import_name, host_cmd) in imports { - let host_import = create_host_import_func(host_cmd); - namespace.insert(import_name, host_import); - } - } - - let mut import_object = ImportObject::new(); - import_object.register("host", namespace); - - wasm_module_config.imports = import_object; - wasm_module_config.wasi_version = wasmer_wasi::WasiVersion::Latest; - - Ok(wasm_module_config) - } } diff --git a/examples/ipfs_node/src/node_public_interface.rs b/examples/ipfs_node/src/node/node_public_interface.rs similarity index 100% rename from examples/ipfs_node/src/node_public_interface.rs rename to examples/ipfs_node/src/node/node_public_interface.rs diff --git a/examples/ipfs_node/src/node/utils.rs b/examples/ipfs_node/src/node/utils.rs new file mode 100644 index 00000000..2c0f2852 --- /dev/null +++ b/examples/ipfs_node/src/node/utils.rs @@ -0,0 +1,171 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use wasmer_core::vm::Ctx; +use super::errors::NodeError; +use super::config::ModuleConfig; + +use fce::FCEModuleConfig; + +use wasmer_core::import::ImportObject; +use wasmer_runtime::func; +use wasmer_core::typed_func::WasmTypeList; +use wasmer_runtime::Func; +use wasmer_runtime::error::ResolveError; +use wasmer_core::backend::SigRegistry; +use wasmer_runtime::types::LocalOrImport; +use wasmer_core::module::ExportIndex; + +use std::collections::HashMap; +use std::path::PathBuf; + +// based on Wasmer: https://github.com/wasmerio/wasmer/blob/081f6250e69b98b9f95a8f62ad6d8386534f3279/lib/runtime-core/src/instance.rs#L863 +pub(super) unsafe fn get_export_func_by_name<'a, Args, Rets>( + ctx: &'a mut Ctx, + name: &str, +) -> Result, ResolveError> +where + Args: WasmTypeList, + Rets: WasmTypeList, +{ + let module_inner = &(*ctx.module); + + let export_index = + module_inner + .info + .exports + .get(name) + .ok_or_else(|| ResolveError::ExportNotFound { + name: name.to_string(), + })?; + + let export_func_index = match export_index { + ExportIndex::Func(func_index) => func_index, + _ => { + return Err(ResolveError::ExportWrongType { + name: name.to_string(), + }) + } + }; + + let export_func_signature_idx = *module_inner + .info + .func_assoc + .get(*export_func_index) + .expect("broken invariant, incorrect func index"); + + let export_func_signature = &module_inner.info.signatures[export_func_signature_idx]; + let export_func_signature_ref = SigRegistry.lookup_signature_ref(export_func_signature); + + if export_func_signature_ref.params() != Args::types() + || export_func_signature_ref.returns() != Rets::types() + { + return Err(ResolveError::Signature { + expected: (*export_func_signature).clone(), + found: Args::types().to_vec(), + }); + } + + let func_wasm_inner = module_inner + .runnable_module + .get_trampoline(&module_inner.info, export_func_signature_idx) + .unwrap(); + + let export_func_ptr = match export_func_index.local_or_import(&module_inner.info) { + LocalOrImport::Local(local_func_index) => module_inner + .runnable_module + .get_func(&module_inner.info, local_func_index) + .unwrap(), + _ => { + return Err(ResolveError::ExportNotFound { + name: name.to_string(), + }) + } + }; + + let typed_func: Func = + Func::from_raw_parts(func_wasm_inner, export_func_ptr, None, ctx as _); + + Ok(typed_func) +} + +pub(super) fn make_wasm_process_config( + config: Option, +) -> Result { + use super::imports::create_host_import_func; + use super::imports::log_utf8_string; + use wasmer_core::import::Namespace; + + let mut wasm_module_config = FCEModuleConfig::default(); + + let module_config = match config { + Some(config) => config, + None => return Ok(wasm_module_config), + }; + + if let Some(mem_pages_count) = module_config.mem_pages_count { + wasm_module_config.mem_pages_count = mem_pages_count; + } + + let mut namespace = Namespace::new(); + + if let Some(logger_enabled) = module_config.logger_enabled { + if logger_enabled { + namespace.insert("log_utf8_string", func!(log_utf8_string)); + } + } + + if let Some(wasi) = module_config.wasi { + if let Some(envs) = wasi.envs { + wasm_module_config.wasi_envs = envs; + } + + if let Some(preopened_files) = wasi.preopened_files { + wasm_module_config.wasi_preopened_files = preopened_files + .iter() + .map(PathBuf::from) + .collect::>(); + } + + if let Some(mapped_dirs) = wasi.mapped_dirs { + wasm_module_config.wasi_mapped_dirs = mapped_dirs + .into_iter() + .map(|(from, to)| (from, PathBuf::from(to))) + .collect::>(); + } + }; + + let _mapped_dirs = wasm_module_config + .wasi_mapped_dirs + .iter() + .map(|(from, to)| (from.clone(), to.as_path().to_str().unwrap().to_string())) + .collect::>(); + + if let Some(imports) = module_config.imports { + for (import_name, host_cmd) in imports { + let host_import = create_host_import_func(host_cmd); + namespace.insert(import_name, host_import); + } + } + + let mut import_object = ImportObject::new(); + import_object.register("host", namespace); + + wasm_module_config.imports = import_object; + wasm_module_config.wasi_version = wasmer_wasi::WasiVersion::Latest; + + Ok(wasm_module_config) +} diff --git a/fce/src/lib.rs b/fce/src/lib.rs index e34be3b9..025f512a 100644 --- a/fce/src/lib.rs +++ b/fce/src/lib.rs @@ -16,6 +16,7 @@ #![warn(rust_2018_idioms)] #![feature(get_mut_unchecked)] #![feature(new_uninit)] +#![feature(stmt_expr_attributes)] #![deny( dead_code, nonstandard_style,