blob: 02a2c4017468b3b011811437d23aa24a5ead8120 [file] [log] [blame]
// 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