mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-09 06:08:29 +00:00
Add an extra layer of indirection for shared globals
This commit is contained in:
@@ -699,20 +699,19 @@ impl<'module_environment> BaseFuncEnvironment for FuncEnvironment<'module_enviro
|
|||||||
|
|
||||||
let (ptr, offset) = {
|
let (ptr, offset) = {
|
||||||
let vmctx = self.vmctx(func);
|
let vmctx = self.vmctx(func);
|
||||||
if let Some(def_index) = self.module.local_global_index(index) {
|
let from_offset = if let Some(def_index) = self.module.local_global_index(index) {
|
||||||
let offset =
|
self.offsets.vmctx_vmglobal_definition(def_index)
|
||||||
i32::try_from(self.offsets.vmctx_vmglobal_definition(def_index)).unwrap();
|
|
||||||
(vmctx, offset)
|
|
||||||
} else {
|
} else {
|
||||||
let from_offset = self.offsets.vmctx_vmglobal_import_definition(index);
|
self.offsets.vmctx_vmglobal_import_definition(index)
|
||||||
let global = func.create_global_value(ir::GlobalValueData::Load {
|
};
|
||||||
base: vmctx,
|
let global = func.create_global_value(ir::GlobalValueData::Load {
|
||||||
offset: Offset32::new(i32::try_from(from_offset).unwrap()),
|
base: vmctx,
|
||||||
global_type: pointer_type,
|
offset: Offset32::new(i32::try_from(from_offset).unwrap()),
|
||||||
readonly: true,
|
global_type: pointer_type,
|
||||||
});
|
readonly: true,
|
||||||
(global, 0)
|
});
|
||||||
}
|
|
||||||
|
(global, 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(GlobalVariable::Memory {
|
Ok(GlobalVariable::Memory {
|
||||||
|
|||||||
@@ -834,34 +834,34 @@ impl<'ctx, 'a> CtxType<'ctx, 'a> {
|
|||||||
let global_value_type = global_type.ty;
|
let global_value_type = global_type.ty;
|
||||||
|
|
||||||
let global_mutability = global_type.mutability;
|
let global_mutability = global_type.mutability;
|
||||||
let global_ptr =
|
let offset = if let Some(local_global_index) = wasm_module.local_global_index(index)
|
||||||
if let Some(local_global_index) = wasm_module.local_global_index(index) {
|
{
|
||||||
let offset = offsets.vmctx_vmglobal_definition(local_global_index);
|
offsets.vmctx_vmglobal_definition(local_global_index)
|
||||||
let offset = intrinsics.i32_ty.const_int(offset.into(), false);
|
} else {
|
||||||
unsafe { cache_builder.build_gep(ctx_ptr_value, &[offset], "") }
|
offsets.vmctx_vmglobal_import(index)
|
||||||
} else {
|
};
|
||||||
let offset = offsets.vmctx_vmglobal_import(index);
|
let offset = intrinsics.i32_ty.const_int(offset.into(), false);
|
||||||
let offset = intrinsics.i32_ty.const_int(offset.into(), false);
|
let global_ptr = {
|
||||||
let global_ptr_ptr =
|
let global_ptr_ptr =
|
||||||
unsafe { cache_builder.build_gep(ctx_ptr_value, &[offset], "") };
|
unsafe { cache_builder.build_gep(ctx_ptr_value, &[offset], "") };
|
||||||
let global_ptr_ptr = cache_builder
|
let global_ptr_ptr = cache_builder
|
||||||
.build_bitcast(
|
.build_bitcast(
|
||||||
global_ptr_ptr,
|
global_ptr_ptr,
|
||||||
intrinsics.i32_ptr_ty.ptr_type(AddressSpace::Generic),
|
intrinsics.i32_ptr_ty.ptr_type(AddressSpace::Generic),
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
.into_pointer_value();
|
.into_pointer_value();
|
||||||
let global_ptr = cache_builder
|
let global_ptr = cache_builder
|
||||||
.build_load(global_ptr_ptr, "")
|
.build_load(global_ptr_ptr, "")
|
||||||
.into_pointer_value();
|
.into_pointer_value();
|
||||||
tbaa_label(
|
tbaa_label(
|
||||||
module,
|
module,
|
||||||
intrinsics,
|
intrinsics,
|
||||||
format!("global_ptr {}", index.as_u32()),
|
format!("global_ptr {}", index.as_u32()),
|
||||||
global_ptr.as_instruction_value().unwrap(),
|
global_ptr.as_instruction_value().unwrap(),
|
||||||
);
|
);
|
||||||
global_ptr
|
global_ptr
|
||||||
};
|
};
|
||||||
let global_ptr = cache_builder
|
let global_ptr = cache_builder
|
||||||
.build_bitcast(
|
.build_bitcast(
|
||||||
global_ptr,
|
global_ptr,
|
||||||
|
|||||||
@@ -1898,7 +1898,13 @@ impl<'a> FuncGen<'a> {
|
|||||||
self.module.local_global_index(global_index)
|
self.module.local_global_index(global_index)
|
||||||
{
|
{
|
||||||
let offset = self.vmoffsets.vmctx_vmglobal_definition(local_global_index);
|
let offset = self.vmoffsets.vmctx_vmglobal_definition(local_global_index);
|
||||||
Location::Memory(Machine::get_vmctx_reg(), offset as i32)
|
self.emit_relaxed_binop(
|
||||||
|
Assembler::emit_mov,
|
||||||
|
Size::S64,
|
||||||
|
Location::Memory(Machine::get_vmctx_reg(), offset as i32),
|
||||||
|
Location::GPR(tmp),
|
||||||
|
);
|
||||||
|
Location::Memory(tmp, 0)
|
||||||
} else {
|
} else {
|
||||||
// Imported globals require one level of indirection.
|
// Imported globals require one level of indirection.
|
||||||
let offset = self
|
let offset = self
|
||||||
@@ -1924,7 +1930,13 @@ impl<'a> FuncGen<'a> {
|
|||||||
self.module.local_global_index(global_index)
|
self.module.local_global_index(global_index)
|
||||||
{
|
{
|
||||||
let offset = self.vmoffsets.vmctx_vmglobal_definition(local_global_index);
|
let offset = self.vmoffsets.vmctx_vmglobal_definition(local_global_index);
|
||||||
Location::Memory(Machine::get_vmctx_reg(), offset as i32)
|
self.emit_relaxed_binop(
|
||||||
|
Assembler::emit_mov,
|
||||||
|
Size::S64,
|
||||||
|
Location::Memory(Machine::get_vmctx_reg(), offset as i32),
|
||||||
|
Location::GPR(tmp),
|
||||||
|
);
|
||||||
|
Location::Memory(tmp, 0)
|
||||||
} else {
|
} else {
|
||||||
// Imported globals require one level of indirection.
|
// Imported globals require one level of indirection.
|
||||||
let offset = self
|
let offset = self
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ pub trait Artifact {
|
|||||||
.map_err(InstantiationError::Link)?
|
.map_err(InstantiationError::Link)?
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
let finished_globals = tunables
|
let finished_globals = tunables
|
||||||
.create_globals(&module)
|
.create_globals(&module, &imports)
|
||||||
.map_err(InstantiationError::Link)?
|
.map_err(InstantiationError::Link)?
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use wasm_common::{
|
|||||||
MemoryType, Mutability, TableIndex, TableType,
|
MemoryType, Mutability, TableIndex, TableType,
|
||||||
};
|
};
|
||||||
use wasmer_runtime::MemoryError;
|
use wasmer_runtime::MemoryError;
|
||||||
use wasmer_runtime::{Global, Memory, ModuleInfo, Table};
|
use wasmer_runtime::{Global, Imports, Memory, ModuleInfo, Table};
|
||||||
use wasmer_runtime::{MemoryPlan, TablePlan};
|
use wasmer_runtime::{MemoryPlan, TablePlan};
|
||||||
|
|
||||||
/// Tunables for an engine
|
/// Tunables for an engine
|
||||||
@@ -26,10 +26,12 @@ pub trait Tunables {
|
|||||||
/// Create a global with the given value.
|
/// Create a global with the given value.
|
||||||
fn create_initialized_global(
|
fn create_initialized_global(
|
||||||
&self,
|
&self,
|
||||||
|
module: &ModuleInfo,
|
||||||
|
imports: &Imports,
|
||||||
mutability: Mutability,
|
mutability: Mutability,
|
||||||
init: GlobalInit,
|
init: GlobalInit,
|
||||||
) -> Result<Arc<Global>, String> {
|
) -> Result<Arc<Global>, String> {
|
||||||
Ok(Arc::new(Global::new_with_init(mutability, init)))
|
Ok(Global::new_with_init(module, imports, mutability, init).map_err(|e| e.to_string())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a global with a default value.
|
/// Create a global with a default value.
|
||||||
@@ -77,17 +79,23 @@ pub trait Tunables {
|
|||||||
fn create_globals(
|
fn create_globals(
|
||||||
&self,
|
&self,
|
||||||
module: &ModuleInfo,
|
module: &ModuleInfo,
|
||||||
|
imports: &Imports,
|
||||||
) -> Result<PrimaryMap<LocalGlobalIndex, Arc<Global>>, LinkError> {
|
) -> Result<PrimaryMap<LocalGlobalIndex, Arc<Global>>, LinkError> {
|
||||||
let num_imports = module.num_imported_globals;
|
let num_imports = module.num_imported_globals;
|
||||||
let mut vmctx_globals = PrimaryMap::with_capacity(module.globals.len() - num_imports);
|
let mut vmctx_globals = PrimaryMap::with_capacity(module.globals.len() - num_imports);
|
||||||
|
|
||||||
for (idx, &global_type) in module.globals.iter().skip(num_imports) {
|
for (idx, &global_type) in module.globals.iter().skip(num_imports) {
|
||||||
let idx = LocalGlobalIndex::new(idx.index());
|
let idx = module.local_global_index(idx).unwrap();
|
||||||
|
|
||||||
vmctx_globals.push(
|
vmctx_globals.push(
|
||||||
if let Some(&initializer) = module.global_initializers.get(idx) {
|
if let Some(&initializer) = module.global_initializers.get(idx) {
|
||||||
self.create_initialized_global(global_type.mutability, initializer)
|
self.create_initialized_global(
|
||||||
.map_err(LinkError::Resource)?
|
module,
|
||||||
|
imports,
|
||||||
|
global_type.mutability,
|
||||||
|
initializer,
|
||||||
|
)
|
||||||
|
.map_err(LinkError::Resource)?
|
||||||
} else {
|
} else {
|
||||||
self.create_global(global_type)
|
self.create_global(global_type)
|
||||||
.map_err(LinkError::Resource)?
|
.map_err(LinkError::Resource)?
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
use crate::imports::Imports;
|
||||||
|
use crate::module::ModuleInfo;
|
||||||
use crate::vmcontext::VMGlobalDefinition;
|
use crate::vmcontext::VMGlobalDefinition;
|
||||||
use std::cell::UnsafeCell;
|
use std::cell::UnsafeCell;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use wasm_common::{GlobalInit, GlobalType, Mutability, Type, Value};
|
use wasm_common::{GlobalInit, GlobalType, Mutability, Type, Value};
|
||||||
@@ -52,53 +55,92 @@ impl Global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new, zero bit-pattern initialized global from a [`GlobalType`].
|
/// Create a new, zero bit-pattern initialized global from a [`GlobalType`].
|
||||||
pub fn new_with_init(mutability: Mutability, value: GlobalInit) -> Self {
|
pub fn new_with_init(
|
||||||
|
module: &ModuleInfo,
|
||||||
|
imports: &Imports,
|
||||||
|
mutability: Mutability,
|
||||||
|
value: GlobalInit,
|
||||||
|
) -> Result<Arc<Self>, GlobalError> {
|
||||||
let mut vm_global_definition = VMGlobalDefinition::new();
|
let mut vm_global_definition = VMGlobalDefinition::new();
|
||||||
let global_type = unsafe {
|
let ty = unsafe {
|
||||||
match value {
|
match value {
|
||||||
GlobalInit::I32Const(v) => {
|
GlobalInit::I32Const(v) => {
|
||||||
*vm_global_definition.as_i32_mut() = v;
|
*vm_global_definition.as_i32_mut() = v;
|
||||||
GlobalType {
|
Type::I32
|
||||||
ty: Type::I32,
|
|
||||||
mutability,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
GlobalInit::I64Const(v) => {
|
GlobalInit::I64Const(v) => {
|
||||||
*vm_global_definition.as_i64_mut() = v;
|
*vm_global_definition.as_i64_mut() = v;
|
||||||
GlobalType {
|
Type::I64
|
||||||
ty: Type::I64,
|
|
||||||
mutability,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
GlobalInit::F32Const(v) => {
|
GlobalInit::F32Const(v) => {
|
||||||
*vm_global_definition.as_f32_mut() = v;
|
*vm_global_definition.as_f32_mut() = v;
|
||||||
GlobalType {
|
Type::F32
|
||||||
ty: Type::F32,
|
|
||||||
mutability,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
GlobalInit::F64Const(v) => {
|
GlobalInit::F64Const(v) => {
|
||||||
*vm_global_definition.as_f64_mut() = v;
|
*vm_global_definition.as_f64_mut() = v;
|
||||||
GlobalType {
|
Type::F64
|
||||||
ty: Type::F64,
|
}
|
||||||
mutability,
|
GlobalInit::GetGlobal(global_index) => {
|
||||||
|
/*let other_type = module.globals.get(global_index).map_err(|e| "global does not exist")?;
|
||||||
|
*/
|
||||||
|
if let Some(global_value) = imports.globals.get(global_index) {
|
||||||
|
return Ok(global_value.from.clone());
|
||||||
|
} else {
|
||||||
|
let idx = module.local_global_index(global_index).unwrap();
|
||||||
|
if let Some(&init) = module.global_initializers.get(idx) {
|
||||||
|
// TODO: test for cycles/infinite loops here
|
||||||
|
return Self::new_with_init(module, imports, mutability, init);
|
||||||
|
}
|
||||||
|
// global may not exist here.
|
||||||
|
// TODO: tests should test if initializing with local global with no initializer and
|
||||||
|
// initializing with an invalid global index
|
||||||
|
todo!("check for initializer?, otherwise 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unimplemented!("Global is not implemented for initializer {:?}", value),
|
_ => unimplemented!(
|
||||||
|
"Global `new_with_init` is not implemented for initializer {:?}",
|
||||||
|
value
|
||||||
|
),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Arc::new(Self {
|
||||||
|
ty: GlobalType { ty, mutability },
|
||||||
|
vm_global_definition: Box::new(UnsafeCell::new(vm_global_definition)),
|
||||||
|
lock: Mutex::new(()),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new global initialized with the given value.
|
||||||
|
pub fn new_with_value<T>(mutability: Mutability, value: Value<T>) -> Self {
|
||||||
|
let mut vm_global_definition = VMGlobalDefinition::new();
|
||||||
|
let ty = unsafe {
|
||||||
|
match value {
|
||||||
|
Value::I32(v) => {
|
||||||
|
*vm_global_definition.as_i32_mut() = v;
|
||||||
|
Type::I32
|
||||||
|
}
|
||||||
|
Value::I64(v) => {
|
||||||
|
*vm_global_definition.as_i64_mut() = v;
|
||||||
|
Type::I64
|
||||||
|
}
|
||||||
|
Value::F32(v) => {
|
||||||
|
*vm_global_definition.as_f32_mut() = v;
|
||||||
|
Type::F32
|
||||||
|
}
|
||||||
|
Value::F64(v) => {
|
||||||
|
*vm_global_definition.as_f64_mut() = v;
|
||||||
|
Type::F64
|
||||||
|
}
|
||||||
|
_ => unimplemented!("Global `new_with_value` is not implemented for {:?}", value),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
ty: global_type,
|
ty: GlobalType { ty, mutability },
|
||||||
vm_global_definition: Box::new(UnsafeCell::new(vm_global_definition)),
|
vm_global_definition: Box::new(UnsafeCell::new(vm_global_definition)),
|
||||||
lock: Mutex::new(()),
|
lock: Mutex::new(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new, zero bit-pattern initialized global from a [`GlobalType`].
|
|
||||||
pub fn new_with_value<T>(mutability: Mutability, value: Value<T>) -> Self {
|
|
||||||
Self::new_with_init(mutability, GlobalInit::from_value(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the type of the global.
|
/// Get the type of the global.
|
||||||
pub fn ty(&self) -> &GlobalType {
|
pub fn ty(&self) -> &GlobalType {
|
||||||
&self.ty
|
&self.ty
|
||||||
|
|||||||
@@ -252,11 +252,12 @@ impl Instance {
|
|||||||
/// Return the indexed `VMGlobalDefinition`.
|
/// Return the indexed `VMGlobalDefinition`.
|
||||||
fn global_ptr(&self, index: LocalGlobalIndex) -> NonNull<VMGlobalDefinition> {
|
fn global_ptr(&self, index: LocalGlobalIndex) -> NonNull<VMGlobalDefinition> {
|
||||||
let index = usize::try_from(index.as_u32()).unwrap();
|
let index = usize::try_from(index.as_u32()).unwrap();
|
||||||
NonNull::new(unsafe { self.globals_ptr().add(index) }).unwrap()
|
// TODO:
|
||||||
|
NonNull::new(unsafe { *self.globals_ptr().add(index) }).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a pointer to the `VMGlobalDefinition`s.
|
/// Return a pointer to the `VMGlobalDefinition`s.
|
||||||
fn globals_ptr(&self) -> *mut VMGlobalDefinition {
|
fn globals_ptr(&self) -> *mut *mut VMGlobalDefinition {
|
||||||
unsafe { self.vmctx_plus_offset(self.offsets.vmctx_globals_begin()) }
|
unsafe { self.vmctx_plus_offset(self.offsets.vmctx_globals_begin()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -822,10 +823,7 @@ impl InstanceHandle {
|
|||||||
|
|
||||||
let vmctx_globals = finished_globals
|
let vmctx_globals = finished_globals
|
||||||
.values()
|
.values()
|
||||||
.map(|m| {
|
.map(|m| m.vmglobal())
|
||||||
let vmglobal_ptr = m.as_ref().vmglobal();
|
|
||||||
vmglobal_ptr.as_ref().clone()
|
|
||||||
})
|
|
||||||
.collect::<PrimaryMap<LocalGlobalIndex, _>>()
|
.collect::<PrimaryMap<LocalGlobalIndex, _>>()
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
|
|
||||||
@@ -897,7 +895,7 @@ impl InstanceHandle {
|
|||||||
);
|
);
|
||||||
ptr::copy(
|
ptr::copy(
|
||||||
vmctx_globals.values().as_slice().as_ptr(),
|
vmctx_globals.values().as_slice().as_ptr(),
|
||||||
instance.globals_ptr() as *mut VMGlobalDefinition,
|
instance.globals_ptr() as *mut NonNull<VMGlobalDefinition>,
|
||||||
vmctx_globals.len(),
|
vmctx_globals.len(),
|
||||||
);
|
);
|
||||||
ptr::write(
|
ptr::write(
|
||||||
@@ -1279,11 +1277,12 @@ fn initialize_globals(instance: &Instance) {
|
|||||||
GlobalInit::F64Const(x) => *(*to).as_f64_mut() = *x,
|
GlobalInit::F64Const(x) => *(*to).as_f64_mut() = *x,
|
||||||
GlobalInit::V128Const(x) => *(*to).as_u128_bits_mut() = *x.bytes(),
|
GlobalInit::V128Const(x) => *(*to).as_u128_bits_mut() = *x.bytes(),
|
||||||
GlobalInit::GetGlobal(x) => {
|
GlobalInit::GetGlobal(x) => {
|
||||||
let from = if let Some(def_x) = module.local_global_index(*x) {
|
let from: VMGlobalDefinition =
|
||||||
instance.global(def_x)
|
if let Some(def_x) = module.local_global_index(*x) {
|
||||||
} else {
|
instance.global(def_x)
|
||||||
instance.imported_global(*x).definition.as_ref().clone()
|
} else {
|
||||||
};
|
instance.imported_global(*x).definition.as_ref().clone()
|
||||||
|
};
|
||||||
*to = from;
|
*to = from;
|
||||||
}
|
}
|
||||||
GlobalInit::RefNullConst | GlobalInit::RefFunc(_) => unimplemented!(),
|
GlobalInit::RefNullConst | GlobalInit::RefFunc(_) => unimplemented!(),
|
||||||
|
|||||||
@@ -277,16 +277,17 @@ impl VMOffsets {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Offsets for [`VMGlobalDefinition`].
|
/// Offsets for a non-null pointer to a [`VMGlobalDefinition`] used as a local global.
|
||||||
///
|
///
|
||||||
/// [`VMGlobalDefinition`]: crate::vmcontext::VMGlobalDefinition
|
/// [`VMGlobalDefinition`]: crate::vmcontext::VMGlobalDefinition
|
||||||
impl VMOffsets {
|
impl VMOffsets {
|
||||||
/// Return the size of [`VMGlobalDefinition`]; this is the size of the largest value type (i.e. a
|
/// Return the size of a pointer to a [`VMGlobalDefinition`];
|
||||||
/// V128).
|
|
||||||
///
|
///
|
||||||
|
/// The underlying global itself is the size of the largest value type (i.e. a V128),
|
||||||
|
/// however the size of this type is just the size of a pointer.
|
||||||
/// [`VMGlobalDefinition`]: crate::vmcontext::VMGlobalDefinition
|
/// [`VMGlobalDefinition`]: crate::vmcontext::VMGlobalDefinition
|
||||||
pub const fn size_of_vmglobal_definition(&self) -> u8 {
|
pub const fn size_of_vmglobal_local(&self) -> u8 {
|
||||||
16
|
self.pointer_size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,7 +427,7 @@ impl VMOffsets {
|
|||||||
self.vmctx_globals_begin()
|
self.vmctx_globals_begin()
|
||||||
.checked_add(
|
.checked_add(
|
||||||
self.num_local_globals
|
self.num_local_globals
|
||||||
.checked_mul(u32::from(self.size_of_vmglobal_definition()))
|
.checked_mul(u32::from(self.size_of_vmglobal_local()))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -559,7 +560,7 @@ impl VMOffsets {
|
|||||||
.checked_add(
|
.checked_add(
|
||||||
index
|
index
|
||||||
.as_u32()
|
.as_u32()
|
||||||
.checked_mul(u32::from(self.size_of_vmglobal_definition()))
|
.checked_mul(u32::from(self.size_of_vmglobal_local()))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|||||||
Reference in New Issue
Block a user