Update to cretonne 0.9.0 and faerie 0.4.1.
This commit is contained in:
12
Cargo.toml
12
Cargo.toml
@@ -17,11 +17,11 @@ name = "wasm2obj"
|
|||||||
path = "src/wasm2obj.rs"
|
path = "src/wasm2obj.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cretonne-codegen = "0.8.0"
|
cretonne-codegen = "0.9.0"
|
||||||
cretonne-frontend = "0.8.0"
|
cretonne-frontend = "0.9.0"
|
||||||
cretonne-reader = "0.8.0"
|
cretonne-reader = "0.9.0"
|
||||||
cretonne-wasm = "0.8.0"
|
cretonne-wasm = "0.9.0"
|
||||||
cretonne-native = "0.8.0"
|
cretonne-native = "0.9.0"
|
||||||
wasmstandalone_runtime = { path = "lib/runtime" }
|
wasmstandalone_runtime = { path = "lib/runtime" }
|
||||||
wasmstandalone_execute = { path = "lib/execute" }
|
wasmstandalone_execute = { path = "lib/execute" }
|
||||||
wasmstandalone_obj = { path = "lib/obj" }
|
wasmstandalone_obj = { path = "lib/obj" }
|
||||||
@@ -29,6 +29,6 @@ docopt = "1.0.0"
|
|||||||
serde = "1.0.55"
|
serde = "1.0.55"
|
||||||
serde_derive = "1.0.55"
|
serde_derive = "1.0.55"
|
||||||
tempdir = "*"
|
tempdir = "*"
|
||||||
faerie = "0.3.0"
|
faerie = "0.4.1"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ repository = "https://github.com/sunfishcode/wasmstandalone"
|
|||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cretonne-codegen = "0.8.0"
|
cretonne-codegen = "0.9.0"
|
||||||
cretonne-wasm = "0.8.0"
|
cretonne-wasm = "0.9.0"
|
||||||
region = "0.3.0"
|
region = "0.3.0"
|
||||||
wasmstandalone_runtime = { path = "../runtime" }
|
wasmstandalone_runtime = { path = "../runtime" }
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ authors = ["The Cretonne Project Developers"]
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cretonne-codegen = "0.8.0"
|
cretonne-codegen = "0.9.0"
|
||||||
cretonne-wasm = "0.8.0"
|
cretonne-wasm = "0.9.0"
|
||||||
wasmstandalone_runtime = { path = "../runtime" }
|
wasmstandalone_runtime = { path = "../runtime" }
|
||||||
faerie = "0.3.0"
|
faerie = "0.4.1"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ repository = "https://github.com/sunfishcode/wasmstandalone"
|
|||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cretonne-codegen = "0.8.0"
|
cretonne-codegen = "0.9.0"
|
||||||
cretonne-wasm = "0.8.0"
|
cretonne-wasm = "0.9.0"
|
||||||
wasmparser = "0.16.1"
|
wasmparser = "0.16.1"
|
||||||
|
target-lexicon = "0.0.1"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
extern crate cretonne_codegen;
|
extern crate cretonne_codegen;
|
||||||
extern crate cretonne_wasm;
|
extern crate cretonne_wasm;
|
||||||
|
extern crate target_lexicon;
|
||||||
extern crate wasmparser;
|
extern crate wasmparser;
|
||||||
|
|
||||||
pub mod compilation;
|
pub mod compilation;
|
||||||
@@ -27,7 +28,8 @@ use cretonne_codegen::ir::{AbiParam, ArgumentExtension, ArgumentLoc, ArgumentPur
|
|||||||
use cretonne_codegen::isa;
|
use cretonne_codegen::isa;
|
||||||
use cretonne_codegen::settings;
|
use cretonne_codegen::settings;
|
||||||
use cretonne_wasm::{FuncTranslator, FunctionIndex, Global, GlobalIndex, GlobalValue, Memory,
|
use cretonne_wasm::{FuncTranslator, FunctionIndex, Global, GlobalIndex, GlobalValue, Memory,
|
||||||
MemoryIndex, SignatureIndex, Table, TableIndex};
|
MemoryIndex, SignatureIndex, Table, TableIndex, WasmResult};
|
||||||
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
/// Compute a `ir::ExternalName` for a given wasm function index.
|
/// Compute a `ir::ExternalName` for a given wasm function index.
|
||||||
pub fn get_func_name(func_index: FunctionIndex) -> cretonne_codegen::ir::ExternalName {
|
pub fn get_func_name(func_index: FunctionIndex) -> cretonne_codegen::ir::ExternalName {
|
||||||
@@ -137,7 +139,7 @@ impl<'data> LazyContents<'data> {
|
|||||||
/// to `cretonne_wasm::translatemodule`.
|
/// to `cretonne_wasm::translatemodule`.
|
||||||
pub struct ModuleEnvironment<'data, 'module> {
|
pub struct ModuleEnvironment<'data, 'module> {
|
||||||
/// Compilation setting flags.
|
/// Compilation setting flags.
|
||||||
pub settings_flags: &'module settings::Flags,
|
pub isa: &'module isa::TargetIsa,
|
||||||
|
|
||||||
/// Module information.
|
/// Module information.
|
||||||
pub module: &'module mut Module,
|
pub module: &'module mut Module,
|
||||||
@@ -148,16 +150,16 @@ pub struct ModuleEnvironment<'data, 'module> {
|
|||||||
|
|
||||||
impl<'data, 'module> ModuleEnvironment<'data, 'module> {
|
impl<'data, 'module> ModuleEnvironment<'data, 'module> {
|
||||||
/// Allocates the runtime data structures with the given isa.
|
/// Allocates the runtime data structures with the given isa.
|
||||||
pub fn new(flags: &'module settings::Flags, module: &'module mut Module) -> Self {
|
pub fn new(isa: &'module isa::TargetIsa, module: &'module mut Module) -> Self {
|
||||||
Self {
|
Self {
|
||||||
settings_flags: flags,
|
isa: isa,
|
||||||
module,
|
module,
|
||||||
lazy: LazyContents::new(),
|
lazy: LazyContents::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn func_env(&self) -> FuncEnvironment {
|
fn func_env(&self) -> FuncEnvironment {
|
||||||
FuncEnvironment::new(&self.settings_flags, &self.module)
|
FuncEnvironment::new(self.isa, &self.module)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn native_pointer(&self) -> ir::Type {
|
fn native_pointer(&self) -> ir::Type {
|
||||||
@@ -171,7 +173,7 @@ impl<'data, 'module> ModuleEnvironment<'data, 'module> {
|
|||||||
/// `Module`.
|
/// `Module`.
|
||||||
pub fn finish_translation(self) -> ModuleTranslation<'data, 'module> {
|
pub fn finish_translation(self) -> ModuleTranslation<'data, 'module> {
|
||||||
ModuleTranslation {
|
ModuleTranslation {
|
||||||
flags: self.settings_flags,
|
isa: self.isa,
|
||||||
module: self.module,
|
module: self.module,
|
||||||
lazy: self.lazy,
|
lazy: self.lazy,
|
||||||
}
|
}
|
||||||
@@ -181,7 +183,7 @@ impl<'data, 'module> ModuleEnvironment<'data, 'module> {
|
|||||||
/// The FuncEnvironment implementation for use by the `ModuleEnvironment`.
|
/// The FuncEnvironment implementation for use by the `ModuleEnvironment`.
|
||||||
pub struct FuncEnvironment<'module_environment> {
|
pub struct FuncEnvironment<'module_environment> {
|
||||||
/// Compilation setting flags.
|
/// Compilation setting flags.
|
||||||
settings_flags: &'module_environment settings::Flags,
|
isa: &'module_environment isa::TargetIsa,
|
||||||
|
|
||||||
/// The module-level environment which this function-level environment belongs to.
|
/// The module-level environment which this function-level environment belongs to.
|
||||||
pub module: &'module_environment Module,
|
pub module: &'module_environment Module,
|
||||||
@@ -200,12 +202,9 @@ pub struct FuncEnvironment<'module_environment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'module_environment> FuncEnvironment<'module_environment> {
|
impl<'module_environment> FuncEnvironment<'module_environment> {
|
||||||
fn new(
|
fn new(isa: &'module_environment isa::TargetIsa, module: &'module_environment Module) -> Self {
|
||||||
flags: &'module_environment settings::Flags,
|
|
||||||
module: &'module_environment Module,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
settings_flags: flags,
|
isa,
|
||||||
module,
|
module,
|
||||||
memories_base: None,
|
memories_base: None,
|
||||||
globals_base: None,
|
globals_base: None,
|
||||||
@@ -223,17 +222,17 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ptr_size(&self) -> usize {
|
fn ptr_size(&self) -> usize {
|
||||||
if self.settings_flags.is_64bit() {
|
usize::from(self.isa.pointer_bytes())
|
||||||
8
|
|
||||||
} else {
|
|
||||||
4
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'module_environment> cretonne_wasm::FuncEnvironment for FuncEnvironment<'module_environment> {
|
impl<'module_environment> cretonne_wasm::FuncEnvironment for FuncEnvironment<'module_environment> {
|
||||||
fn flags(&self) -> &settings::Flags {
|
fn flags(&self) -> &settings::Flags {
|
||||||
&self.settings_flags
|
&self.isa.flags()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn triple(&self) -> &Triple {
|
||||||
|
self.isa.triple()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_global(&mut self, func: &mut ir::Function, index: GlobalIndex) -> GlobalValue {
|
fn make_global(&mut self, func: &mut ir::Function, index: GlobalIndex) -> GlobalValue {
|
||||||
@@ -314,12 +313,12 @@ impl<'module_environment> cretonne_wasm::FuncEnvironment for FuncEnvironment<'mo
|
|||||||
sig_ref: ir::SigRef,
|
sig_ref: ir::SigRef,
|
||||||
callee: ir::Value,
|
callee: ir::Value,
|
||||||
call_args: &[ir::Value],
|
call_args: &[ir::Value],
|
||||||
) -> ir::Inst {
|
) -> WasmResult<ir::Inst> {
|
||||||
// TODO: Cretonne's call_indirect doesn't implement bounds checking
|
// TODO: Cretonne's call_indirect doesn't implement bounds checking
|
||||||
// or signature checking, so we need to implement it ourselves.
|
// or signature checking, so we need to implement it ourselves.
|
||||||
debug_assert_eq!(table_index, 0, "non-default tables not supported yet");
|
debug_assert_eq!(table_index, 0, "non-default tables not supported yet");
|
||||||
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args);
|
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args);
|
||||||
pos.ins().call_indirect(sig_ref, callee, &real_call_args)
|
Ok(pos.ins().call_indirect(sig_ref, callee, &real_call_args))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate_call(
|
fn translate_call(
|
||||||
@@ -328,9 +327,9 @@ impl<'module_environment> cretonne_wasm::FuncEnvironment for FuncEnvironment<'mo
|
|||||||
_callee_index: FunctionIndex,
|
_callee_index: FunctionIndex,
|
||||||
callee: ir::FuncRef,
|
callee: ir::FuncRef,
|
||||||
call_args: &[ir::Value],
|
call_args: &[ir::Value],
|
||||||
) -> ir::Inst {
|
) -> WasmResult<ir::Inst> {
|
||||||
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args);
|
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args);
|
||||||
pos.ins().call(callee, &real_call_args)
|
Ok(pos.ins().call(callee, &real_call_args))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate_grow_memory(
|
fn translate_grow_memory(
|
||||||
@@ -339,11 +338,11 @@ impl<'module_environment> cretonne_wasm::FuncEnvironment for FuncEnvironment<'mo
|
|||||||
index: MemoryIndex,
|
index: MemoryIndex,
|
||||||
_heap: ir::Heap,
|
_heap: ir::Heap,
|
||||||
val: ir::Value,
|
val: ir::Value,
|
||||||
) -> ir::Value {
|
) -> WasmResult<ir::Value> {
|
||||||
debug_assert_eq!(index, 0, "non-default memories not supported yet");
|
debug_assert_eq!(index, 0, "non-default memories not supported yet");
|
||||||
let grow_mem_func = self.grow_memory_extfunc.unwrap_or_else(|| {
|
let grow_mem_func = self.grow_memory_extfunc.unwrap_or_else(|| {
|
||||||
let sig_ref = pos.func.import_signature(Signature {
|
let sig_ref = pos.func.import_signature(Signature {
|
||||||
call_conv: self.settings_flags.call_conv(),
|
call_conv: self.isa.flags().call_conv(),
|
||||||
argument_bytes: None,
|
argument_bytes: None,
|
||||||
params: vec![AbiParam::new(I32)],
|
params: vec![AbiParam::new(I32)],
|
||||||
returns: vec![AbiParam::new(I32)],
|
returns: vec![AbiParam::new(I32)],
|
||||||
@@ -360,7 +359,7 @@ impl<'module_environment> cretonne_wasm::FuncEnvironment for FuncEnvironment<'mo
|
|||||||
});
|
});
|
||||||
self.grow_memory_extfunc = Some(grow_mem_func);
|
self.grow_memory_extfunc = Some(grow_mem_func);
|
||||||
let call_inst = pos.ins().call(grow_mem_func, &[val]);
|
let call_inst = pos.ins().call(grow_mem_func, &[val]);
|
||||||
*pos.func.dfg.inst_results(call_inst).first().unwrap()
|
Ok(*pos.func.dfg.inst_results(call_inst).first().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate_current_memory(
|
fn translate_current_memory(
|
||||||
@@ -368,11 +367,11 @@ impl<'module_environment> cretonne_wasm::FuncEnvironment for FuncEnvironment<'mo
|
|||||||
mut pos: FuncCursor,
|
mut pos: FuncCursor,
|
||||||
index: MemoryIndex,
|
index: MemoryIndex,
|
||||||
_heap: ir::Heap,
|
_heap: ir::Heap,
|
||||||
) -> ir::Value {
|
) -> WasmResult<ir::Value> {
|
||||||
debug_assert_eq!(index, 0, "non-default memories not supported yet");
|
debug_assert_eq!(index, 0, "non-default memories not supported yet");
|
||||||
let cur_mem_func = self.current_memory_extfunc.unwrap_or_else(|| {
|
let cur_mem_func = self.current_memory_extfunc.unwrap_or_else(|| {
|
||||||
let sig_ref = pos.func.import_signature(Signature {
|
let sig_ref = pos.func.import_signature(Signature {
|
||||||
call_conv: self.settings_flags.call_conv(),
|
call_conv: self.isa.flags().call_conv(),
|
||||||
argument_bytes: None,
|
argument_bytes: None,
|
||||||
params: Vec::new(),
|
params: Vec::new(),
|
||||||
returns: vec![AbiParam::new(I32)],
|
returns: vec![AbiParam::new(I32)],
|
||||||
@@ -389,7 +388,7 @@ impl<'module_environment> cretonne_wasm::FuncEnvironment for FuncEnvironment<'mo
|
|||||||
});
|
});
|
||||||
self.current_memory_extfunc = Some(cur_mem_func);
|
self.current_memory_extfunc = Some(cur_mem_func);
|
||||||
let call_inst = pos.ins().call(cur_mem_func, &[]);
|
let call_inst = pos.ins().call(cur_mem_func, &[]);
|
||||||
*pos.func.dfg.inst_results(call_inst).first().unwrap()
|
Ok(*pos.func.dfg.inst_results(call_inst).first().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +402,7 @@ impl<'data, 'module> cretonne_wasm::ModuleEnvironment<'data> for ModuleEnvironme
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn flags(&self) -> &settings::Flags {
|
fn flags(&self) -> &settings::Flags {
|
||||||
self.settings_flags
|
self.isa.flags()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_signature(&mut self, sig: &ir::Signature) {
|
fn declare_signature(&mut self, sig: &ir::Signature) {
|
||||||
@@ -524,7 +523,7 @@ impl<'data, 'module> cretonne_wasm::ModuleEnvironment<'data> for ModuleEnvironme
|
|||||||
self.module.start_func = Some(func_index);
|
self.module.start_func = Some(func_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn define_function_body(&mut self, body_bytes: &'data [u8]) -> Result<(), String> {
|
fn define_function_body(&mut self, body_bytes: &'data [u8]) -> WasmResult<()> {
|
||||||
self.lazy.function_body_inputs.push(body_bytes);
|
self.lazy.function_body_inputs.push(body_bytes);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -549,7 +548,7 @@ pub type Relocations = Vec<Vec<Relocation>>;
|
|||||||
/// The result of translating via `ModuleEnvironment`.
|
/// The result of translating via `ModuleEnvironment`.
|
||||||
pub struct ModuleTranslation<'data, 'module> {
|
pub struct ModuleTranslation<'data, 'module> {
|
||||||
/// Compilation setting flags.
|
/// Compilation setting flags.
|
||||||
pub flags: &'module settings::Flags,
|
pub isa: &'module isa::TargetIsa,
|
||||||
|
|
||||||
/// Module information.
|
/// Module information.
|
||||||
pub module: &'module Module,
|
pub module: &'module Module,
|
||||||
@@ -561,7 +560,7 @@ pub struct ModuleTranslation<'data, 'module> {
|
|||||||
/// Convenience functions for the user to be called after execution for debug purposes.
|
/// Convenience functions for the user to be called after execution for debug purposes.
|
||||||
impl<'data, 'module> ModuleTranslation<'data, 'module> {
|
impl<'data, 'module> ModuleTranslation<'data, 'module> {
|
||||||
fn func_env(&self) -> FuncEnvironment {
|
fn func_env(&self) -> FuncEnvironment {
|
||||||
FuncEnvironment::new(&self.flags, &self.module)
|
FuncEnvironment::new(self.isa, &self.module)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile the module, producing a compilation result with associated
|
/// Compile the module, producing a compilation result with associated
|
||||||
|
|||||||
@@ -119,8 +119,8 @@ fn handle_module(args: &Args, path: PathBuf, isa: &TargetIsa) -> Result<(), Stri
|
|||||||
data = read_to_end(file_path).map_err(|err| String::from(err.description()))?;
|
data = read_to_end(file_path).map_err(|err| String::from(err.description()))?;
|
||||||
}
|
}
|
||||||
let mut module = Module::new();
|
let mut module = Module::new();
|
||||||
let mut environ = ModuleEnvironment::new(isa.flags(), &mut module);
|
let mut environ = ModuleEnvironment::new(isa, &mut module);
|
||||||
translate_module(&data, &mut environ)?;
|
translate_module(&data, &mut environ).map_err(|e| e.to_string())?;
|
||||||
let translation = environ.finish_translation();
|
let translation = environ.finish_translation();
|
||||||
let instance = match compile_module(isa, &translation) {
|
let instance = match compile_module(isa, &translation) {
|
||||||
Ok(compilation) => {
|
Ok(compilation) => {
|
||||||
|
|||||||
@@ -14,11 +14,10 @@ extern crate wasmstandalone_runtime;
|
|||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
extern crate faerie;
|
extern crate faerie;
|
||||||
|
|
||||||
use cretonne_codegen::isa;
|
|
||||||
use cretonne_codegen::settings;
|
use cretonne_codegen::settings;
|
||||||
use cretonne_wasm::translate_module;
|
use cretonne_wasm::translate_module;
|
||||||
use docopt::Docopt;
|
use docopt::Docopt;
|
||||||
use faerie::{Artifact, Elf, Target};
|
use faerie::Artifact;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::format;
|
use std::fmt::format;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@@ -92,10 +91,10 @@ fn handle_module(path: PathBuf, output: &str) -> Result<(), String> {
|
|||||||
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
||||||
|
|
||||||
let mut module = wasmstandalone_runtime::Module::new();
|
let mut module = wasmstandalone_runtime::Module::new();
|
||||||
let mut environ = wasmstandalone_runtime::ModuleEnvironment::new(isa.flags(), &mut module);
|
let mut environ = wasmstandalone_runtime::ModuleEnvironment::new(&*isa, &mut module);
|
||||||
translate_module(&data, &mut environ)?;
|
translate_module(&data, &mut environ).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let mut obj = Artifact::new(faerie_target(&*isa)?, String::from(output));
|
let mut obj = Artifact::new(isa.triple().clone(), String::from(output));
|
||||||
|
|
||||||
// FIXME: We need to initialize memory in a way that supports alternate
|
// FIXME: We need to initialize memory in a way that supports alternate
|
||||||
// memory spaces, imported base addresses, and offsets.
|
// memory spaces, imported base addresses, and offsets.
|
||||||
@@ -120,22 +119,7 @@ fn handle_module(path: PathBuf, output: &str) -> Result<(), String> {
|
|||||||
// FIXME: Make the format a parameter.
|
// FIXME: Make the format a parameter.
|
||||||
let file =
|
let file =
|
||||||
::std::fs::File::create(Path::new(output)).map_err(|x| format(format_args!("{}", x)))?;
|
::std::fs::File::create(Path::new(output)).map_err(|x| format(format_args!("{}", x)))?;
|
||||||
obj.write::<Elf>(file)
|
obj.write(file).map_err(|e| e.to_string())?;
|
||||||
.map_err(|x| format(format_args!("{}", x)))?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn faerie_target(isa: &isa::TargetIsa) -> Result<Target, String> {
|
|
||||||
let name = isa.name();
|
|
||||||
match name {
|
|
||||||
"intel" => Ok(if isa.flags().is_64bit() {
|
|
||||||
Target::X86_64
|
|
||||||
} else {
|
|
||||||
Target::X86
|
|
||||||
}),
|
|
||||||
"arm32" => Ok(Target::ARMv7),
|
|
||||||
"arm64" => Ok(Target::ARM64),
|
|
||||||
_ => Err(format!("unsupported isa: {}", name)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user