From a380d5f26de1e38e33e25c00bccc6e4b504e54fa Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Tue, 29 Jul 2014 14:49:05 +0200 Subject: [PATCH] constant propagation --- arangod/Aql/AstNode.cpp | 8 +- arangod/Aql/AstNode.h | 13 ++- arangod/Aql/QueryAst.cpp | 182 ++++++++++++++++++++++++++++++++++----- arangod/Aql/QueryAst.h | 45 ++++++---- arangod/Aql/Scopes.cpp | 40 ++++++--- arangod/Aql/Scopes.h | 35 ++++++++ arangod/Aql/grammar.y | 8 +- 7 files changed, 270 insertions(+), 61 deletions(-) diff --git a/arangod/Aql/AstNode.cpp b/arangod/Aql/AstNode.cpp index 8fbbe5e722..eba0764b20 100644 --- a/arangod/Aql/AstNode.cpp +++ b/arangod/Aql/AstNode.cpp @@ -76,8 +76,7 @@ void AstNode::toJson (TRI_json_t* json, // dump node type TRI_Insert3ArrayJson(zone, node, "type", TRI_CreateStringCopyJson(zone, typeString().c_str())); - if (type == NODE_TYPE_REFERENCE || - type == NODE_TYPE_COLLECTION || + if (type == NODE_TYPE_COLLECTION || type == NODE_TYPE_PARAMETER || type == NODE_TYPE_ATTRIBUTE_ACCESS || type == NODE_TYPE_FCALL || @@ -109,7 +108,8 @@ void AstNode::toJson (TRI_json_t* json, } } - if (type == NODE_TYPE_VARIABLE) { + if (type == NODE_TYPE_VARIABLE || + type == NODE_TYPE_REFERENCE) { auto variable = static_cast(getData()); TRI_Insert3ArrayJson(zone, node, "name", TRI_CreateStringCopyJson(zone, variable->name.c_str())); @@ -301,8 +301,6 @@ std::string AstNode::typeString () const { return "user function call"; case NODE_TYPE_RANGE: return "range"; - case NODE_TYPE_NOP: - return "no-op"; default: { } } diff --git a/arangod/Aql/AstNode.h b/arangod/Aql/AstNode.h index a3b864202a..67ebf372d5 100644 --- a/arangod/Aql/AstNode.h +++ b/arangod/Aql/AstNode.h @@ -123,8 +123,7 @@ namespace triagens { NODE_TYPE_PARAMETER, NODE_TYPE_FCALL, NODE_TYPE_FCALL_USER, - NODE_TYPE_RANGE, - NODE_TYPE_NOP, + NODE_TYPE_RANGE }; // ----------------------------------------------------------------------------- @@ -270,7 +269,15 @@ namespace triagens { } //////////////////////////////////////////////////////////////////////////////// -/// @brief return the int value of a node +/// @brief return the int value of a node, without asserting the node type +//////////////////////////////////////////////////////////////////////////////// + + inline int64_t getIntValue (bool) const { + return value.value._int; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the int value of a node, with asserting the node type //////////////////////////////////////////////////////////////////////////////// inline int64_t getIntValue () const { diff --git a/arangod/Aql/QueryAst.cpp b/arangod/Aql/QueryAst.cpp index e3e1954682..a173a62b22 100644 --- a/arangod/Aql/QueryAst.cpp +++ b/arangod/Aql/QueryAst.cpp @@ -65,8 +65,7 @@ QueryAst::QueryAst (Query* query, _collectionNames(), _root(nullptr), _writeCollection(nullptr), - _writeOptions(nullptr), - _nopNode() { + _writeOptions(nullptr) { TRI_ASSERT(_query != nullptr); TRI_ASSERT(_parser != nullptr); @@ -200,6 +199,7 @@ AstNode* QueryAst::createNodeLet (char const* variableName, AstNode* QueryAst::createNodeFilter (AstNode const* expression) { AstNode* node = createNode(NODE_TYPE_FILTER); + node->setIntValue(static_cast(FILTER_UNKNOWN)); node->addMember(expression); return node; @@ -403,7 +403,14 @@ AstNode* QueryAst::createNodeCollection (char const* name) { AstNode* QueryAst::createNodeReference (char const* name) { AstNode* node = createNode(NODE_TYPE_REFERENCE); - node->setStringValue(name); + + auto variable = _scopes.getVariable(name); + + if (variable == nullptr) { + THROW_ARANGO_EXCEPTION(TRI_ERROR_INTERNAL); + } + + node->setData(variable); return node; } @@ -666,20 +673,6 @@ AstNode* QueryAst::createNodeRange (AstNode const* start, return node; } -//////////////////////////////////////////////////////////////////////////////// -/// @brief create a no-op node -/// note: the same no-op node can be returned multiple times -//////////////////////////////////////////////////////////////////////////////// - -AstNode* QueryAst::createNodeNop () { - // the nop node is a singleton inside a query - if (_nopNode == nullptr) { - _nopNode = createNode(NODE_TYPE_NOP); - } - - return _nopNode; -} - //////////////////////////////////////////////////////////////////////////////// /// @brief injects bind parameters into the AST //////////////////////////////////////////////////////////////////////////////// @@ -793,11 +786,28 @@ void QueryAst::optimize () { if (node->type == NODE_TYPE_OPERATOR_TERNARY) { return optimizeTernaryOperator(node); } + + // reference to a variable + if (node->type == NODE_TYPE_REFERENCE) { + return optimizeReference(node); + } + + // range + if (node->type == NODE_TYPE_RANGE) { + return optimizeRange(node); + } + + // LET + if (node->type == NODE_TYPE_LET) { + return optimizeLet(node); + } return node; }; _root = traverse(_root, func, nullptr); + + optimizeRoot(); } // ----------------------------------------------------------------------------- @@ -853,12 +863,16 @@ AstNode* QueryAst::optimizeFilter (AstNode* node) { return node; } - if (operand->getBoolValue()) { - // FILTER is always true, optimize it away - return createNodeNop(); - } + TRI_ASSERT(node->getIntValue(true) == static_cast(FILTER_UNKNOWN)); - // TODO: optimize FILTERs that are always false + if (operand->getBoolValue()) { + // FILTER is always true + node->setIntValue(static_cast(FILTER_TRUE)); + } + else { + // FILTER is always false + node->setIntValue(static_cast(FILTER_FALSE)); + } return node; } @@ -1164,6 +1178,130 @@ AstNode* QueryAst::optimizeTernaryOperator (AstNode* node) { return falsePart; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief optimizes a reference to a variable +//////////////////////////////////////////////////////////////////////////////// + +AstNode* QueryAst::optimizeReference (AstNode* node) { + TRI_ASSERT(node != nullptr); + TRI_ASSERT(node->type == NODE_TYPE_REFERENCE); + + auto variable = static_cast(node->getData()); + + if (variable == nullptr) { + THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); + } + + if (variable->constValue() == nullptr) { + return node; + } + + return static_cast(variable->constValue()); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief optimizes the range operator +//////////////////////////////////////////////////////////////////////////////// + +AstNode* QueryAst::optimizeRange (AstNode* node) { + TRI_ASSERT(node != nullptr); + TRI_ASSERT(node->type == NODE_TYPE_RANGE); + TRI_ASSERT(node->numMembers() == 2); + + AstNode* lhs = node->getMember(0); + AstNode* rhs = node->getMember(1); + + if (lhs == nullptr || rhs == nullptr) { + THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); + } + + if (! lhs->isConstant() || ! rhs->isConstant()) { + return node; + } + + if (! lhs->isNumericValue() || ! rhs->isNumericValue()) { + _parser->registerError(TRI_ERROR_QUERY_INVALID_ARITHMETIC_VALUE); + return node; + } + + double const l = lhs->getDoubleValue(); + double const r = rhs->getDoubleValue(); + + if (l < r) { + return node; + } + + // x..y with x > y, replace with empty list + + return createNodeList(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief optimizes the LET statement +//////////////////////////////////////////////////////////////////////////////// + +AstNode* QueryAst::optimizeLet (AstNode* node) { + TRI_ASSERT(node != nullptr); + TRI_ASSERT(node->type == NODE_TYPE_LET); + TRI_ASSERT(node->numMembers() == 2); + + AstNode* variable = node->getMember(0); + AstNode* expression = node->getMember(1); + + if (expression->isConstant()) { + // if the expression assigned to the LET variable is constant, we'll store + // a pointer to the const value in the variable + // further optimizations can then use this pointer and optimize further, e.g. + // LET a = 1 LET b = a + 1, c = b + a can be optimized to LET a = 1 LET b = 2 LET c = 4 + auto v = static_cast(variable->getData()); + + TRI_ASSERT(v != nullptr); + v->constValue(static_cast(expression)); + } + + return node; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief optimizes the top-level statements +//////////////////////////////////////////////////////////////////////////////// + +void QueryAst::optimizeRoot() { + TRI_ASSERT(_root != nullptr); + TRI_ASSERT(_root->type == NODE_TYPE_ROOT); + + size_t const n = _root->numMembers(); + for (size_t i = 0; i < n; ++i) { + AstNode* member = _root->getMember(i); + + if (member == nullptr) { + continue; + } + + if (member->type == NODE_TYPE_FOR) { + // peek forward and check if the for contains a FILTER that is always false + bool isEmpty = false; + + for (size_t j = i + 1; j < n; ++j) { + AstNode* sub = _root->getMember(j); + + if (sub == nullptr) { + continue; + } + + if (sub->type == NODE_TYPE_FILTER) { + // TODO: found a FILTER that is always false + isEmpty = true; + } + + if (sub->type == NODE_TYPE_RETURN) { + } + } + } + + } +} + //////////////////////////////////////////////////////////////////////////////// /// @brief create an AST node from JSON //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/QueryAst.h b/arangod/Aql/QueryAst.h index 5b20189991..8928748b87 100644 --- a/arangod/Aql/QueryAst.h +++ b/arangod/Aql/QueryAst.h @@ -56,6 +56,12 @@ namespace triagens { class QueryAst { + enum FilterType { + FILTER_UNKNOWN, + FILTER_TRUE, + FILTER_FALSE + }; + // ----------------------------------------------------------------------------- // --SECTION-- constructors / destructors // ----------------------------------------------------------------------------- @@ -81,8 +87,6 @@ namespace triagens { public: - - //////////////////////////////////////////////////////////////////////////////// /// @brief return a copy of our own bind parameters //////////////////////////////////////////////////////////////////////////////// @@ -387,13 +391,6 @@ namespace triagens { AstNode* createNodeRange (AstNode const*, AstNode const*); -//////////////////////////////////////////////////////////////////////////////// -/// @brief create a no-op node -/// note: the same no-op node can be returned multiple times -//////////////////////////////////////////////////////////////////////////////// - - AstNode* createNodeNop (); - //////////////////////////////////////////////////////////////////////////////// /// @brief injects bind parameters into the AST //////////////////////////////////////////////////////////////////////////////// @@ -464,6 +461,30 @@ namespace triagens { AstNode* optimizeTernaryOperator (AstNode*); +//////////////////////////////////////////////////////////////////////////////// +/// @brief optimizes a reference to a variable +//////////////////////////////////////////////////////////////////////////////// + + AstNode* optimizeReference (AstNode*); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief optimizes the range operator +//////////////////////////////////////////////////////////////////////////////// + + AstNode* optimizeRange (AstNode*); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief optimizes the LET statement +//////////////////////////////////////////////////////////////////////////////// + + AstNode* optimizeLet (AstNode*); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief optimizes the top-level statements +//////////////////////////////////////////////////////////////////////////////// + + void optimizeRoot (); + //////////////////////////////////////////////////////////////////////////////// /// @brief create an AST node from JSON //////////////////////////////////////////////////////////////////////////////// @@ -556,12 +577,6 @@ namespace triagens { AstNode const* _writeOptions; -//////////////////////////////////////////////////////////////////////////////// -/// @brief a reusable no-operation node -//////////////////////////////////////////////////////////////////////////////// - - AstNode* _nopNode; - //////////////////////////////////////////////////////////////////////////////// /// @brief AQL function names //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/Scopes.cpp b/arangod/Aql/Scopes.cpp index 8a4a1581e1..a669d12842 100644 --- a/arangod/Aql/Scopes.cpp +++ b/arangod/Aql/Scopes.cpp @@ -99,19 +99,28 @@ Variable* Scope::addVariable (std::string const& name, return variable; } -//////////////////////////////////////////////////////////////////////////////// -/// @brief end a scope -//////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// /// @brief checks if a variable exists in the scope //////////////////////////////////////////////////////////////////////////////// bool Scope::existsVariable (char const* name) const { - TRI_ASSERT(name != nullptr); - auto it = _variables.find(std::string(name)); + return (getVariable(name) != nullptr); +} - return (it != _variables.end()); +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns a variable +//////////////////////////////////////////////////////////////////////////////// + +Variable* Scope::getVariable (char const* name) const { + std::string const varname(name); + + auto it = _variables.find(varname); + + if (it == _variables.end()) { + return nullptr; + } + + return (*it).second; } // ----------------------------------------------------------------------------- @@ -203,6 +212,7 @@ void Scopes::endNested () { Variable* Scopes::addVariable (char const* name, bool isUserDefined) { TRI_ASSERT(! _activeScopes.empty()); + TRI_ASSERT(name != nullptr); for (auto it = _activeScopes.rbegin(); it != _activeScopes.rend(); ++it) { auto scope = (*it); @@ -223,17 +233,25 @@ Variable* Scopes::addVariable (char const* name, //////////////////////////////////////////////////////////////////////////////// bool Scopes::existsVariable (char const* name) const { + return (getVariable(name) != nullptr); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return a variable +//////////////////////////////////////////////////////////////////////////////// + +Variable* Scopes::getVariable (char const* name) const { TRI_ASSERT(! _activeScopes.empty()); for (auto it = _activeScopes.rbegin(); it != _activeScopes.rend(); ++it) { - auto scope = (*it); + auto variable = (*it)->getVariable(name); - if (scope->existsVariable(name)) { - return true; + if (variable != nullptr) { + return variable; } } - return false; + return nullptr; } // ----------------------------------------------------------------------------- diff --git a/arangod/Aql/Scopes.h b/arangod/Aql/Scopes.h index a7b4381d7e..977f06c310 100644 --- a/arangod/Aql/Scopes.h +++ b/arangod/Aql/Scopes.h @@ -44,6 +44,7 @@ namespace triagens { size_t id, bool isUserDefined) : name(name), + value(nullptr), id(id), isUserDefined(isUserDefined) { } @@ -51,7 +52,29 @@ namespace triagens { ~Variable () { } +//////////////////////////////////////////////////////////////////////////////// +/// @brief registers a constant value for the variable +/// this constant value is used for constant propagation in optimizations +//////////////////////////////////////////////////////////////////////////////// + + void constValue (void* node) { + value = node; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns a constant value registered for this variable +//////////////////////////////////////////////////////////////////////////////// + + void* constValue () const { + return value; + } + +// ----------------------------------------------------------------------------- +// --SECTION-- public variables +// ----------------------------------------------------------------------------- + std::string const name; + void* value; size_t const id; bool const isUserDefined; }; @@ -125,6 +148,12 @@ namespace triagens { bool existsVariable (char const*) const; +//////////////////////////////////////////////////////////////////////////////// +/// @brief return a variable +//////////////////////////////////////////////////////////////////////////////// + + Variable* getVariable (char const*) const; + // ----------------------------------------------------------------------------- // --SECTION-- private variables // ----------------------------------------------------------------------------- @@ -209,6 +238,12 @@ namespace triagens { bool existsVariable (char const*) const; +//////////////////////////////////////////////////////////////////////////////// +/// @brief return a variable +//////////////////////////////////////////////////////////////////////////////// + + Variable* getVariable (char const*) const; + // ----------------------------------------------------------------------------- // --SECTION-- private variables // ----------------------------------------------------------------------------- diff --git a/arangod/Aql/grammar.y b/arangod/Aql/grammar.y index 511b5053cc..f0dcb5c4a3 100644 --- a/arangod/Aql/grammar.y +++ b/arangod/Aql/grammar.y @@ -443,11 +443,7 @@ expression: auto subQuery = parser->ast()->createNodeSubquery(tempName); parser->ast()->addOperation(subQuery); - auto nameNode = subQuery->getMember(0); - auto result = parser->ast()->createNodeReference(nameNode->getStringValue()); - - // return the result - $$ = result; + $$ = parser->ast()->createNodeReference(tempName); } | operator_unary { $$ = $1; @@ -683,6 +679,7 @@ reference: parser->pushStack($1); // create a temporary variable for the row iterator (will be popped by "expansion" rule") + // TODO: auto node = parser->ast()->createNodeReference(varname); // push the variable @@ -700,6 +697,7 @@ reference: auto nameNode = expand->getMember(1); // return a reference only + // TODO: $$ = parser->ast()->createNodeReference(nameNode->getStringValue()); } ;