[turbofan] Add the RoundInt32ToFloat32 operator to turbofan.
The new operator converts an int32 input to float32. If the input cannot be represented exactly in float32, the value is rounded using the round-ties-even rounding mode (the default rounding mode). I provide implementations of the new operator for x64, ia32, arm, arm64, mips, mips64, ppc, and ppc64. R=titzer@chromium.org, v8-arm-ports@googlegroups.com, v8-mips-ports@googlegroups.com, v8-ppc-ports@googlegroups.com Review URL: https://codereview.chromium.org/1589363002 Cr-Commit-Position: refs/heads/master@{#33347}
This commit is contained in:
parent
fc53eed14b
commit
e06f7d784e
@ -831,6 +831,13 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) {
|
||||
DCHECK_EQ(LeaveCC, i.OutputSBit());
|
||||
break;
|
||||
}
|
||||
case kArmVcvtF32S32: {
|
||||
SwVfpRegister scratch = kScratchDoubleReg.low();
|
||||
__ vmov(scratch, i.InputRegister(0));
|
||||
__ vcvt_f32_s32(i.OutputFloat32Register(), scratch);
|
||||
DCHECK_EQ(LeaveCC, i.OutputSBit());
|
||||
break;
|
||||
}
|
||||
case kArmVcvtF64S32: {
|
||||
SwVfpRegister scratch = kScratchDoubleReg.low();
|
||||
__ vmov(scratch, i.InputRegister(0));
|
||||
|
@ -76,6 +76,7 @@ namespace compiler {
|
||||
V(ArmVrintnF64) \
|
||||
V(ArmVcvtF32F64) \
|
||||
V(ArmVcvtF64F32) \
|
||||
V(ArmVcvtF32S32) \
|
||||
V(ArmVcvtF64S32) \
|
||||
V(ArmVcvtF64U32) \
|
||||
V(ArmVcvtS32F32) \
|
||||
|
@ -78,6 +78,7 @@ int InstructionScheduler::GetTargetInstructionFlags(
|
||||
case kArmVrintnF64:
|
||||
case kArmVcvtF32F64:
|
||||
case kArmVcvtF64F32:
|
||||
case kArmVcvtF32S32:
|
||||
case kArmVcvtF64S32:
|
||||
case kArmVcvtF64U32:
|
||||
case kArmVcvtS32F32:
|
||||
|
@ -921,6 +921,11 @@ void InstructionSelector::VisitChangeFloat32ToFloat64(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitRoundInt32ToFloat32(Node* node) {
|
||||
VisitRR(this, kArmVcvtF32S32, node);
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitChangeInt32ToFloat64(Node* node) {
|
||||
VisitRR(this, kArmVcvtF64S32, node);
|
||||
}
|
||||
|
@ -1096,6 +1096,9 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) {
|
||||
__ Cset(i.OutputRegister(1), ne);
|
||||
}
|
||||
break;
|
||||
case kArm64Int32ToFloat32:
|
||||
__ Scvtf(i.OutputFloat32Register(), i.InputRegister32(0));
|
||||
break;
|
||||
case kArm64Int32ToFloat64:
|
||||
__ Scvtf(i.OutputDoubleRegister(), i.InputRegister32(0));
|
||||
break;
|
||||
|
@ -117,6 +117,7 @@ namespace compiler {
|
||||
V(Arm64Float64ToInt64) \
|
||||
V(Arm64Float32ToUint64) \
|
||||
V(Arm64Float64ToUint64) \
|
||||
V(Arm64Int32ToFloat32) \
|
||||
V(Arm64Int32ToFloat64) \
|
||||
V(Arm64Int64ToFloat32) \
|
||||
V(Arm64Int64ToFloat64) \
|
||||
|
@ -113,6 +113,7 @@ int InstructionScheduler::GetTargetInstructionFlags(
|
||||
case kArm64Float64ToInt64:
|
||||
case kArm64Float32ToUint64:
|
||||
case kArm64Float64ToUint64:
|
||||
case kArm64Int32ToFloat32:
|
||||
case kArm64Int32ToFloat64:
|
||||
case kArm64Int64ToFloat32:
|
||||
case kArm64Int64ToFloat64:
|
||||
|
@ -1219,6 +1219,11 @@ void InstructionSelector::VisitChangeFloat32ToFloat64(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitRoundInt32ToFloat32(Node* node) {
|
||||
VisitRR(this, kArm64Int32ToFloat32, node);
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitChangeInt32ToFloat64(Node* node) {
|
||||
VisitRR(this, kArm64Int32ToFloat64, node);
|
||||
}
|
||||
|
@ -752,6 +752,9 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) {
|
||||
__ add(i.OutputRegister(), Immediate(0x80000000));
|
||||
break;
|
||||
}
|
||||
case kSSEInt32ToFloat32:
|
||||
__ cvtsi2ss(i.OutputDoubleRegister(), i.InputOperand(0));
|
||||
break;
|
||||
case kSSEInt32ToFloat64:
|
||||
__ cvtsi2sd(i.OutputDoubleRegister(), i.InputOperand(0));
|
||||
break;
|
||||
|
@ -61,6 +61,7 @@ namespace compiler {
|
||||
V(SSEFloat32ToInt32) \
|
||||
V(SSEFloat64ToInt32) \
|
||||
V(SSEFloat64ToUint32) \
|
||||
V(SSEInt32ToFloat32) \
|
||||
V(SSEInt32ToFloat64) \
|
||||
V(SSEUint32ToFloat64) \
|
||||
V(SSEFloat64ExtractLowWord32) \
|
||||
|
@ -64,6 +64,7 @@ int InstructionScheduler::GetTargetInstructionFlags(
|
||||
case kSSEFloat32ToInt32:
|
||||
case kSSEFloat64ToInt32:
|
||||
case kSSEFloat64ToUint32:
|
||||
case kSSEInt32ToFloat32:
|
||||
case kSSEInt32ToFloat64:
|
||||
case kSSEUint32ToFloat64:
|
||||
case kSSEFloat64ExtractLowWord32:
|
||||
|
@ -695,6 +695,11 @@ void InstructionSelector::VisitChangeFloat32ToFloat64(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitRoundInt32ToFloat32(Node* node) {
|
||||
VisitRO(this, node, kSSEInt32ToFloat32);
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitChangeInt32ToFloat64(Node* node) {
|
||||
VisitRO(this, node, kSSEInt32ToFloat64);
|
||||
}
|
||||
|
@ -978,6 +978,8 @@ void InstructionSelector::VisitNode(Node* node) {
|
||||
return MarkAsWord32(node), VisitTruncateInt64ToInt32(node);
|
||||
case IrOpcode::kRoundInt64ToFloat32:
|
||||
return MarkAsFloat32(node), VisitRoundInt64ToFloat32(node);
|
||||
case IrOpcode::kRoundInt32ToFloat32:
|
||||
return MarkAsFloat32(node), VisitRoundInt32ToFloat32(node);
|
||||
case IrOpcode::kRoundInt64ToFloat64:
|
||||
return MarkAsFloat64(node), VisitRoundInt64ToFloat64(node);
|
||||
case IrOpcode::kBitcastFloat32ToInt32:
|
||||
|
@ -150,6 +150,7 @@ CheckedStoreRepresentation CheckedStoreRepresentationOf(Operator const* op) {
|
||||
V(TryTruncateFloat32ToUint64, Operator::kNoProperties, 1, 0, 2) \
|
||||
V(TryTruncateFloat64ToUint64, Operator::kNoProperties, 1, 0, 2) \
|
||||
V(ChangeInt32ToFloat64, Operator::kNoProperties, 1, 0, 1) \
|
||||
V(RoundInt32ToFloat32, Operator::kNoProperties, 1, 0, 1) \
|
||||
V(RoundInt64ToFloat32, Operator::kNoProperties, 1, 0, 1) \
|
||||
V(RoundInt64ToFloat64, Operator::kNoProperties, 1, 0, 1) \
|
||||
V(RoundUint64ToFloat32, Operator::kNoProperties, 1, 0, 1) \
|
||||
|
@ -228,6 +228,7 @@ class MachineOperatorBuilder final : public ZoneObject {
|
||||
const Operator* TruncateFloat64ToFloat32();
|
||||
const Operator* TruncateFloat64ToInt32(TruncationMode);
|
||||
const Operator* TruncateInt64ToInt32();
|
||||
const Operator* RoundInt32ToFloat32();
|
||||
const Operator* RoundInt64ToFloat32();
|
||||
const Operator* RoundInt64ToFloat64();
|
||||
const Operator* RoundUint64ToFloat32();
|
||||
|
@ -503,6 +503,11 @@ void InstructionSelector::VisitChangeFloat32ToFloat64(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitRoundInt32ToFloat32(Node* node) {
|
||||
VisitRR(this, kMipsCvtSW, node);
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitChangeInt32ToFloat64(Node* node) {
|
||||
VisitRR(this, kMipsCvtDW, node);
|
||||
}
|
||||
|
@ -802,6 +802,11 @@ void InstructionSelector::VisitChangeFloat32ToFloat64(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitRoundInt32ToFloat32(Node* node) {
|
||||
VisitRR(this, kMips64CvtSW, node);
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitChangeInt32ToFloat64(Node* node) {
|
||||
VisitRR(this, kMips64CvtDW, node);
|
||||
}
|
||||
|
@ -282,6 +282,7 @@
|
||||
V(TruncateFloat64ToFloat32) \
|
||||
V(TruncateFloat64ToInt32) \
|
||||
V(TruncateInt64ToInt32) \
|
||||
V(RoundInt32ToFloat32) \
|
||||
V(RoundInt64ToFloat32) \
|
||||
V(RoundInt64ToFloat64) \
|
||||
V(RoundUint64ToFloat32) \
|
||||
|
@ -1194,6 +1194,11 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) {
|
||||
DCHECK_EQ(LeaveRC, i.OutputRCBit());
|
||||
break;
|
||||
#endif
|
||||
case kPPC_Int32ToFloat32:
|
||||
__ ConvertIntToFloat(i.OutputDoubleRegister(), i.InputRegister(0),
|
||||
kScratchReg);
|
||||
DCHECK_EQ(LeaveRC, i.OutputRCBit());
|
||||
break;
|
||||
case kPPC_Int32ToDouble:
|
||||
__ ConvertIntToDouble(i.InputRegister(0), i.OutputDoubleRegister());
|
||||
DCHECK_EQ(LeaveRC, i.OutputRCBit());
|
||||
|
@ -82,6 +82,7 @@ namespace compiler {
|
||||
V(PPC_Int64ToDouble) \
|
||||
V(PPC_Uint64ToFloat32) \
|
||||
V(PPC_Uint64ToDouble) \
|
||||
V(PPC_Int32ToFloat32) \
|
||||
V(PPC_Int32ToDouble) \
|
||||
V(PPC_Uint32ToDouble) \
|
||||
V(PPC_Float32ToDouble) \
|
||||
|
@ -81,6 +81,7 @@ int InstructionScheduler::GetTargetInstructionFlags(
|
||||
case kPPC_Int64ToDouble:
|
||||
case kPPC_Uint64ToFloat32:
|
||||
case kPPC_Uint64ToDouble:
|
||||
case kPPC_Int32ToFloat32:
|
||||
case kPPC_Int32ToDouble:
|
||||
case kPPC_Uint32ToDouble:
|
||||
case kPPC_Float32ToDouble:
|
||||
|
@ -940,6 +940,11 @@ void InstructionSelector::VisitChangeFloat32ToFloat64(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitRoundInt32ToFloat32(Node* node) {
|
||||
VisitRR(this, kPPC_Int32ToFloat32, node);
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitChangeInt32ToFloat64(Node* node) {
|
||||
VisitRR(this, kPPC_Int32ToDouble, node);
|
||||
}
|
||||
|
@ -472,6 +472,9 @@ class RawMachineAssembler {
|
||||
Node* TruncateInt64ToInt32(Node* a) {
|
||||
return AddNode(machine()->TruncateInt64ToInt32(), a);
|
||||
}
|
||||
Node* RoundInt32ToFloat32(Node* a) {
|
||||
return AddNode(machine()->RoundInt32ToFloat32(), a);
|
||||
}
|
||||
Node* RoundInt64ToFloat32(Node* a) {
|
||||
return AddNode(machine()->RoundInt64ToFloat32(), a);
|
||||
}
|
||||
|
@ -2205,6 +2205,11 @@ Type* Typer::Visitor::TypeTruncateInt64ToInt32(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
Type* Typer::Visitor::TypeRoundInt32ToFloat32(Node* node) {
|
||||
return Type::Intersect(Type::PlainNumber(), Type::UntaggedFloat32(), zone());
|
||||
}
|
||||
|
||||
|
||||
Type* Typer::Visitor::TypeRoundInt64ToFloat32(Node* node) {
|
||||
return Type::Intersect(Type::PlainNumber(), Type::UntaggedFloat32(), zone());
|
||||
}
|
||||
|
@ -907,6 +907,7 @@ void Verifier::Visitor::Check(Node* node) {
|
||||
case IrOpcode::kFloat64LessThan:
|
||||
case IrOpcode::kFloat64LessThanOrEqual:
|
||||
case IrOpcode::kTruncateInt64ToInt32:
|
||||
case IrOpcode::kRoundInt32ToFloat32:
|
||||
case IrOpcode::kRoundInt64ToFloat32:
|
||||
case IrOpcode::kRoundInt64ToFloat64:
|
||||
case IrOpcode::kRoundUint64ToFloat64:
|
||||
|
@ -696,9 +696,7 @@ Node* WasmGraphBuilder::Unop(wasm::WasmOpcode opcode, Node* input) {
|
||||
op = m->ChangeUint32ToFloat64();
|
||||
break;
|
||||
case wasm::kExprF32SConvertI32:
|
||||
op = m->ChangeInt32ToFloat64(); // TODO(titzer): two conversions
|
||||
input = graph()->NewNode(op, input);
|
||||
op = m->TruncateFloat64ToFloat32();
|
||||
op = m->RoundInt32ToFloat32();
|
||||
break;
|
||||
case wasm::kExprF32UConvertI32:
|
||||
op = m->ChangeUint32ToFloat64();
|
||||
|
@ -1204,6 +1204,13 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) {
|
||||
__ Cvtlsi2sd(i.OutputDoubleRegister(), i.InputOperand(0));
|
||||
}
|
||||
break;
|
||||
case kSSEInt32ToFloat32:
|
||||
if (instr->InputAt(0)->IsRegister()) {
|
||||
__ Cvtlsi2ss(i.OutputDoubleRegister(), i.InputRegister(0));
|
||||
} else {
|
||||
__ Cvtlsi2ss(i.OutputDoubleRegister(), i.InputOperand(0));
|
||||
}
|
||||
break;
|
||||
case kSSEInt64ToFloat32:
|
||||
if (instr->InputAt(0)->IsRegister()) {
|
||||
__ Cvtqsi2ss(i.OutputDoubleRegister(), i.InputRegister(0));
|
||||
|
@ -85,6 +85,7 @@ namespace compiler {
|
||||
V(SSEFloat32ToUint64) \
|
||||
V(SSEFloat64ToUint64) \
|
||||
V(SSEInt32ToFloat64) \
|
||||
V(SSEInt32ToFloat32) \
|
||||
V(SSEInt64ToFloat32) \
|
||||
V(SSEInt64ToFloat64) \
|
||||
V(SSEUint64ToFloat32) \
|
||||
|
@ -87,6 +87,7 @@ int InstructionScheduler::GetTargetInstructionFlags(
|
||||
case kSSEFloat64ToUint64:
|
||||
case kSSEFloat32ToUint64:
|
||||
case kSSEInt32ToFloat64:
|
||||
case kSSEInt32ToFloat32:
|
||||
case kSSEInt64ToFloat32:
|
||||
case kSSEInt64ToFloat64:
|
||||
case kSSEUint64ToFloat32:
|
||||
|
@ -1052,6 +1052,12 @@ void InstructionSelector::VisitTruncateInt64ToInt32(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitRoundInt32ToFloat32(Node* node) {
|
||||
X64OperandGenerator g(this);
|
||||
Emit(kSSEInt32ToFloat32, g.DefineAsRegister(node), g.Use(node->InputAt(0)));
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitRoundInt64ToFloat32(Node* node) {
|
||||
X64OperandGenerator g(this);
|
||||
Emit(kSSEInt64ToFloat32, g.DefineAsRegister(node), g.Use(node->InputAt(0)));
|
||||
|
@ -655,6 +655,11 @@ void InstructionSelector::VisitChangeFloat32ToFloat64(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitRoundInt32ToFloat32(Node* node) {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
|
||||
void InstructionSelector::VisitChangeInt32ToFloat64(Node* node) {
|
||||
X87OperandGenerator g(this);
|
||||
Emit(kX87Int32ToFloat64, g.DefineAsFixed(node, stX_0),
|
||||
|
@ -2000,6 +2000,15 @@ void Assembler::cvtsd2si(Register dst, XMMRegister src) {
|
||||
}
|
||||
|
||||
|
||||
void Assembler::cvtsi2ss(XMMRegister dst, const Operand& src) {
|
||||
EnsureSpace ensure_space(this);
|
||||
EMIT(0xF3);
|
||||
EMIT(0x0F);
|
||||
EMIT(0x2A);
|
||||
emit_sse_operand(dst, src);
|
||||
}
|
||||
|
||||
|
||||
void Assembler::cvtsi2sd(XMMRegister dst, const Operand& src) {
|
||||
EnsureSpace ensure_space(this);
|
||||
EMIT(0xF2);
|
||||
|
@ -960,6 +960,8 @@ class Assembler : public AssemblerBase {
|
||||
}
|
||||
void cvtsd2si(Register dst, XMMRegister src);
|
||||
|
||||
void cvtsi2ss(XMMRegister dst, Register src) { cvtsi2ss(dst, Operand(src)); }
|
||||
void cvtsi2ss(XMMRegister dst, const Operand& src);
|
||||
void cvtsi2sd(XMMRegister dst, Register src) { cvtsi2sd(dst, Operand(src)); }
|
||||
void cvtsi2sd(XMMRegister dst, const Operand& src);
|
||||
void cvtss2sd(XMMRegister dst, const Operand& src);
|
||||
|
@ -3179,6 +3179,17 @@ void Assembler::cvtlsi2sd(XMMRegister dst, Register src) {
|
||||
}
|
||||
|
||||
|
||||
void Assembler::cvtlsi2ss(XMMRegister dst, const Operand& src) {
|
||||
DCHECK(!IsEnabled(AVX));
|
||||
EnsureSpace ensure_space(this);
|
||||
emit(0xF3);
|
||||
emit_optional_rex_32(dst, src);
|
||||
emit(0x0F);
|
||||
emit(0x2A);
|
||||
emit_sse_operand(dst, src);
|
||||
}
|
||||
|
||||
|
||||
void Assembler::cvtlsi2ss(XMMRegister dst, Register src) {
|
||||
EnsureSpace ensure_space(this);
|
||||
emit(0xF3);
|
||||
|
@ -1025,6 +1025,7 @@ class Assembler : public AssemblerBase {
|
||||
|
||||
void cvttss2si(Register dst, const Operand& src);
|
||||
void cvttss2si(Register dst, XMMRegister src);
|
||||
void cvtlsi2ss(XMMRegister dst, const Operand& src);
|
||||
void cvtlsi2ss(XMMRegister dst, Register src);
|
||||
|
||||
void andps(XMMRegister dst, XMMRegister src);
|
||||
@ -1370,6 +1371,13 @@ class Assembler : public AssemblerBase {
|
||||
void vcvtlsi2sd(XMMRegister dst, XMMRegister src1, const Operand& src2) {
|
||||
vsd(0x2a, dst, src1, src2, kF2, k0F, kW0);
|
||||
}
|
||||
void vcvtlsi2ss(XMMRegister dst, XMMRegister src1, Register src2) {
|
||||
XMMRegister isrc2 = {src2.code()};
|
||||
vsd(0x2a, dst, src1, isrc2, kF3, k0F, kW0);
|
||||
}
|
||||
void vcvtlsi2ss(XMMRegister dst, XMMRegister src1, const Operand& src2) {
|
||||
vsd(0x2a, dst, src1, src2, kF3, k0F, kW0);
|
||||
}
|
||||
void vcvtqsi2ss(XMMRegister dst, XMMRegister src1, Register src2) {
|
||||
XMMRegister isrc2 = {src2.code()};
|
||||
vsd(0x2a, dst, src1, isrc2, kF3, k0F, kW1);
|
||||
|
@ -823,6 +823,30 @@ void MacroAssembler::Cvtlsi2sd(XMMRegister dst, const Operand& src) {
|
||||
}
|
||||
|
||||
|
||||
void MacroAssembler::Cvtlsi2ss(XMMRegister dst, Register src) {
|
||||
if (CpuFeatures::IsSupported(AVX)) {
|
||||
CpuFeatureScope scope(this, AVX);
|
||||
vxorps(dst, dst, dst);
|
||||
vcvtlsi2ss(dst, dst, src);
|
||||
} else {
|
||||
xorps(dst, dst);
|
||||
cvtlsi2ss(dst, src);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MacroAssembler::Cvtlsi2ss(XMMRegister dst, const Operand& src) {
|
||||
if (CpuFeatures::IsSupported(AVX)) {
|
||||
CpuFeatureScope scope(this, AVX);
|
||||
vxorps(dst, dst, dst);
|
||||
vcvtlsi2ss(dst, dst, src);
|
||||
} else {
|
||||
xorps(dst, dst);
|
||||
cvtlsi2ss(dst, src);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MacroAssembler::Cvtqsi2ss(XMMRegister dst, Register src) {
|
||||
if (CpuFeatures::IsSupported(AVX)) {
|
||||
CpuFeatureScope scope(this, AVX);
|
||||
|
@ -812,6 +812,8 @@ class MacroAssembler: public Assembler {
|
||||
void Cvtlsi2sd(XMMRegister dst, Register src);
|
||||
void Cvtlsi2sd(XMMRegister dst, const Operand& src);
|
||||
|
||||
void Cvtlsi2ss(XMMRegister dst, Register src);
|
||||
void Cvtlsi2ss(XMMRegister dst, const Operand& src);
|
||||
void Cvtqsi2ss(XMMRegister dst, Register src);
|
||||
void Cvtqsi2ss(XMMRegister dst, const Operand& src);
|
||||
|
||||
|
@ -6013,6 +6013,13 @@ TEST(RunBitcastFloat32ToInt32) {
|
||||
}
|
||||
|
||||
|
||||
TEST(RunRoundInt32ToFloat32) {
|
||||
BufferedRawMachineAssemblerTester<float> m(MachineType::Int32());
|
||||
m.Return(m.RoundInt32ToFloat32(m.Parameter(0)));
|
||||
FOR_INT32_INPUTS(i) { CHECK_EQ(static_cast<float>(*i), m.Call(*i)); }
|
||||
}
|
||||
|
||||
|
||||
TEST(RunBitcastInt32ToFloat32) {
|
||||
int32_t input = 1;
|
||||
float output = 0.0;
|
||||
|
Loading…
Reference in New Issue
Block a user