Commit 45bdd8fd authored by LiuYu's avatar LiuYu Committed by Commit Bot

[mips] Reduce the use of ctc1 and cfc1

Change-Id: Id20ea44e9cf804d305731799045c45a577df47ec
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2607266Reviewed-by: 's avatarZhao Jiazhong <zhaojiazhong-hf@loongson.cn>
Commit-Queue: Zhao Jiazhong <zhaojiazhong-hf@loongson.cn>
Auto-Submit: Liu yu <liuyu@loongson.cn>
Cr-Commit-Position: refs/heads/master@{#71902}
parent 2ab1e53c
......@@ -2664,23 +2664,18 @@ void Builtins::Generate_DoubleToI(MacroAssembler* masm) {
// Load double input.
__ Ldc1(double_scratch, MemOperand(sp, kArgumentOffset));
// Clear cumulative exception flags and save the FCSR.
__ cfc1(scratch2, FCSR);
__ ctc1(zero_reg, FCSR);
// Try a conversion to a signed integer.
__ Trunc_w_d(double_scratch, double_scratch);
// Move the converted value into the result register.
__ mfc1(scratch3, double_scratch);
// Retrieve and restore the FCSR.
// Retrieve the FCSR.
__ cfc1(scratch, FCSR);
__ ctc1(scratch2, FCSR);
// Check for overflow and NaNs.
__ And(
scratch, scratch,
kFCSROverflowFlagMask | kFCSRUnderflowFlagMask | kFCSRInvalidOpFlagMask);
__ And(scratch, scratch,
kFCSROverflowCauseMask | kFCSRUnderflowCauseMask |
kFCSRInvalidOpCauseMask);
// If we had no exceptions then set result_reg and we are done.
Label error;
__ Branch(&error, ne, scratch, Operand(zero_reg));
......
......@@ -2731,23 +2731,18 @@ void Builtins::Generate_DoubleToI(MacroAssembler* masm) {
// Load double input.
__ Ldc1(double_scratch, MemOperand(sp, kArgumentOffset));
// Clear cumulative exception flags and save the FCSR.
__ cfc1(scratch2, FCSR);
__ ctc1(zero_reg, FCSR);
// Try a conversion to a signed integer.
__ Trunc_w_d(double_scratch, double_scratch);
// Move the converted value into the result register.
__ mfc1(scratch3, double_scratch);
// Retrieve and restore the FCSR.
// Retrieve the FCSR.
__ cfc1(scratch, FCSR);
__ ctc1(scratch2, FCSR);
// Check for overflow and NaNs.
__ And(
scratch, scratch,
kFCSROverflowFlagMask | kFCSRUnderflowFlagMask | kFCSRInvalidOpFlagMask);
__ And(scratch, scratch,
kFCSROverflowCauseMask | kFCSRUnderflowCauseMask |
kFCSRInvalidOpCauseMask);
// If we had no exceptions then set result_reg and we are done.
Label error;
__ Branch(&error, ne, scratch, Operand(zero_reg));
......
......@@ -203,6 +203,26 @@ const uint32_t kFCSRFlagMask =
const uint32_t kFCSRExceptionFlagMask = kFCSRFlagMask ^ kFCSRInexactFlagMask;
const uint32_t kFCSRInexactCauseBit = 12;
const uint32_t kFCSRUnderflowCauseBit = 13;
const uint32_t kFCSROverflowCauseBit = 14;
const uint32_t kFCSRDivideByZeroCauseBit = 15;
const uint32_t kFCSRInvalidOpCauseBit = 16;
const uint32_t kFCSRUnimplementedOpCauseBit = 17;
const uint32_t kFCSRInexactCauseMask = 1 << kFCSRInexactCauseBit;
const uint32_t kFCSRUnderflowCauseMask = 1 << kFCSRUnderflowCauseBit;
const uint32_t kFCSROverflowCauseMask = 1 << kFCSROverflowCauseBit;
const uint32_t kFCSRDivideByZeroCauseMask = 1 << kFCSRDivideByZeroCauseBit;
const uint32_t kFCSRInvalidOpCauseMask = 1 << kFCSRInvalidOpCauseBit;
const uint32_t kFCSRUnimplementedOpCauseMask = 1
<< kFCSRUnimplementedOpCauseBit;
const uint32_t kFCSRCauseMask =
kFCSRInexactCauseMask | kFCSRUnderflowCauseMask | kFCSROverflowCauseMask |
kFCSRDivideByZeroCauseMask | kFCSRInvalidOpCauseMask |
kFCSRUnimplementedOpCauseBit;
// 'pref' instruction hints
const int32_t kPrefHintLoad = 0;
const int32_t kPrefHintStore = 1;
......
......@@ -2713,22 +2713,17 @@ void TurboAssembler::TryInlineTruncateDoubleToI(Register result,
Label* done) {
BlockTrampolinePoolScope block_trampoline_pool(this);
DoubleRegister single_scratch = kScratchDoubleReg.low();
UseScratchRegisterScope temps(this);
Register scratch = temps.Acquire();
Register scratch2 = t9;
Register scratch = t9;
// Clear cumulative exception flags and save the FCSR.
cfc1(scratch2, FCSR);
ctc1(zero_reg, FCSR);
// Try a conversion to a signed integer.
trunc_w_d(single_scratch, double_input);
mfc1(result, single_scratch);
// Retrieve and restore the FCSR.
// Retrieve the FCSR.
cfc1(scratch, FCSR);
ctc1(scratch2, FCSR);
// Check for overflow and NaNs.
And(scratch, scratch,
kFCSROverflowFlagMask | kFCSRUnderflowFlagMask | kFCSRInvalidOpFlagMask);
kFCSROverflowCauseMask | kFCSRUnderflowCauseMask |
kFCSRInvalidOpCauseMask);
// If we had no exceptions we are done.
Branch(done, eq, scratch, Operand(zero_reg));
}
......
......@@ -165,6 +165,26 @@ const uint32_t kFCSRFlagMask =
const uint32_t kFCSRExceptionFlagMask = kFCSRFlagMask ^ kFCSRInexactFlagMask;
const uint32_t kFCSRInexactCauseBit = 12;
const uint32_t kFCSRUnderflowCauseBit = 13;
const uint32_t kFCSROverflowCauseBit = 14;
const uint32_t kFCSRDivideByZeroCauseBit = 15;
const uint32_t kFCSRInvalidOpCauseBit = 16;
const uint32_t kFCSRUnimplementedOpCauseBit = 17;
const uint32_t kFCSRInexactCauseMask = 1 << kFCSRInexactCauseBit;
const uint32_t kFCSRUnderflowCauseMask = 1 << kFCSRUnderflowCauseBit;
const uint32_t kFCSROverflowCauseMask = 1 << kFCSROverflowCauseBit;
const uint32_t kFCSRDivideByZeroCauseMask = 1 << kFCSRDivideByZeroCauseBit;
const uint32_t kFCSRInvalidOpCauseMask = 1 << kFCSRInvalidOpCauseBit;
const uint32_t kFCSRUnimplementedOpCauseMask = 1
<< kFCSRUnimplementedOpCauseBit;
const uint32_t kFCSRCauseMask =
kFCSRInexactCauseMask | kFCSRUnderflowCauseMask | kFCSROverflowCauseMask |
kFCSRDivideByZeroCauseMask | kFCSRInvalidOpCauseMask |
kFCSRUnimplementedOpCauseBit;
// 'pref' instruction hints
const int32_t kPrefHintLoad = 0;
const int32_t kPrefHintStore = 1;
......
......@@ -3239,23 +3239,18 @@ void TurboAssembler::TryInlineTruncateDoubleToI(Register result,
DoubleRegister double_input,
Label* done) {
DoubleRegister single_scratch = kScratchDoubleReg.low();
UseScratchRegisterScope temps(this);
BlockTrampolinePoolScope block_trampoline_pool(this);
Register scratch = temps.Acquire();
Register scratch2 = t9;
Register scratch = t9;
// Clear cumulative exception flags and save the FCSR.
cfc1(scratch2, FCSR);
ctc1(zero_reg, FCSR);
// Try a conversion to a signed integer.
trunc_w_d(single_scratch, double_input);
mfc1(result, single_scratch);
// Retrieve and restore the FCSR.
// Retrieve the FCSR.
cfc1(scratch, FCSR);
ctc1(scratch2, FCSR);
// Check for overflow and NaNs.
And(scratch, scratch,
kFCSROverflowFlagMask | kFCSRUnderflowFlagMask | kFCSRInvalidOpFlagMask);
kFCSROverflowCauseMask | kFCSRUnderflowCauseMask |
kFCSRInvalidOpCauseMask);
// If we had no exceptions we are done.
Branch(done, eq, scratch, Operand(zero_reg));
}
......
......@@ -1578,59 +1578,41 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
}
case kMips64TruncLS: {
FPURegister scratch = kScratchDoubleReg;
Register tmp_fcsr = kScratchReg;
Register result = kScratchReg2;
Register result = kScratchReg;
bool load_status = instr->OutputCount() > 1;
if (load_status) {
// Save FCSR.
__ cfc1(tmp_fcsr, FCSR);
// Clear FPU flags.
__ ctc1(zero_reg, FCSR);
}
// Other arches use round to zero here, so we follow.
__ trunc_l_s(scratch, i.InputDoubleRegister(0));
__ dmfc1(i.OutputRegister(), scratch);
if (load_status) {
__ cfc1(result, FCSR);
// Check for overflow and NaNs.
__ andi(result, result,
(kFCSROverflowFlagMask | kFCSRInvalidOpFlagMask));
__ And(result, result,
(kFCSROverflowCauseMask | kFCSRInvalidOpCauseMask));
__ Slt(result, zero_reg, result);
__ xori(result, result, 1);
__ mov(i.OutputRegister(1), result);
// Restore FCSR
__ ctc1(tmp_fcsr, FCSR);
}
break;
}
case kMips64TruncLD: {
FPURegister scratch = kScratchDoubleReg;
Register tmp_fcsr = kScratchReg;
Register result = kScratchReg2;
Register result = kScratchReg;
bool set_overflow_to_min_i64 = MiscField::decode(instr->opcode());
bool load_status = instr->OutputCount() > 1;
DCHECK_IMPLIES(set_overflow_to_min_i64, instr->OutputCount() == 1);
if (load_status) {
// Save FCSR.
__ cfc1(tmp_fcsr, FCSR);
// Clear FPU flags.
__ ctc1(zero_reg, FCSR);
}
// Other arches use round to zero here, so we follow.
__ trunc_l_d(scratch, i.InputDoubleRegister(0));
__ dmfc1(i.OutputRegister(0), scratch);
if (load_status) {
__ cfc1(result, FCSR);
// Check for overflow and NaNs.
__ andi(result, result,
(kFCSROverflowFlagMask | kFCSRInvalidOpFlagMask));
__ And(result, result,
(kFCSROverflowCauseMask | kFCSRInvalidOpCauseMask));
__ Slt(result, zero_reg, result);
__ xori(result, result, 1);
__ mov(i.OutputRegister(1), result);
// Restore FCSR
__ ctc1(tmp_fcsr, FCSR);
}
if (set_overflow_to_min_i64) {
// Avoid INT64_MAX as an overflow indicator and use INT64_MIN instead,
......
......@@ -1107,6 +1107,10 @@ void Simulator::set_fcsr_bit(uint32_t cc, bool value) {
bool Simulator::test_fcsr_bit(uint32_t cc) { return FCSR_ & (1 << cc); }
void Simulator::clear_fcsr_cause() {
FCSR_ &= ~kFCSRCauseMask;
}
void Simulator::set_fcsr_rounding_mode(FPURoundingMode mode) {
FCSR_ |= mode & kFPURoundingModeMask;
}
......@@ -1247,24 +1251,31 @@ bool Simulator::set_fcsr_round_error(double original, double rounded) {
double max_int32 = std::numeric_limits<int32_t>::max();
double min_int32 = std::numeric_limits<int32_t>::min();
clear_fcsr_cause();
if (!std::isfinite(original) || !std::isfinite(rounded)) {
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
if (original != rounded) {
set_fcsr_bit(kFCSRInexactFlagBit, true);
set_fcsr_bit(kFCSRInexactCauseBit, true);
}
if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) {
set_fcsr_bit(kFCSRUnderflowFlagBit, true);
set_fcsr_bit(kFCSRUnderflowCauseBit, true);
ret = true;
}
if (rounded > max_int32 || rounded < min_int32) {
set_fcsr_bit(kFCSROverflowFlagBit, true);
set_fcsr_bit(kFCSROverflowCauseBit, true);
// The reference is not really clear but it seems this is required:
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
......@@ -1280,24 +1291,31 @@ bool Simulator::set_fcsr_round64_error(double original, double rounded) {
double max_int64 = std::numeric_limits<int64_t>::max();
double min_int64 = std::numeric_limits<int64_t>::min();
clear_fcsr_cause();
if (!std::isfinite(original) || !std::isfinite(rounded)) {
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
if (original != rounded) {
set_fcsr_bit(kFCSRInexactFlagBit, true);
set_fcsr_bit(kFCSRInexactCauseBit, true);
}
if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) {
set_fcsr_bit(kFCSRUnderflowFlagBit, true);
set_fcsr_bit(kFCSRUnderflowCauseBit, true);
ret = true;
}
if (rounded >= max_int64 || rounded < min_int64) {
set_fcsr_bit(kFCSROverflowFlagBit, true);
set_fcsr_bit(kFCSROverflowCauseBit, true);
// The reference is not really clear but it seems this is required:
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
......@@ -1311,24 +1329,31 @@ bool Simulator::set_fcsr_round_error(float original, float rounded) {
double max_int32 = std::numeric_limits<int32_t>::max();
double min_int32 = std::numeric_limits<int32_t>::min();
clear_fcsr_cause();
if (!std::isfinite(original) || !std::isfinite(rounded)) {
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
if (original != rounded) {
set_fcsr_bit(kFCSRInexactFlagBit, true);
set_fcsr_bit(kFCSRInexactCauseBit, true);
}
if (rounded < FLT_MIN && rounded > -FLT_MIN && rounded != 0) {
set_fcsr_bit(kFCSRUnderflowFlagBit, true);
set_fcsr_bit(kFCSRUnderflowCauseBit, true);
ret = true;
}
if (rounded > max_int32 || rounded < min_int32) {
set_fcsr_bit(kFCSROverflowFlagBit, true);
set_fcsr_bit(kFCSROverflowCauseBit, true);
// The reference is not really clear but it seems this is required:
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
......@@ -1344,24 +1369,31 @@ bool Simulator::set_fcsr_round64_error(float original, float rounded) {
double max_int64 = std::numeric_limits<int64_t>::max();
double min_int64 = std::numeric_limits<int64_t>::min();
clear_fcsr_cause();
if (!std::isfinite(original) || !std::isfinite(rounded)) {
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
if (original != rounded) {
set_fcsr_bit(kFCSRInexactFlagBit, true);
set_fcsr_bit(kFCSRInexactCauseBit, true);
}
if (rounded < FLT_MIN && rounded > -FLT_MIN && rounded != 0) {
set_fcsr_bit(kFCSRUnderflowFlagBit, true);
set_fcsr_bit(kFCSRUnderflowCauseBit, true);
ret = true;
}
if (rounded >= max_int64 || rounded < min_int64) {
set_fcsr_bit(kFCSROverflowFlagBit, true);
set_fcsr_bit(kFCSROverflowCauseBit, true);
// The reference is not really clear but it seems this is required:
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
......
......@@ -267,6 +267,7 @@ class Simulator : public SimulatorBase {
void set_msa_register(int wreg, const T* value);
void set_fcsr_bit(uint32_t cc, bool value);
bool test_fcsr_bit(uint32_t cc);
void clear_fcsr_cause();
void set_fcsr_rounding_mode(FPURoundingMode mode);
void set_msacsr_rounding_mode(FPURoundingMode mode);
unsigned int get_fcsr_rounding_mode();
......
......@@ -1034,6 +1034,10 @@ void Simulator::set_fcsr_bit(uint32_t cc, bool value) {
bool Simulator::test_fcsr_bit(uint32_t cc) { return FCSR_ & (1 << cc); }
void Simulator::clear_fcsr_cause() {
FCSR_ &= ~kFCSRCauseMask;
}
void Simulator::set_fcsr_rounding_mode(FPURoundingMode mode) {
FCSR_ |= mode & kFPURoundingModeMask;
}
......@@ -1057,24 +1061,31 @@ bool Simulator::set_fcsr_round_error(double original, double rounded) {
double max_int32 = std::numeric_limits<int32_t>::max();
double min_int32 = std::numeric_limits<int32_t>::min();
clear_fcsr_cause();
if (!std::isfinite(original) || !std::isfinite(rounded)) {
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
if (original != rounded) {
set_fcsr_bit(kFCSRInexactFlagBit, true);
set_fcsr_bit(kFCSRInexactCauseBit, true);
}
if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) {
set_fcsr_bit(kFCSRUnderflowFlagBit, true);
set_fcsr_bit(kFCSRUnderflowCauseBit, true);
ret = true;
}
if (rounded > max_int32 || rounded < min_int32) {
set_fcsr_bit(kFCSROverflowFlagBit, true);
set_fcsr_bit(kFCSROverflowCauseBit, true);
// The reference is not really clear but it seems this is required:
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
......@@ -1090,24 +1101,31 @@ bool Simulator::set_fcsr_round64_error(double original, double rounded) {
double max_int64 = std::numeric_limits<int64_t>::max();
double min_int64 = std::numeric_limits<int64_t>::min();
clear_fcsr_cause();
if (!std::isfinite(original) || !std::isfinite(rounded)) {
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
if (original != rounded) {
set_fcsr_bit(kFCSRInexactFlagBit, true);
set_fcsr_bit(kFCSRInexactCauseBit, true);
}
if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) {
set_fcsr_bit(kFCSRUnderflowFlagBit, true);
set_fcsr_bit(kFCSRUnderflowCauseBit, true);
ret = true;
}
if (rounded >= max_int64 || rounded < min_int64) {
set_fcsr_bit(kFCSROverflowFlagBit, true);
set_fcsr_bit(kFCSROverflowCauseBit, true);
// The reference is not really clear but it seems this is required:
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
......@@ -1121,24 +1139,31 @@ bool Simulator::set_fcsr_round_error(float original, float rounded) {
double max_int32 = std::numeric_limits<int32_t>::max();
double min_int32 = std::numeric_limits<int32_t>::min();
clear_fcsr_cause();
if (!std::isfinite(original) || !std::isfinite(rounded)) {
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
if (original != rounded) {
set_fcsr_bit(kFCSRInexactFlagBit, true);
set_fcsr_bit(kFCSRInexactCauseBit, true);
}
if (rounded < FLT_MIN && rounded > -FLT_MIN && rounded != 0) {
set_fcsr_bit(kFCSRUnderflowFlagBit, true);
set_fcsr_bit(kFCSRUnderflowCauseBit, true);
ret = true;
}
if (rounded > max_int32 || rounded < min_int32) {
set_fcsr_bit(kFCSROverflowFlagBit, true);
set_fcsr_bit(kFCSROverflowCauseBit, true);
// The reference is not really clear but it seems this is required:
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
......@@ -1271,24 +1296,31 @@ bool Simulator::set_fcsr_round64_error(float original, float rounded) {
double max_int64 = std::numeric_limits<int64_t>::max();
double min_int64 = std::numeric_limits<int64_t>::min();
clear_fcsr_cause();
if (!std::isfinite(original) || !std::isfinite(rounded)) {
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
if (original != rounded) {
set_fcsr_bit(kFCSRInexactFlagBit, true);
set_fcsr_bit(kFCSRInexactCauseBit, true);
}
if (rounded < FLT_MIN && rounded > -FLT_MIN && rounded != 0) {
set_fcsr_bit(kFCSRUnderflowFlagBit, true);
set_fcsr_bit(kFCSRUnderflowCauseBit, true);
ret = true;
}
if (rounded >= max_int64 || rounded < min_int64) {
set_fcsr_bit(kFCSROverflowFlagBit, true);
set_fcsr_bit(kFCSROverflowCauseBit, true);
// The reference is not really clear but it seems this is required:
set_fcsr_bit(kFCSRInvalidOpFlagBit, true);
set_fcsr_bit(kFCSRInvalidOpCauseBit, true);
ret = true;
}
......
......@@ -283,6 +283,7 @@ class Simulator : public SimulatorBase {
template <typename T_fp, typename T_int>
void round_according_to_msacsr(T_fp toRound, T_fp* rounded,
T_int* rounded_int);
void clear_fcsr_cause();
void set_fcsr_rounding_mode(FPURoundingMode mode);
void set_msacsr_rounding_mode(FPURoundingMode mode);
unsigned int get_fcsr_rounding_mode();
......
......@@ -1335,22 +1335,17 @@ bool LiftoffAssembler::emit_type_conversion(WasmOpcode opcode,
case kExprI32SConvertF64: {
LiftoffRegister scratch =
GetUnusedRegister(kGpReg, LiftoffRegList::ForRegs(dst));
LiftoffRegister scratch2 =
GetUnusedRegister(kGpReg, LiftoffRegList::ForRegs(dst, scratch));
// Clear cumulative exception flags and save the FCSR.
cfc1(scratch2.gp(), FCSR);
ctc1(zero_reg, FCSR);
// Try a conversion to a signed integer.
trunc_w_d(kScratchDoubleReg, src.fp());
mfc1(dst.gp(), kScratchDoubleReg);
// Retrieve and restore the FCSR.
// Retrieve the FCSR.
cfc1(scratch.gp(), FCSR);
ctc1(scratch2.gp(), FCSR);
// Check for overflow and NaNs.
And(scratch.gp(), scratch.gp(),
kFCSROverflowFlagMask | kFCSRUnderflowFlagMask |
kFCSRInvalidOpFlagMask);
kFCSROverflowCauseMask | kFCSRUnderflowCauseMask |
kFCSRInvalidOpCauseMask);
// If we had exceptions we are trap.
Branch(trap, ne, scratch.gp(), Operand(zero_reg));
return true;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment