Commit 0296e169 by Shahbaz Youssefi Committed by Commit Bot

Vulkan: Refactor atomic counter retype code

A generic "retyper" class is extracted out of the atomic counter retype code to be used with coverting samplerCube to sampler2DArray for seamful cubemap sampling emulation. Bug: angleproject:3732 Change-Id: I8b5f835125b9513afcfe7baeea48afaf1299a027 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1733807 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarTim Van Patten <timvp@google.com>
parent ed5f7e4d
...@@ -71,6 +71,7 @@ class TVector : public std::vector<T, pool_allocator<T>> ...@@ -71,6 +71,7 @@ class TVector : public std::vector<T, pool_allocator<T>>
TVector() : std::vector<T, pool_allocator<T>>() {} TVector() : std::vector<T, pool_allocator<T>>() {}
TVector(const pool_allocator<T> &a) : std::vector<T, pool_allocator<T>>(a) {} TVector(const pool_allocator<T> &a) : std::vector<T, pool_allocator<T>>(a) {}
TVector(size_type i) : std::vector<T, pool_allocator<T>>(i) {} TVector(size_type i) : std::vector<T, pool_allocator<T>>(i) {}
TVector(std::initializer_list<T> init) : std::vector<T, pool_allocator<T>>(init) {}
}; };
template <class K, class D, class H = std::hash<K>, class CMP = std::equal_to<K>> template <class K, class D, class H = std::hash<K>, class CMP = std::equal_to<K>>
......
...@@ -152,14 +152,6 @@ class DeclareDefaultUniformsTraverser : public TIntermTraverser ...@@ -152,14 +152,6 @@ class DeclareDefaultUniformsTraverser : public TIntermTraverser
bool mInDefaultUniform; bool mInDefaultUniform;
}; };
TIntermConstantUnion *CreateFloatConstant(float value)
{
const TType *constantType = StaticType::GetBasic<EbtFloat, 1>();
TConstantUnion *constantValue = new TConstantUnion;
constantValue->setFConst(value);
return new TIntermConstantUnion(constantValue, *constantType);
}
constexpr ImmutableString kFlippedPointCoordName = ImmutableString("flippedPointCoord"); constexpr ImmutableString kFlippedPointCoordName = ImmutableString("flippedPointCoord");
constexpr ImmutableString kFlippedFragCoordName = ImmutableString("flippedFragCoord"); constexpr ImmutableString kFlippedFragCoordName = ImmutableString("flippedFragCoord");
constexpr ImmutableString kEmulatedDepthRangeParams = ImmutableString("ANGLEDepthRangeParams"); constexpr ImmutableString kEmulatedDepthRangeParams = ImmutableString("ANGLEDepthRangeParams");
...@@ -223,8 +215,7 @@ void FlipBuiltinVariable(TIntermBlock *root, ...@@ -223,8 +215,7 @@ void FlipBuiltinVariable(TIntermBlock *root,
TIntermSymbol *builtinRef = new TIntermSymbol(builtin); TIntermSymbol *builtinRef = new TIntermSymbol(builtin);
// Create a swizzle to "builtin.y" // Create a swizzle to "builtin.y"
TVector<int> swizzleOffsetY; TVector<int> swizzleOffsetY = {1};
swizzleOffsetY.push_back(1);
TIntermSwizzle *builtinY = new TIntermSwizzle(builtinRef, swizzleOffsetY); TIntermSwizzle *builtinY = new TIntermSwizzle(builtinRef, swizzleOffsetY);
// Create a symbol reference to our new variable that will hold the modified builtin. // Create a symbol reference to our new variable that will hold the modified builtin.
...@@ -296,16 +287,14 @@ void AppendVertexShaderDepthCorrectionToMain(TIntermBlock *root, TSymbolTable *s ...@@ -296,16 +287,14 @@ void AppendVertexShaderDepthCorrectionToMain(TIntermBlock *root, TSymbolTable *s
TIntermSymbol *positionRef = new TIntermSymbol(position); TIntermSymbol *positionRef = new TIntermSymbol(position);
// Create a swizzle to "gl_Position.z" // Create a swizzle to "gl_Position.z"
TVector<int> swizzleOffsetZ; TVector<int> swizzleOffsetZ = {2};
swizzleOffsetZ.push_back(2);
TIntermSwizzle *positionZ = new TIntermSwizzle(positionRef, swizzleOffsetZ); TIntermSwizzle *positionZ = new TIntermSwizzle(positionRef, swizzleOffsetZ);
// Create a constant "0.5" // Create a constant "0.5"
TIntermConstantUnion *oneHalf = CreateFloatConstant(0.5f); TIntermConstantUnion *oneHalf = CreateFloatNode(0.5f);
// Create a swizzle to "gl_Position.w" // Create a swizzle to "gl_Position.w"
TVector<int> swizzleOffsetW; TVector<int> swizzleOffsetW = {3};
swizzleOffsetW.push_back(3);
TIntermSwizzle *positionW = new TIntermSwizzle(positionRef->deepCopy(), swizzleOffsetW); TIntermSwizzle *positionW = new TIntermSwizzle(positionRef->deepCopy(), swizzleOffsetW);
// Create the expression "(gl_Position.z + gl_Position.w) * 0.5". // Create the expression "(gl_Position.z + gl_Position.w) * 0.5".
...@@ -517,27 +506,22 @@ void AddLineSegmentRasterizationEmulation(TInfoSinkBase &sink, ...@@ -517,27 +506,22 @@ void AddLineSegmentRasterizationEmulation(TInfoSinkBase &sink,
// Create a swizzle to "ANGLEUniforms.viewport.xy". // Create a swizzle to "ANGLEUniforms.viewport.xy".
TIntermBinary *viewportRef = CreateDriverUniformRef(driverUniforms, kViewport); TIntermBinary *viewportRef = CreateDriverUniformRef(driverUniforms, kViewport);
TVector<int> swizzleOffsetXY; TVector<int> swizzleOffsetXY = {0, 1};
swizzleOffsetXY.push_back(0);
swizzleOffsetXY.push_back(1);
TIntermSwizzle *viewportXY = new TIntermSwizzle(viewportRef->deepCopy(), swizzleOffsetXY); TIntermSwizzle *viewportXY = new TIntermSwizzle(viewportRef->deepCopy(), swizzleOffsetXY);
// Create a swizzle to "ANGLEUniforms.viewport.zw". // Create a swizzle to "ANGLEUniforms.viewport.zw".
TVector<int> swizzleOffsetZW; TVector<int> swizzleOffsetZW = {2, 3};
swizzleOffsetZW.push_back(2);
swizzleOffsetZW.push_back(3);
TIntermSwizzle *viewportZW = new TIntermSwizzle(viewportRef, swizzleOffsetZW); TIntermSwizzle *viewportZW = new TIntermSwizzle(viewportRef, swizzleOffsetZW);
// ANGLEPosition.xy / ANGLEPosition.w // ANGLEPosition.xy / ANGLEPosition.w
TIntermSymbol *position = new TIntermSymbol(anglePosition); TIntermSymbol *position = new TIntermSymbol(anglePosition);
TIntermSwizzle *positionXY = new TIntermSwizzle(position, swizzleOffsetXY); TIntermSwizzle *positionXY = new TIntermSwizzle(position, swizzleOffsetXY);
TVector<int> swizzleOffsetW; TVector<int> swizzleOffsetW = {3};
swizzleOffsetW.push_back(3);
TIntermSwizzle *positionW = new TIntermSwizzle(position->deepCopy(), swizzleOffsetW); TIntermSwizzle *positionW = new TIntermSwizzle(position->deepCopy(), swizzleOffsetW);
TIntermBinary *positionNDC = new TIntermBinary(EOpDiv, positionXY, positionW); TIntermBinary *positionNDC = new TIntermBinary(EOpDiv, positionXY, positionW);
// ANGLEPosition * 0.5 // ANGLEPosition * 0.5
TIntermConstantUnion *oneHalf = CreateFloatConstant(0.5f); TIntermConstantUnion *oneHalf = CreateFloatNode(0.5f);
TIntermBinary *halfPosition = new TIntermBinary(EOpVectorTimesScalar, positionNDC, oneHalf); TIntermBinary *halfPosition = new TIntermBinary(EOpVectorTimesScalar, positionNDC, oneHalf);
// (ANGLEPosition * 0.5) + 0.5 // (ANGLEPosition * 0.5) + 0.5
...@@ -575,7 +559,7 @@ void AddLineSegmentRasterizationEmulation(TInfoSinkBase &sink, ...@@ -575,7 +559,7 @@ void AddLineSegmentRasterizationEmulation(TInfoSinkBase &sink,
TIntermBinary *baSq = new TIntermBinary(EOpMul, ba, ba->deepCopy()); TIntermBinary *baSq = new TIntermBinary(EOpMul, ba, ba->deepCopy());
// 2.0 * ba * ba // 2.0 * ba * ba
TIntermTyped *two = CreateFloatConstant(2.0f); TIntermTyped *two = CreateFloatNode(2.0f);
TIntermBinary *twoBaSq = new TIntermBinary(EOpVectorTimesScalar, baSq, two); TIntermBinary *twoBaSq = new TIntermBinary(EOpVectorTimesScalar, baSq, two);
// Assign to a temporary "ba2". // Assign to a temporary "ba2".
...@@ -583,9 +567,7 @@ void AddLineSegmentRasterizationEmulation(TInfoSinkBase &sink, ...@@ -583,9 +567,7 @@ void AddLineSegmentRasterizationEmulation(TInfoSinkBase &sink,
TIntermDeclaration *ba2Decl = CreateTempInitDeclarationNode(ba2Temp, twoBaSq); TIntermDeclaration *ba2Decl = CreateTempInitDeclarationNode(ba2Temp, twoBaSq);
// Create a swizzle to "ba2.yx". // Create a swizzle to "ba2.yx".
TVector<int> swizzleOffsetYX; TVector<int> swizzleOffsetYX = {1, 0};
swizzleOffsetYX.push_back(1);
swizzleOffsetYX.push_back(0);
TIntermSymbol *ba2 = CreateTempSymbolNode(ba2Temp); TIntermSymbol *ba2 = CreateTempSymbolNode(ba2Temp);
TIntermSwizzle *ba2YX = new TIntermSwizzle(ba2, swizzleOffsetYX); TIntermSwizzle *ba2YX = new TIntermSwizzle(ba2, swizzleOffsetYX);
...@@ -599,21 +581,19 @@ void AddLineSegmentRasterizationEmulation(TInfoSinkBase &sink, ...@@ -599,21 +581,19 @@ void AddLineSegmentRasterizationEmulation(TInfoSinkBase &sink,
TIntermSymbol *bp = CreateTempSymbolNode(bpTemp); TIntermSymbol *bp = CreateTempSymbolNode(bpTemp);
// Create a swizzle to "bp.x". // Create a swizzle to "bp.x".
TVector<int> swizzleOffsetX; TVector<int> swizzleOffsetX = {0};
swizzleOffsetX.push_back(0);
TIntermSwizzle *bpX = new TIntermSwizzle(bp, swizzleOffsetX); TIntermSwizzle *bpX = new TIntermSwizzle(bp, swizzleOffsetX);
// Using a small epsilon value ensures that we don't suffer from numerical instability when // Using a small epsilon value ensures that we don't suffer from numerical instability when
// lines are exactly vertical or horizontal. // lines are exactly vertical or horizontal.
static constexpr float kEpisilon = 0.00001f; static constexpr float kEpisilon = 0.00001f;
TIntermConstantUnion *epsilon = CreateFloatConstant(kEpisilon); TIntermConstantUnion *epsilon = CreateFloatNode(kEpisilon);
// bp.x > epsilon // bp.x > epsilon
TIntermBinary *checkX = new TIntermBinary(EOpGreaterThan, bpX, epsilon); TIntermBinary *checkX = new TIntermBinary(EOpGreaterThan, bpX, epsilon);
// Create a swizzle to "bp.y". // Create a swizzle to "bp.y".
TVector<int> swizzleOffsetY; TVector<int> swizzleOffsetY = {1};
swizzleOffsetY.push_back(1);
TIntermSwizzle *bpY = new TIntermSwizzle(bp->deepCopy(), swizzleOffsetY); TIntermSwizzle *bpY = new TIntermSwizzle(bp->deepCopy(), swizzleOffsetY);
// bp.y > epsilon // bp.y > epsilon
...@@ -798,7 +778,7 @@ void TranslatorVulkan::translate(TIntermBlock *root, ...@@ -798,7 +778,7 @@ void TranslatorVulkan::translate(TIntermBlock *root,
{ {
TIntermBinary *viewportYScale = TIntermBinary *viewportYScale =
CreateDriverUniformRef(driverUniforms, kNegViewportYScale); CreateDriverUniformRef(driverUniforms, kNegViewportYScale);
TIntermConstantUnion *pivot = CreateFloatConstant(0.5f); TIntermConstantUnion *pivot = CreateFloatNode(0.5f);
FlipBuiltinVariable(root, GetMainSequence(root), viewportYScale, &getSymbolTable(), FlipBuiltinVariable(root, GetMainSequence(root), viewportYScale, &getSymbolTable(),
BuiltInVariable::gl_PointCoord(), kFlippedPointCoordName, pivot); BuiltInVariable::gl_PointCoord(), kFlippedPointCoordName, pivot);
} }
......
...@@ -113,14 +113,22 @@ TIntermTyped *CreateZeroNode(const TType &type) ...@@ -113,14 +113,22 @@ TIntermTyped *CreateZeroNode(const TType &type)
return TIntermAggregate::CreateConstructor(constType, arguments); return TIntermAggregate::CreateConstructor(constType, arguments);
} }
TIntermConstantUnion *CreateFloatNode(float value)
{
TConstantUnion *u = new TConstantUnion[1];
u[0].setFConst(value);
TType type(EbtFloat, EbpUndefined, EvqConst, 1);
return new TIntermConstantUnion(u, type);
}
TIntermConstantUnion *CreateIndexNode(int index) TIntermConstantUnion *CreateIndexNode(int index)
{ {
TConstantUnion *u = new TConstantUnion[1]; TConstantUnion *u = new TConstantUnion[1];
u[0].setIConst(index); u[0].setIConst(index);
TType type(EbtInt, EbpUndefined, EvqConst, 1); TType type(EbtInt, EbpUndefined, EvqConst, 1);
TIntermConstantUnion *node = new TIntermConstantUnion(u, type); return new TIntermConstantUnion(u, type);
return node;
} }
TIntermConstantUnion *CreateBoolNode(bool value) TIntermConstantUnion *CreateBoolNode(bool value)
...@@ -129,8 +137,7 @@ TIntermConstantUnion *CreateBoolNode(bool value) ...@@ -129,8 +137,7 @@ TIntermConstantUnion *CreateBoolNode(bool value)
u[0].setBConst(value); u[0].setBConst(value);
TType type(EbtBool, EbpUndefined, EvqConst, 1); TType type(EbtBool, EbpUndefined, EvqConst, 1);
TIntermConstantUnion *node = new TIntermConstantUnion(u, type); return new TIntermConstantUnion(u, type);
return node;
} }
TVariable *CreateTempVariable(TSymbolTable *symbolTable, const TType *type) TVariable *CreateTempVariable(TSymbolTable *symbolTable, const TType *type)
......
...@@ -23,6 +23,7 @@ TIntermFunctionDefinition *CreateInternalFunctionDefinitionNode(const TFunction ...@@ -23,6 +23,7 @@ TIntermFunctionDefinition *CreateInternalFunctionDefinitionNode(const TFunction
TIntermBlock *functionBody); TIntermBlock *functionBody);
TIntermTyped *CreateZeroNode(const TType &type); TIntermTyped *CreateZeroNode(const TType &type);
TIntermConstantUnion *CreateFloatNode(float value);
TIntermConstantUnion *CreateIndexNode(int index); TIntermConstantUnion *CreateIndexNode(int index);
TIntermConstantUnion *CreateBoolNode(bool value); TIntermConstantUnion *CreateBoolNode(bool value);
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "compiler/translator/tree_util/ReplaceVariable.h" #include "compiler/translator/tree_util/ReplaceVariable.h"
#include "compiler/translator/IntermNode.h" #include "compiler/translator/IntermNode.h"
#include "compiler/translator/Symbol.h"
#include "compiler/translator/tree_util/IntermTraverse.h" #include "compiler/translator/tree_util/IntermTraverse.h"
namespace sh namespace sh
...@@ -61,4 +62,75 @@ void ReplaceVariableWithTyped(TIntermBlock *root, ...@@ -61,4 +62,75 @@ void ReplaceVariableWithTyped(TIntermBlock *root,
traverser.updateTree(); traverser.updateTree();
} }
TIntermFunctionPrototype *RetypeOpaqueVariablesHelper::convertFunctionPrototype(
TSymbolTable *symbolTable,
const TFunction *oldFunction)
{
if (mReplacedFunctionParams.empty())
{
return nullptr;
}
// Create a new function prototype for replacement.
TFunction *replacementFunction = new TFunction(
symbolTable, oldFunction->name(), SymbolType::UserDefined,
new TType(oldFunction->getReturnType()), oldFunction->isKnownToNotHaveSideEffects());
for (size_t paramIndex = 0; paramIndex < oldFunction->getParamCount(); ++paramIndex)
{
const TVariable *param = oldFunction->getParam(paramIndex);
TVariable *replacement = nullptr;
auto replaced = mReplacedFunctionParams.find(param);
if (replaced != mReplacedFunctionParams.end())
{
replacement = replaced->second;
}
else
{
replacement = new TVariable(symbolTable, param->name(), new TType(param->getType()),
SymbolType::UserDefined);
}
replacementFunction->addParameter(replacement);
}
mReplacedFunctions[oldFunction] = replacementFunction;
TIntermFunctionPrototype *replacementPrototype =
new TIntermFunctionPrototype(replacementFunction);
return replacementPrototype;
}
TIntermAggregate *RetypeOpaqueVariablesHelper::convertASTFunction(TIntermAggregate *node)
{
// See if the function needs replacement at all.
const TFunction *function = node->getFunction();
auto replacedFunction = mReplacedFunctions.find(function);
if (replacedFunction == mReplacedFunctions.end())
{
return nullptr;
}
// Arguments to this call are staged to be replaced at the same time.
TFunction *substituteFunction = replacedFunction->second;
TIntermSequence *substituteArguments = new TIntermSequence;
for (size_t paramIndex = 0; paramIndex < function->getParamCount(); ++paramIndex)
{
TIntermNode *param = node->getChildNode(paramIndex);
TIntermNode *replacement = nullptr;
auto replacedArg = mReplacedFunctionCallArgs.top().find(param);
if (replacedArg != mReplacedFunctionCallArgs.top().end())
{
replacement = replacedArg->second;
}
else
{
replacement = param->getAsTyped()->deepCopy();
}
substituteArguments->push_back(replacement);
}
return TIntermAggregate::CreateFunctionCall(*substituteFunction, substituteArguments);
}
} // namespace sh } // namespace sh
...@@ -9,12 +9,22 @@ ...@@ -9,12 +9,22 @@
#ifndef COMPILER_TRANSLATOR_TREEUTIL_REPLACEVARIABLE_H_ #ifndef COMPILER_TRANSLATOR_TREEUTIL_REPLACEVARIABLE_H_
#define COMPILER_TRANSLATOR_TREEUTIL_REPLACEVARIABLE_H_ #define COMPILER_TRANSLATOR_TREEUTIL_REPLACEVARIABLE_H_
#include "common/debug.h"
#include <stack>
#include <unordered_map>
namespace sh namespace sh
{ {
class TFunction;
class TIntermAggregate;
class TIntermBlock; class TIntermBlock;
class TVariable; class TIntermFunctionPrototype;
class TIntermNode;
class TIntermTyped; class TIntermTyped;
class TSymbolTable;
class TVariable;
void ReplaceVariable(TIntermBlock *root, void ReplaceVariable(TIntermBlock *root,
const TVariable *toBeReplaced, const TVariable *toBeReplaced,
...@@ -22,6 +32,108 @@ void ReplaceVariable(TIntermBlock *root, ...@@ -22,6 +32,108 @@ void ReplaceVariable(TIntermBlock *root,
void ReplaceVariableWithTyped(TIntermBlock *root, void ReplaceVariableWithTyped(TIntermBlock *root,
const TVariable *toBeReplaced, const TVariable *toBeReplaced,
const TIntermTyped *replacement); const TIntermTyped *replacement);
// A helper class to keep track of opaque variable re-typing during a pass. Unlike the above
// functions, this can be used to replace all opaque variables of a certain type with another in a
// pass that possibly does other related transformations. Only opaque variables are supported as
// replacing local variables is not supported.
//
// The class uses "old" to refer to the original type of the variable and "new" to refer to the type
// that will replace it.
//
// - replaceGlobalVariable(): Call to track a global variable that is replaced.
// - in TIntermTraverser::visitFunctionPrototype():
// * Call visitFunctionPrototype().
// * For every replaced parameter, call replaceFunctionParam().
// * call convertFunctionPrototype() to convert the prototype based on the above replacements
// and track the function with its replacement.
// * Call replaceFunction() to track the function that is replaced.
// - In PreVisit of TIntermTraverser::visitAggregate():
// * call preVisitAggregate()
// - In TIntermTraverser::visitSymbol():
// * Replace non-function-call-argument symbols that refer to a global or function param with the
// replacement (getVariableReplacement()).
// * For function call arguments, call replaceFunctionCallArg() to track the replacement.
// - In PostVisit of TIntermTraverser::visitAggregate():
// * Convert built-in functions per case. Call convertASTFunction() for non built-in functions
// for the replacement to be created.
// * Call postVisitAggregate() when done.
//
class RetypeOpaqueVariablesHelper
{
public:
RetypeOpaqueVariablesHelper() {}
~RetypeOpaqueVariablesHelper() {}
// Global variable handling:
void replaceGlobalVariable(const TVariable *oldVar, TVariable *newVar)
{
ASSERT(mReplacedGlobalVariables.count(oldVar) == 0);
mReplacedGlobalVariables[oldVar] = newVar;
}
TVariable *getVariableReplacement(const TVariable *oldVar) const
{
if (mReplacedGlobalVariables.count(oldVar) != 0)
{
return mReplacedGlobalVariables.at(oldVar);
}
else
{
// This function should only be called if the variable is expected to have been
// replaced either way (as a global variable or a function parameter).
ASSERT(mReplacedFunctionParams.count(oldVar) != 0);
return mReplacedFunctionParams.at(oldVar);
}
}
// Function parameters handling:
void visitFunctionPrototype() { mReplacedFunctionParams.clear(); }
void replaceFunctionParam(const TVariable *oldParam, TVariable *newParam)
{
ASSERT(mReplacedFunctionParams.count(oldParam) == 0);
mReplacedFunctionParams[oldParam] = newParam;
}
TVariable *getFunctionParamReplacement(const TVariable *oldParam) const
{
ASSERT(mReplacedFunctionParams.count(oldParam) != 0);
return mReplacedFunctionParams.at(oldParam);
}
// Function call arguments handling:
void preVisitAggregate() { mReplacedFunctionCallArgs.emplace(); }
void postVisitAggregate() { mReplacedFunctionCallArgs.pop(); }
void replaceFunctionCallArg(const TIntermNode *oldArg, TIntermTyped *newArg)
{
ASSERT(mReplacedFunctionCallArgs.top().count(oldArg) == 0);
mReplacedFunctionCallArgs.top()[oldArg] = newArg;
}
TIntermTyped *getFunctionCallArgReplacement(const TIntermNode *oldArg) const
{
ASSERT(mReplacedFunctionCallArgs.top().count(oldArg) != 0);
return mReplacedFunctionCallArgs.top().at(oldArg);
}
// Helper code conversion methods.
TIntermFunctionPrototype *convertFunctionPrototype(TSymbolTable *symbolTable,
const TFunction *oldFunction);
TIntermAggregate *convertASTFunction(TIntermAggregate *node);
private:
// A map from the old global variable to the new one.
std::unordered_map<const TVariable *, TVariable *> mReplacedGlobalVariables;
// A map from functions with old type parameters to one where that's replaced with the new type.
std::unordered_map<const TFunction *, TFunction *> mReplacedFunctions;
// A map from function old type parameters to their replacement new type parameter for the
// current function definition.
std::unordered_map<const TVariable *, TVariable *> mReplacedFunctionParams;
// A map from function call old type arguments to their replacement for the current function
// call.
std::stack<std::unordered_map<const TIntermNode *, TIntermTyped *>> mReplacedFunctionCallArgs;
};
} // namespace sh } // namespace sh
#endif // COMPILER_TRANSLATOR_TREEUTIL_REPLACEVARIABLE_H_ #endif // COMPILER_TRANSLATOR_TREEUTIL_REPLACEVARIABLE_H_
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