| // Copyright 2017-2022 The Verible Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "verilog/analysis/flow_tree.h" | |
| #include <map> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/status/status.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "verilog/parser/verilog_token_enum.h" | |
| namespace verilog { | |
| // Adds edges within a conditonal block. | |
| // Such that the first edge represents the condition being true, | |
| // and the second edge represents the condition being false. | |
| absl::Status FlowTree::AddBlockEdges(const ConditionalBlock &block) { | |
| bool contains_elsif = !block.elsif_locations.empty(); | |
| bool contains_else = block.else_location != source_sequence_.end(); | |
| // Handling `ifdef/ifndef. | |
| // Assuming the condition is true. | |
| edges_[block.if_location].push_back(block.if_location + 1); | |
| // Assuming the condition is false. | |
| // Checking if there is an `elsif. | |
| if (contains_elsif) { | |
| // Add edge to the first `elsif in the block. | |
| edges_[block.if_location].push_back(block.elsif_locations[0]); | |
| } else if (contains_else) { | |
| // Checking if there is an `else. | |
| edges_[block.if_location].push_back(block.else_location); | |
| } else { | |
| // `endif exists. | |
| edges_[block.if_location].push_back(block.endif_location); | |
| } | |
| // Handling `elsif. | |
| if (contains_elsif) { | |
| for (auto iter = block.elsif_locations.begin(); | |
| iter != block.elsif_locations.end(); iter++) { | |
| // Assuming the condition is true. | |
| edges_[*iter].push_back((*iter) + 1); | |
| // Assuming the condition is false. | |
| if (iter + 1 != block.elsif_locations.end()) | |
| edges_[*iter].push_back(*(iter + 1)); | |
| else if (contains_else) | |
| edges_[*iter].push_back(block.else_location); | |
| else | |
| edges_[*iter].push_back(block.endif_location); | |
| } | |
| } | |
| // Handling `else. | |
| if (contains_else) { | |
| edges_[block.else_location].push_back(block.else_location + 1); | |
| } | |
| // For edges that are generated assuming the conditons are true, | |
| // We need to add an edge from the end of the condition group of lines to | |
| // `endif, e.g. `ifdef | |
| // <line1> | |
| // <line2> | |
| // ... | |
| // <line_final> | |
| // `else | |
| // <group_of_lines> | |
| // `endif | |
| // Edge to be added: from <line_final> to `endif. | |
| edges_[block.endif_location - 1].push_back(block.endif_location); | |
| if (contains_elsif) { | |
| for (auto iter : block.elsif_locations) | |
| edges_[iter - 1].push_back(block.endif_location); | |
| } | |
| if (contains_else) { | |
| edges_[block.else_location - 1].push_back(block.endif_location); | |
| } | |
| // Connecting `endif to the next token directly (if not EOF). | |
| auto next_iter = block.endif_location + 1; | |
| if (next_iter != source_sequence_.end() && | |
| next_iter->token_enum() != PP_else && | |
| next_iter->token_enum() != PP_elsif && | |
| next_iter->token_enum() != PP_endif) { | |
| edges_[block.endif_location].push_back(next_iter); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| // Checks if the iterator is pointing to a conditional directive. | |
| bool FlowTree::IsConditional(TokenSequenceConstIterator iterator) { | |
| auto current_node = iterator->token_enum(); | |
| return current_node == PP_ifndef || current_node == PP_ifdef || | |
| current_node == PP_elsif || current_node == PP_else || | |
| current_node == PP_endif; | |
| } | |
| // Checks if after the conditional_iterator (`ifdef/`ifndef... ) there exists | |
| // a macro identifier. | |
| absl::Status FlowTree::MacroFollows( | |
| TokenSequenceConstIterator conditional_iterator) { | |
| if (conditional_iterator->token_enum() != PP_ifdef && | |
| conditional_iterator->token_enum() != PP_ifndef && | |
| conditional_iterator->token_enum() != PP_elsif) { | |
| return absl::InvalidArgumentError("Error macro name can't be extracted."); | |
| } | |
| auto macro_iterator = conditional_iterator + 1; | |
| if (macro_iterator->token_enum() != PP_Identifier) | |
| return absl::InvalidArgumentError("Expected identifier for macro name."); | |
| else | |
| return absl::OkStatus(); | |
| } | |
| // Adds a conditional macro to conditional_macros_ if not added before, | |
| // And gives it a new ID, then saves the ID in conditional_macro_id_ map. | |
| absl::Status FlowTree::AddMacroOfConditional( | |
| TokenSequenceConstIterator conditional_iterator) { | |
| auto status = MacroFollows(conditional_iterator); | |
| if (!status.ok()) { | |
| return absl::InvalidArgumentError( | |
| "Error no macro follows the conditional directive."); | |
| } | |
| auto macro_iterator = conditional_iterator + 1; | |
| auto macro_identifier = macro_iterator->text(); | |
| if (conditional_macro_id_.find(macro_identifier) == | |
| conditional_macro_id_.end()) { | |
| conditional_macro_id_[macro_identifier] = conditional_macros_counter_; | |
| conditional_macros_.push_back(macro_iterator); | |
| conditional_macros_counter_++; | |
| } | |
| return absl::OkStatus(); | |
| } | |
| // Gets the conditonal macro ID from the conditional_macro_id_. | |
| // Note: conditional_iterator is pointing to the conditional. | |
| int FlowTree::GetMacroIDOfConditional( | |
| TokenSequenceConstIterator conditional_iterator) { | |
| auto status = MacroFollows(conditional_iterator); | |
| if (!status.ok()) { | |
| // TODO(karimtera): add a better error handling. | |
| return -1; | |
| } | |
| auto macro_iterator = conditional_iterator + 1; | |
| auto macro_identifier = macro_iterator->text(); | |
| // It is always assumed that the macro already exists in the map. | |
| return conditional_macro_id_[macro_identifier]; | |
| } | |
| // An API that provides a callback function to receive variants. | |
| absl::Status FlowTree::GenerateVariants(const VariantReceiver &receiver) { | |
| auto status = GenerateControlFlowTree(); | |
| if (!status.ok()) { | |
| return status; | |
| } | |
| return DepthFirstSearch(receiver, source_sequence_.begin()); | |
| } | |
| // Constructs the control flow tree, which determines the edge from each node | |
| // (token index) to the next possible childs, And save edge_from_iterator in | |
| // edges_. | |
| absl::Status FlowTree::GenerateControlFlowTree() { | |
| // Adding edges for if blocks. | |
| const TokenSequenceConstIterator non_location = source_sequence_.end(); | |
| for (TokenSequenceConstIterator iter = source_sequence_.begin(); | |
| iter != source_sequence_.end(); iter++) { | |
| int current_token_enum = iter->token_enum(); | |
| if (IsConditional(iter)) { | |
| switch (current_token_enum) { | |
| case PP_ifdef: | |
| case PP_ifndef: { | |
| if_blocks_.emplace_back(iter, non_location); | |
| auto status = AddMacroOfConditional(iter); | |
| if (!status.ok()) { | |
| return absl::InvalidArgumentError( | |
| "ERROR: couldn't give a macro an ID."); | |
| } | |
| break; | |
| } | |
| case PP_elsif: { | |
| if (if_blocks_.empty()) { | |
| return absl::InvalidArgumentError("ERROR: Unmatched `elsif."); | |
| } | |
| if_blocks_.back().elsif_locations.push_back(iter); | |
| auto status = AddMacroOfConditional(iter); | |
| if (!status.ok()) { | |
| return absl::InvalidArgumentError( | |
| "ERROR: couldn't give a macro an ID."); | |
| } | |
| break; | |
| } | |
| case PP_else: { | |
| if (if_blocks_.empty()) { | |
| return absl::InvalidArgumentError("ERROR: Unmatched `else."); | |
| } | |
| if_blocks_.back().else_location = iter; | |
| break; | |
| } | |
| case PP_endif: { | |
| if (if_blocks_.empty()) { | |
| return absl::InvalidArgumentError("ERROR: Unmatched `endif."); | |
| } | |
| if_blocks_.back().endif_location = iter; | |
| auto status = AddBlockEdges(if_blocks_.back()); | |
| if (!status.ok()) return status; | |
| // TODO(karimtera): add an error message. | |
| if_blocks_.pop_back(); | |
| break; | |
| } | |
| } | |
| } else { | |
| // Only add normal edges if the next token is not `else/`elsif/`endif. | |
| auto next_iter = iter + 1; | |
| if (next_iter != source_sequence_.end() && | |
| next_iter->token_enum() != PP_else && | |
| next_iter->token_enum() != PP_elsif && | |
| next_iter->token_enum() != PP_endif) { | |
| edges_[iter].push_back(next_iter); | |
| } | |
| } | |
| } | |
| // Checks for uncompleted conditionals. | |
| if (!if_blocks_.empty()) | |
| return absl::InvalidArgumentError( | |
| "ERROR: Uncompleted conditional is found."); | |
| return absl::OkStatus(); | |
| } | |
| // Traveses the control flow tree in a depth first manner, appending the visited | |
| // tokens to current_variant_, then provide the completed variant to the user | |
| // using a callback function (VariantReceiver). | |
| absl::Status FlowTree::DepthFirstSearch( | |
| const VariantReceiver &receiver, TokenSequenceConstIterator current_node) { | |
| if (!wants_more_) return absl::OkStatus(); | |
| // Skips directives so that current_variant_ doesn't contain any. | |
| if (current_node->token_enum() != PP_Identifier && | |
| current_node->token_enum() != PP_ifndef && | |
| current_node->token_enum() != PP_ifdef && | |
| current_node->token_enum() != PP_define && | |
| current_node->token_enum() != PP_define_body && | |
| current_node->token_enum() != PP_elsif && | |
| current_node->token_enum() != PP_else && | |
| current_node->token_enum() != PP_endif) { | |
| current_variant_.sequence.push_back(*current_node); | |
| } | |
| // Checks if the current token is a `ifdef/`ifndef/`elsif. | |
| if (current_node->token_enum() == PP_ifdef || | |
| current_node->token_enum() == PP_ifndef || | |
| current_node->token_enum() == PP_elsif) { | |
| int macro_id = GetMacroIDOfConditional(current_node); | |
| bool negated = (current_node->token_enum() == PP_ifndef); | |
| // Checks if this macro is already visited (either defined/undefined). | |
| if (current_variant_.visited.test(macro_id)) { | |
| bool assume_condition_is_true = | |
| (negated ^ current_variant_.macros_mask.test(macro_id)); | |
| if (auto status = DepthFirstSearch( | |
| receiver, edges_[current_node][!assume_condition_is_true]); | |
| !status.ok()) { | |
| std::cerr << "ERROR: DepthFirstSearch fails."; | |
| return status; | |
| } | |
| } else { | |
| current_variant_.visited.flip(macro_id); | |
| // This macro wans't visited before, then we can check both edges. | |
| // Assume the condition is true. | |
| if (negated) | |
| current_variant_.macros_mask.reset(macro_id); | |
| else | |
| current_variant_.macros_mask.set(macro_id); | |
| if (auto status = DepthFirstSearch(receiver, edges_[current_node][0]); | |
| !status.ok()) { | |
| std::cerr << "ERROR: DepthFirstSearch fails."; | |
| return status; | |
| } | |
| // Assume the condition is false. | |
| if (!negated) | |
| current_variant_.macros_mask.reset(macro_id); | |
| else | |
| current_variant_.macros_mask.set(macro_id); | |
| if (auto status = DepthFirstSearch(receiver, edges_[current_node][1]); | |
| !status.ok()) { | |
| std::cerr << "ERROR: DepthFirstSearch fails."; | |
| return status; | |
| } | |
| // Undo the change to allow for backtracking. | |
| current_variant_.visited.flip(macro_id); | |
| } | |
| } else { | |
| // Do recursive search through every possible edge. | |
| // Expected to be only one edge in this case. | |
| for (auto next_node : edges_[current_node]) { | |
| if (auto status = FlowTree::DepthFirstSearch(receiver, next_node); | |
| !status.ok()) { | |
| std::cerr << "ERROR: DepthFirstSearch fails\n"; | |
| return status; | |
| } | |
| } | |
| } | |
| // If the current node is the last one, push the completed current_variant_ | |
| // then it is ready to be sent. | |
| if (current_node == source_sequence_.end() - 1) { | |
| wants_more_ &= receiver(current_variant_); | |
| } | |
| if (current_node->token_enum() != PP_Identifier && | |
| current_node->token_enum() != PP_ifndef && | |
| current_node->token_enum() != PP_ifdef && | |
| current_node->token_enum() != PP_define && | |
| current_node->token_enum() != PP_define_body && | |
| current_node->token_enum() != PP_elsif && | |
| current_node->token_enum() != PP_else && | |
| current_node->token_enum() != PP_endif) { | |
| // Remove tokens to back track into other variants. | |
| current_variant_.sequence.pop_back(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| } // namespace verilog |