Commit aebd002d by Jamie Madill

Revert "Make sure type gets set consistently in folded binary operations"

This is blocking the revert of the geometric constant folding patch, which is breaking gpu_unittests and blocking the roll. BUG=angleproject:817 This reverts commit b07aba07. Change-Id: Ia00fc45b1ddd9d3c079742dea0627aa12304f93b Reviewed-on: https://chromium-review.googlesource.com/275321Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Tested-by: 's avatarJamie Madill <jmadill@chromium.org>
parent c84e20ee
......@@ -747,385 +747,458 @@ bool TIntermBinary::promote(TInfoSink &infoSink)
return true;
}
TIntermTyped *TIntermBinary::fold(TInfoSink &infoSink)
{
TIntermConstantUnion *leftConstant = mLeft->getAsConstantUnion();
TIntermConstantUnion *rightConstant = mRight->getAsConstantUnion();
if (leftConstant == nullptr || rightConstant == nullptr)
{
return nullptr;
}
TConstantUnion *constArray = leftConstant->foldBinary(mOp, rightConstant, infoSink);
if (constArray == nullptr)
{
return nullptr;
}
TIntermTyped *folded = new TIntermConstantUnion(constArray, getType());
folded->getTypePointer()->setQualifier(EvqConst);
folded->setLine(getLine());
return folded;
}
//
// The fold functions see if an operation on a constant can be done in place,
// without generating run-time code.
//
// Returns the constant value to keep using or nullptr.
// Returns the node to keep using, which may or may not be the node passed in.
//
TConstantUnion *TIntermConstantUnion::foldBinary(TOperator op, TIntermConstantUnion *rightNode, TInfoSink &infoSink)
TIntermTyped *TIntermConstantUnion::fold(
TOperator op, TIntermConstantUnion *rightNode, TInfoSink &infoSink)
{
TConstantUnion *leftArray = getUnionArrayPointer();
TConstantUnion *rightArray = rightNode->getUnionArrayPointer();
TConstantUnion *unionArray = getUnionArrayPointer();
if (!leftArray)
return nullptr;
if (!rightArray)
if (!unionArray)
return nullptr;
size_t objectSize = getType().getObjectSize();
// for a case like float f = vec4(2, 3, 4, 5) + 1.2;
if (rightNode->getType().getObjectSize() == 1 && objectSize > 1)
{
rightArray = Vectorize(*rightNode->getUnionArrayPointer(), objectSize);
}
else if (rightNode->getType().getObjectSize() > 1 && objectSize == 1)
if (rightNode)
{
// for a case like float f = 1.2 + vec4(2, 3, 4, 5);
leftArray = Vectorize(*getUnionArrayPointer(), rightNode->getType().getObjectSize());
objectSize = rightNode->getType().getObjectSize();
}
// binary operations
TConstantUnion *rightUnionArray = rightNode->getUnionArrayPointer();
TType returnType = getType();
TConstantUnion *resultArray = nullptr;
if (!rightUnionArray)
return nullptr;
switch(op)
{
case EOpAdd:
resultArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
resultArray[i] = leftArray[i] + rightArray[i];
break;
case EOpSub:
resultArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
resultArray[i] = leftArray[i] - rightArray[i];
break;
// for a case like float f = vec4(2, 3, 4, 5) + 1.2;
if (rightNode->getType().getObjectSize() == 1 && objectSize > 1)
{
rightUnionArray = Vectorize(*rightNode->getUnionArrayPointer(), objectSize);
returnType = getType();
}
else if (rightNode->getType().getObjectSize() > 1 && objectSize == 1)
{
// for a case like float f = 1.2 + vec4(2, 3, 4, 5);
unionArray = Vectorize(*getUnionArrayPointer(), rightNode->getType().getObjectSize());
returnType = rightNode->getType();
objectSize = rightNode->getType().getObjectSize();
}
case EOpMul:
case EOpVectorTimesScalar:
case EOpMatrixTimesScalar:
resultArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
resultArray[i] = leftArray[i] * rightArray[i];
break;
TConstantUnion *tempConstArray = nullptr;
TIntermConstantUnion *tempNode;
case EOpMatrixTimesMatrix:
bool boolNodeFlag = false;
switch(op)
{
if (getType().getBasicType() != EbtFloat ||
rightNode->getBasicType() != EbtFloat)
{
infoSink.info.message(
EPrefixInternalError, getLine(),
"Constant Folding cannot be done for matrix multiply");
return nullptr;
}
case EOpAdd:
tempConstArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
tempConstArray[i] = unionArray[i] + rightUnionArray[i];
break;
case EOpSub:
tempConstArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
tempConstArray[i] = unionArray[i] - rightUnionArray[i];
break;
const int leftCols = getCols();
const int leftRows = getRows();
const int rightCols = rightNode->getType().getCols();
const int rightRows = rightNode->getType().getRows();
const int resultCols = rightCols;
const int resultRows = leftRows;
case EOpMul:
case EOpVectorTimesScalar:
case EOpMatrixTimesScalar:
tempConstArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
tempConstArray[i] = unionArray[i] * rightUnionArray[i];
break;
resultArray = new TConstantUnion[resultCols * resultRows];
for (int row = 0; row < resultRows; row++)
case EOpMatrixTimesMatrix:
{
for (int column = 0; column < resultCols; column++)
if (getType().getBasicType() != EbtFloat ||
rightNode->getBasicType() != EbtFloat)
{
resultArray[resultRows * column + row].setFConst(0.0f);
for (int i = 0; i < leftCols; i++)
infoSink.info.message(
EPrefixInternalError, getLine(),
"Constant Folding cannot be done for matrix multiply");
return nullptr;
}
const int leftCols = getCols();
const int leftRows = getRows();
const int rightCols = rightNode->getType().getCols();
const int rightRows = rightNode->getType().getRows();
const int resultCols = rightCols;
const int resultRows = leftRows;
tempConstArray = new TConstantUnion[resultCols * resultRows];
for (int row = 0; row < resultRows; row++)
{
for (int column = 0; column < resultCols; column++)
{
resultArray[resultRows * column + row].setFConst(
resultArray[resultRows * column + row].getFConst() +
leftArray[i * leftRows + row].getFConst() *
rightArray[column * rightRows + i].getFConst());
tempConstArray[resultRows * column + row].setFConst(0.0f);
for (int i = 0; i < leftCols; i++)
{
tempConstArray[resultRows * column + row].setFConst(
tempConstArray[resultRows * column + row].getFConst() +
unionArray[i * leftRows + row].getFConst() *
rightUnionArray[column * rightRows + i].getFConst());
}
}
}
// update return type for matrix product
returnType.setPrimarySize(static_cast<unsigned char>(resultCols));
returnType.setSecondarySize(static_cast<unsigned char>(resultRows));
}
}
break;
break;
case EOpDiv:
case EOpIMod:
{
resultArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
case EOpDiv:
case EOpIMod:
{
switch (getType().getBasicType())
tempConstArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
{
case EbtFloat:
if (rightArray[i] == 0.0f)
{
infoSink.info.message(EPrefixWarning, getLine(),
"Divide by zero error during constant folding");
resultArray[i].setFConst(leftArray[i].getFConst() < 0 ? -FLT_MAX : FLT_MAX);
}
else
switch (getType().getBasicType())
{
ASSERT(op == EOpDiv);
resultArray[i].setFConst(leftArray[i].getFConst() / rightArray[i].getFConst());
}
break;
case EbtFloat:
if (rightUnionArray[i] == 0.0f)
{
infoSink.info.message(
EPrefixWarning, getLine(),
"Divide by zero error during constant folding");
tempConstArray[i].setFConst(
unionArray[i].getFConst() < 0 ? -FLT_MAX : FLT_MAX);
}
else
{
ASSERT(op == EOpDiv);
tempConstArray[i].setFConst(
unionArray[i].getFConst() /
rightUnionArray[i].getFConst());
}
break;
case EbtInt:
if (rightArray[i] == 0)
{
infoSink.info.message(EPrefixWarning, getLine(),
"Divide by zero error during constant folding");
resultArray[i].setIConst(INT_MAX);
}
else
{
if (op == EOpDiv)
case EbtInt:
if (rightUnionArray[i] == 0)
{
resultArray[i].setIConst(leftArray[i].getIConst() / rightArray[i].getIConst());
infoSink.info.message(
EPrefixWarning, getLine(),
"Divide by zero error during constant folding");
tempConstArray[i].setIConst(INT_MAX);
}
else
{
ASSERT(op == EOpIMod);
resultArray[i].setIConst(leftArray[i].getIConst() % rightArray[i].getIConst());
if (op == EOpDiv)
{
tempConstArray[i].setIConst(
unionArray[i].getIConst() /
rightUnionArray[i].getIConst());
}
else
{
ASSERT(op == EOpIMod);
tempConstArray[i].setIConst(
unionArray[i].getIConst() %
rightUnionArray[i].getIConst());
}
}
}
break;
break;
case EbtUInt:
if (rightArray[i] == 0)
{
infoSink.info.message(EPrefixWarning, getLine(),
"Divide by zero error during constant folding");
resultArray[i].setUConst(UINT_MAX);
}
else
{
if (op == EOpDiv)
case EbtUInt:
if (rightUnionArray[i] == 0)
{
resultArray[i].setUConst(leftArray[i].getUConst() / rightArray[i].getUConst());
infoSink.info.message(
EPrefixWarning, getLine(),
"Divide by zero error during constant folding");
tempConstArray[i].setUConst(UINT_MAX);
}
else
{
ASSERT(op == EOpIMod);
resultArray[i].setUConst(leftArray[i].getUConst() % rightArray[i].getUConst());
if (op == EOpDiv)
{
tempConstArray[i].setUConst(
unionArray[i].getUConst() /
rightUnionArray[i].getUConst());
}
else
{
ASSERT(op == EOpIMod);
tempConstArray[i].setUConst(
unionArray[i].getUConst() %
rightUnionArray[i].getUConst());
}
}
break;
default:
infoSink.info.message(
EPrefixInternalError, getLine(),
"Constant folding cannot be done for \"/\"");
return nullptr;
}
break;
}
}
break;
default:
infoSink.info.message(EPrefixInternalError, getLine(),
"Constant folding cannot be done for \"/\"");
case EOpMatrixTimesVector:
{
if (rightNode->getBasicType() != EbtFloat)
{
infoSink.info.message(
EPrefixInternalError, getLine(),
"Constant Folding cannot be done for matrix times vector");
return nullptr;
}
const int matrixCols = getCols();
const int matrixRows = getRows();
tempConstArray = new TConstantUnion[matrixRows];
for (int matrixRow = 0; matrixRow < matrixRows; matrixRow++)
{
tempConstArray[matrixRow].setFConst(0.0f);
for (int col = 0; col < matrixCols; col++)
{
tempConstArray[matrixRow].setFConst(
tempConstArray[matrixRow].getFConst() +
unionArray[col * matrixRows + matrixRow].getFConst() *
rightUnionArray[col].getFConst());
}
}
returnType = rightNode->getType();
returnType.setPrimarySize(static_cast<unsigned char>(matrixRows));
tempNode = new TIntermConstantUnion(tempConstArray, returnType);
tempNode->setLine(getLine());
return tempNode;
}
}
break;
case EOpMatrixTimesVector:
{
if (rightNode->getBasicType() != EbtFloat)
case EOpVectorTimesMatrix:
{
infoSink.info.message(EPrefixInternalError, getLine(),
"Constant Folding cannot be done for matrix times vector");
return nullptr;
}
if (getType().getBasicType() != EbtFloat)
{
infoSink.info.message(
EPrefixInternalError, getLine(),
"Constant Folding cannot be done for vector times matrix");
return nullptr;
}
const int matrixCols = getCols();
const int matrixRows = getRows();
const int matrixCols = rightNode->getType().getCols();
const int matrixRows = rightNode->getType().getRows();
resultArray = new TConstantUnion[matrixRows];
tempConstArray = new TConstantUnion[matrixCols];
for (int matrixRow = 0; matrixRow < matrixRows; matrixRow++)
{
resultArray[matrixRow].setFConst(0.0f);
for (int col = 0; col < matrixCols; col++)
for (int matrixCol = 0; matrixCol < matrixCols; matrixCol++)
{
resultArray[matrixRow].setFConst(resultArray[matrixRow].getFConst() +
leftArray[col * matrixRows + matrixRow].getFConst() *
rightArray[col].getFConst());
tempConstArray[matrixCol].setFConst(0.0f);
for (int matrixRow = 0; matrixRow < matrixRows; matrixRow++)
{
tempConstArray[matrixCol].setFConst(
tempConstArray[matrixCol].getFConst() +
unionArray[matrixRow].getFConst() *
rightUnionArray[matrixCol * matrixRows + matrixRow].getFConst());
}
}
returnType.setPrimarySize(static_cast<unsigned char>(matrixCols));
}
}
break;
break;
case EOpVectorTimesMatrix:
{
if (getType().getBasicType() != EbtFloat)
case EOpLogicalAnd:
// this code is written for possible future use,
// will not get executed currently
{
infoSink.info.message(EPrefixInternalError, getLine(),
"Constant Folding cannot be done for vector times matrix");
return nullptr;
tempConstArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
{
tempConstArray[i] = unionArray[i] && rightUnionArray[i];
}
}
break;
const int matrixCols = rightNode->getType().getCols();
const int matrixRows = rightNode->getType().getRows();
resultArray = new TConstantUnion[matrixCols];
case EOpLogicalOr:
// this code is written for possible future use,
// will not get executed currently
{
tempConstArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
{
tempConstArray[i] = unionArray[i] || rightUnionArray[i];
}
}
break;
for (int matrixCol = 0; matrixCol < matrixCols; matrixCol++)
case EOpLogicalXor:
{
resultArray[matrixCol].setFConst(0.0f);
for (int matrixRow = 0; matrixRow < matrixRows; matrixRow++)
tempConstArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
{
resultArray[matrixCol].setFConst(resultArray[matrixCol].getFConst() +
leftArray[matrixRow].getFConst() *
rightArray[matrixCol * matrixRows + matrixRow].getFConst());
switch (getType().getBasicType())
{
case EbtBool:
tempConstArray[i].setBConst(
unionArray[i] == rightUnionArray[i] ? false : true);
break;
default:
UNREACHABLE();
break;
}
}
}
}
break;
break;
case EOpLogicalAnd:
{
resultArray = new TConstantUnion[objectSize];
case EOpBitwiseAnd:
tempConstArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
tempConstArray[i] = unionArray[i] & rightUnionArray[i];
break;
case EOpBitwiseXor:
tempConstArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
tempConstArray[i] = unionArray[i] ^ rightUnionArray[i];
break;
case EOpBitwiseOr:
tempConstArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
tempConstArray[i] = unionArray[i] | rightUnionArray[i];
break;
case EOpBitShiftLeft:
tempConstArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
tempConstArray[i] = unionArray[i] << rightUnionArray[i];
break;
case EOpBitShiftRight:
tempConstArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
tempConstArray[i] = unionArray[i] >> rightUnionArray[i];
break;
case EOpLessThan:
ASSERT(objectSize == 1);
tempConstArray = new TConstantUnion[1];
tempConstArray->setBConst(*unionArray < *rightUnionArray);
returnType = TType(EbtBool, EbpUndefined, EvqConst);
break;
case EOpGreaterThan:
ASSERT(objectSize == 1);
tempConstArray = new TConstantUnion[1];
tempConstArray->setBConst(*unionArray > *rightUnionArray);
returnType = TType(EbtBool, EbpUndefined, EvqConst);
break;
case EOpLessThanEqual:
{
resultArray[i] = leftArray[i] && rightArray[i];
ASSERT(objectSize == 1);
TConstantUnion constant;
constant.setBConst(*unionArray > *rightUnionArray);
tempConstArray = new TConstantUnion[1];
tempConstArray->setBConst(!constant.getBConst());
returnType = TType(EbtBool, EbpUndefined, EvqConst);
break;
}
}
break;
case EOpLogicalOr:
{
resultArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
case EOpGreaterThanEqual:
{
resultArray[i] = leftArray[i] || rightArray[i];
ASSERT(objectSize == 1);
TConstantUnion constant;
constant.setBConst(*unionArray < *rightUnionArray);
tempConstArray = new TConstantUnion[1];
tempConstArray->setBConst(!constant.getBConst());
returnType = TType(EbtBool, EbpUndefined, EvqConst);
break;
}
}
break;
case EOpLogicalXor:
{
resultArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
case EOpEqual:
if (getType().getBasicType() == EbtStruct)
{
switch (getType().getBasicType())
if (!CompareStructure(rightNode->getType(),
rightNode->getUnionArrayPointer(),
unionArray))
{
case EbtBool:
resultArray[i].setBConst(leftArray[i] != rightArray[i]);
break;
default:
UNREACHABLE();
break;
boolNodeFlag = true;
}
}
else
{
for (size_t i = 0; i < objectSize; i++)
{
if (unionArray[i] != rightUnionArray[i])
{
boolNodeFlag = true;
break; // break out of for loop
}
}
}
}
break;
case EOpBitwiseAnd:
resultArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
resultArray[i] = leftArray[i] & rightArray[i];
break;
case EOpBitwiseXor:
resultArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
resultArray[i] = leftArray[i] ^ rightArray[i];
break;
case EOpBitwiseOr:
resultArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
resultArray[i] = leftArray[i] | rightArray[i];
break;
case EOpBitShiftLeft:
resultArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
resultArray[i] = leftArray[i] << rightArray[i];
break;
case EOpBitShiftRight:
resultArray = new TConstantUnion[objectSize];
for (size_t i = 0; i < objectSize; i++)
resultArray[i] = leftArray[i] >> rightArray[i];
break;
case EOpLessThan:
ASSERT(objectSize == 1);
resultArray = new TConstantUnion[1];
resultArray->setBConst(*leftArray < *rightArray);
break;
tempConstArray = new TConstantUnion[1];
if (!boolNodeFlag)
{
tempConstArray->setBConst(true);
}
else
{
tempConstArray->setBConst(false);
}
case EOpGreaterThan:
ASSERT(objectSize == 1);
resultArray = new TConstantUnion[1];
resultArray->setBConst(*leftArray > *rightArray);
break;
tempNode = new TIntermConstantUnion(
tempConstArray, TType(EbtBool, EbpUndefined, EvqConst));
tempNode->setLine(getLine());
case EOpLessThanEqual:
ASSERT(objectSize == 1);
resultArray = new TConstantUnion[1];
resultArray->setBConst(!(*leftArray > *rightArray));
break;
return tempNode;
case EOpGreaterThanEqual:
ASSERT(objectSize == 1);
resultArray = new TConstantUnion[1];
resultArray->setBConst(!(*leftArray < *rightArray));
break;
case EOpEqual:
case EOpNotEqual:
{
resultArray = new TConstantUnion[1];
bool equal = true;
case EOpNotEqual:
if (getType().getBasicType() == EbtStruct)
{
equal = CompareStructure(getType(), rightArray, leftArray);
if (CompareStructure(rightNode->getType(),
rightNode->getUnionArrayPointer(),
unionArray))
{
boolNodeFlag = true;
}
}
else
{
for (size_t i = 0; i < objectSize; i++)
{
if (leftArray[i] != rightArray[i])
if (unionArray[i] == rightUnionArray[i])
{
equal = false;
boolNodeFlag = true;
break; // break out of for loop
}
}
}
if (op == EOpEqual)
tempConstArray = new TConstantUnion[1];
if (!boolNodeFlag)
{
resultArray->setBConst(equal);
tempConstArray->setBConst(true);
}
else
{
resultArray->setBConst(!equal);
tempConstArray->setBConst(false);
}
}
break;
default:
infoSink.info.message(
EPrefixInternalError, getLine(),
"Invalid operator for constant folding");
return nullptr;
}
return resultArray;
}
//
// The fold functions see if an operation on a constant can be done in place,
// without generating run-time code.
//
// Returns the node to keep using or nullptr.
//
TIntermTyped *TIntermConstantUnion::foldUnary(TOperator op, TInfoSink &infoSink)
{
TConstantUnion *unionArray = getUnionArrayPointer();
tempNode = new TIntermConstantUnion(
tempConstArray, TType(EbtBool, EbpUndefined, EvqConst));
tempNode->setLine(getLine());
if (!unionArray)
return nullptr;
return tempNode;
size_t objectSize = getType().getObjectSize();
default:
infoSink.info.message(
EPrefixInternalError, getLine(),
"Invalid operator for constant folding");
return nullptr;
}
tempNode = new TIntermConstantUnion(tempConstArray, returnType);
tempNode->setLine(getLine());
if (op == EOpAny || op == EOpAll || op == EOpLength)
return tempNode;
}
else if (op == EOpAny || op == EOpAll || op == EOpLength)
{
// Do operations where the return type is different from the operand type.
......
......@@ -299,8 +299,7 @@ class TIntermConstantUnion : public TIntermTyped
virtual void traverse(TIntermTraverser *);
virtual bool replaceChildNode(TIntermNode *, TIntermNode *) { return false; }
TConstantUnion *foldBinary(TOperator op, TIntermConstantUnion *rightNode, TInfoSink &infoSink);
TIntermTyped *foldUnary(TOperator op, TInfoSink &infoSink);
TIntermTyped *fold(TOperator op, TIntermConstantUnion *rightNode, TInfoSink &infoSink);
static TIntermTyped *FoldAggregateBuiltIn(TOperator op, TIntermAggregate *aggregate, TInfoSink &infoSink);
......@@ -363,7 +362,6 @@ class TIntermBinary : public TIntermOperator
TIntermTyped *getLeft() const { return mLeft; }
TIntermTyped *getRight() const { return mRight; }
bool promote(TInfoSink &);
TIntermTyped *fold(TInfoSink &infoSink);
void setAddIndexClamp() { mAddIndexClamp = true; }
bool getAddIndexClamp() { return mAddIndexClamp; }
......
......@@ -57,10 +57,19 @@ TIntermTyped *TIntermediate::addBinaryMath(
if (!node->promote(mInfoSink))
return NULL;
//
// See if we can fold constants.
TIntermTyped *foldedNode = node->fold(mInfoSink);
if (foldedNode)
return foldedNode;
//
TIntermConstantUnion *leftTempConstant = left->getAsConstantUnion();
TIntermConstantUnion *rightTempConstant = right->getAsConstantUnion();
if (leftTempConstant && rightTempConstant)
{
TIntermTyped *typedReturnNode =
leftTempConstant->fold(node->getOp(), rightTempConstant, mInfoSink);
if (typedReturnNode)
return typedReturnNode;
}
return node;
}
......@@ -134,7 +143,7 @@ TIntermTyped *TIntermediate::addUnaryMath(
if (childTempConstant)
{
TIntermTyped *newChild = childTempConstant->foldUnary(op, mInfoSink);
TIntermTyped *newChild = childTempConstant->fold(op, nullptr, mInfoSink);
if (newChild)
return newChild;
......
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