Skip to content

Commit 7e6dda3

Browse files
authored
Merge pull request #1372 from karimtera/pp_generate_variants_mode
[VeriblePreProcessor][1]: multiple-cu and generate-variants modes
2 parents 4faba50 + 37cc052 commit 7e6dda3

File tree

7 files changed

+1287
-13
lines changed

7 files changed

+1287
-13
lines changed

verilog/analysis/BUILD

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,34 @@ cc_library(
7474
],
7575
)
7676

77+
cc_library(
78+
name = "flow_tree",
79+
srcs = ["flow_tree.cc"],
80+
hdrs = ["flow_tree.h"],
81+
deps = [
82+
"//common/lexer:token_stream_adapter",
83+
"//verilog/parser:verilog_token_enum",
84+
"@com_google_absl//absl/strings",
85+
"@com_google_absl//absl/status",
86+
],
87+
)
88+
89+
cc_test(
90+
name = "flow_tree_test",
91+
srcs = ["flow_tree_test.cc"],
92+
deps = [
93+
":flow_tree",
94+
"//common/util:logging",
95+
"//common/lexer:token_stream_adapter",
96+
"//verilog/parser:verilog_lexer",
97+
"//verilog/parser:verilog_token_enum",
98+
"@com_google_absl//absl/memory",
99+
"@com_google_absl//absl/strings",
100+
"@com_google_googletest//:gtest_main",
101+
],
102+
)
103+
104+
77105
cc_library(
78106
name = "lint_rule_registry",
79107
srcs = ["lint_rule_registry.cc"],

verilog/analysis/flow_tree.cc

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
// Copyright 2017-2022 The Verible Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing,
10+
// software distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "verilog/analysis/flow_tree.h"
16+
17+
#include <map>
18+
#include <string>
19+
#include <vector>
20+
21+
#include "absl/status/status.h"
22+
#include "absl/strings/str_cat.h"
23+
#include "absl/strings/string_view.h"
24+
#include "common/lexer/token_stream_adapter.h"
25+
#include "verilog/parser/verilog_token_enum.h"
26+
27+
namespace verilog {
28+
29+
// Adds edges within a conditonal block.
30+
// Such that the first edge represents the condition being true,
31+
// and the second edge represents the condition being false.
32+
absl::Status FlowTree::AddBlockEdges(const ConditionalBlock &block) {
33+
bool contains_elsif = !block.elsif_locations.empty();
34+
bool contains_else = block.else_location != source_sequence_.end();
35+
36+
// Handling `ifdef/ifndef.
37+
38+
// Assuming the condition is true.
39+
edges_[block.if_location].push_back(block.if_location + 1);
40+
41+
// Assuming the condition is false.
42+
// Checking if there is an `elsif.
43+
if (contains_elsif) {
44+
// Add edge to the first `elsif in the block.
45+
edges_[block.if_location].push_back(block.elsif_locations[0]);
46+
} else if (contains_else) {
47+
// Checking if there is an `else.
48+
edges_[block.if_location].push_back(block.else_location);
49+
} else {
50+
// `endif exists.
51+
edges_[block.if_location].push_back(block.endif_location);
52+
}
53+
54+
// Handling `elsif.
55+
if (contains_elsif) {
56+
for (auto iter = block.elsif_locations.begin();
57+
iter != block.elsif_locations.end(); iter++) {
58+
// Assuming the condition is true.
59+
edges_[*iter].push_back((*iter) + 1);
60+
61+
// Assuming the condition is false.
62+
if (iter + 1 != block.elsif_locations.end())
63+
edges_[*iter].push_back(*(iter + 1));
64+
else if (contains_else)
65+
edges_[*iter].push_back(block.else_location);
66+
else
67+
edges_[*iter].push_back(block.endif_location);
68+
}
69+
}
70+
71+
// Handling `else.
72+
if (contains_else) {
73+
edges_[block.else_location].push_back(block.else_location + 1);
74+
}
75+
76+
// For edges that are generated assuming the conditons are true,
77+
// We need to add an edge from the end of the condition group of lines to
78+
// `endif, e.g. `ifdef
79+
// <line1>
80+
// <line2>
81+
// ...
82+
// <line_final>
83+
// `else
84+
// <group_of_lines>
85+
// `endif
86+
// Edge to be added: from <line_final> to `endif.
87+
edges_[block.endif_location - 1].push_back(block.endif_location);
88+
if (contains_elsif) {
89+
for (auto iter : block.elsif_locations)
90+
edges_[iter - 1].push_back(block.endif_location);
91+
}
92+
if (contains_else) {
93+
edges_[block.else_location - 1].push_back(block.endif_location);
94+
}
95+
96+
// Connecting `endif to the next token directly (if not EOF).
97+
auto next_iter = block.endif_location + 1;
98+
if (next_iter != source_sequence_.end() &&
99+
next_iter->token_enum() != PP_else &&
100+
next_iter->token_enum() != PP_elsif &&
101+
next_iter->token_enum() != PP_endif) {
102+
edges_[block.endif_location].push_back(next_iter);
103+
}
104+
105+
return absl::OkStatus();
106+
}
107+
108+
// Checks if the iterator is pointing to a conditional directive.
109+
bool FlowTree::IsConditional(TokenSequenceConstIterator iterator) {
110+
auto current_node = iterator->token_enum();
111+
return current_node == PP_ifndef || current_node == PP_ifdef ||
112+
current_node == PP_elsif || current_node == PP_else ||
113+
current_node == PP_endif;
114+
}
115+
116+
// Checks if after the conditional_iterator (`ifdef/`ifndef... ) there exists
117+
// a macro identifier.
118+
absl::Status FlowTree::MacroFollows(
119+
TokenSequenceConstIterator conditional_iterator) {
120+
if (conditional_iterator->token_enum() != PP_ifdef &&
121+
conditional_iterator->token_enum() != PP_ifndef &&
122+
conditional_iterator->token_enum() != PP_elsif) {
123+
return absl::InvalidArgumentError("Error macro name can't be extracted.");
124+
}
125+
auto macro_iterator = conditional_iterator + 1;
126+
if (macro_iterator->token_enum() != PP_Identifier)
127+
return absl::InvalidArgumentError("Expected identifier for macro name.");
128+
else
129+
return absl::OkStatus();
130+
}
131+
132+
// Adds a conditional macro to conditional_macros_ if not added before,
133+
// And gives it a new ID, then saves the ID in conditional_macro_id_ map.
134+
absl::Status FlowTree::AddMacroOfConditional(
135+
TokenSequenceConstIterator conditional_iterator) {
136+
auto status = MacroFollows(conditional_iterator);
137+
if (!status.ok()) {
138+
return absl::InvalidArgumentError(
139+
"Error no macro follows the conditional directive.");
140+
}
141+
auto macro_iterator = conditional_iterator + 1;
142+
auto macro_identifier = macro_iterator->text();
143+
if (conditional_macro_id_.find(macro_identifier) ==
144+
conditional_macro_id_.end()) {
145+
conditional_macro_id_[macro_identifier] = conditional_macros_counter_;
146+
conditional_macros_.push_back(macro_iterator);
147+
conditional_macros_counter_++;
148+
}
149+
return absl::OkStatus();
150+
}
151+
152+
// Gets the conditonal macro ID from the conditional_macro_id_.
153+
// Note: conditional_iterator is pointing to the conditional.
154+
int FlowTree::GetMacroIDOfConditional(
155+
TokenSequenceConstIterator conditional_iterator) {
156+
auto status = MacroFollows(conditional_iterator);
157+
if (!status.ok()) {
158+
// TODO(karimtera): add a better error handling.
159+
return -1;
160+
}
161+
auto macro_iterator = conditional_iterator + 1;
162+
auto macro_identifier = macro_iterator->text();
163+
// It is always assumed that the macro already exists in the map.
164+
return conditional_macro_id_[macro_identifier];
165+
}
166+
167+
// An API that provides a callback function to receive variants.
168+
absl::Status FlowTree::GenerateVariants(const VariantReceiver &receiver) {
169+
auto status = GenerateControlFlowTree();
170+
if (!status.ok()) {
171+
return status;
172+
}
173+
return DepthFirstSearch(receiver, source_sequence_.begin());
174+
}
175+
176+
// Constructs the control flow tree, which determines the edge from each node
177+
// (token index) to the next possible childs, And save edge_from_iterator in
178+
// edges_.
179+
absl::Status FlowTree::GenerateControlFlowTree() {
180+
// Adding edges for if blocks.
181+
int current_token_enum = 0;
182+
ConditionalBlock empty_block;
183+
empty_block.if_location = source_sequence_.end();
184+
empty_block.else_location = source_sequence_.end();
185+
empty_block.endif_location = source_sequence_.end();
186+
187+
for (TokenSequenceConstIterator iter = source_sequence_.begin();
188+
iter != source_sequence_.end(); iter++) {
189+
current_token_enum = iter->token_enum();
190+
191+
if (IsConditional(iter)) {
192+
switch (current_token_enum) {
193+
case PP_ifdef: {
194+
if_blocks_.push_back(empty_block);
195+
if_blocks_.back().if_location = iter;
196+
if_blocks_.back().positive_condition = 1;
197+
auto status = AddMacroOfConditional(iter);
198+
if (!status.ok()) {
199+
return absl::InvalidArgumentError(
200+
"ERROR: couldn't give a macro an ID.");
201+
}
202+
break;
203+
}
204+
case PP_ifndef: {
205+
if_blocks_.push_back(empty_block);
206+
if_blocks_.back().if_location = iter;
207+
if_blocks_.back().positive_condition = 0;
208+
auto status = AddMacroOfConditional(iter);
209+
if (!status.ok()) {
210+
return absl::InvalidArgumentError(
211+
"ERROR: couldn't give a macro an ID.");
212+
}
213+
break;
214+
}
215+
case PP_elsif: {
216+
if (if_blocks_.empty()) {
217+
return absl::InvalidArgumentError("ERROR: Unmatched `elsif.");
218+
}
219+
if_blocks_.back().elsif_locations.push_back(iter);
220+
auto status = AddMacroOfConditional(iter);
221+
if (!status.ok()) {
222+
return absl::InvalidArgumentError(
223+
"ERROR: couldn't give a macro an ID.");
224+
}
225+
break;
226+
}
227+
case PP_else: {
228+
if (if_blocks_.empty()) {
229+
return absl::InvalidArgumentError("ERROR: Unmatched `else.");
230+
}
231+
if_blocks_.back().else_location = iter;
232+
break;
233+
}
234+
case PP_endif: {
235+
if (if_blocks_.empty()) {
236+
return absl::InvalidArgumentError("ERROR: Unmatched `endif.");
237+
}
238+
if_blocks_.back().endif_location = iter;
239+
auto status = AddBlockEdges(if_blocks_.back());
240+
if (!status.ok()) return status;
241+
// TODO(karimtera): add an error message.
242+
if_blocks_.pop_back();
243+
break;
244+
}
245+
}
246+
247+
} else {
248+
// Only add normal edges if the next token is not `else/`elsif/`endif.
249+
auto next_iter = iter + 1;
250+
if (next_iter != source_sequence_.end() &&
251+
next_iter->token_enum() != PP_else &&
252+
next_iter->token_enum() != PP_elsif &&
253+
next_iter->token_enum() != PP_endif) {
254+
edges_[iter].push_back(next_iter);
255+
}
256+
}
257+
}
258+
259+
// Checks for uncompleted conditionals.
260+
if (!if_blocks_.empty())
261+
return absl::InvalidArgumentError(
262+
"ERROR: Uncompleted conditional is found.");
263+
264+
return absl::OkStatus();
265+
}
266+
267+
// Traveses the control flow tree in a depth first manner, appending the visited
268+
// tokens to current_variant_, then provide the completed variant to the user
269+
// using a callback function (VariantReceiver).
270+
absl::Status FlowTree::DepthFirstSearch(
271+
const VariantReceiver &receiver, TokenSequenceConstIterator current_node) {
272+
if (!wants_more_) return absl::OkStatus();
273+
274+
// Skips directives so that current_variant_ doesn't contain any.
275+
if (current_node->token_enum() != PP_Identifier &&
276+
current_node->token_enum() != PP_ifndef &&
277+
current_node->token_enum() != PP_ifdef &&
278+
current_node->token_enum() != PP_define &&
279+
current_node->token_enum() != PP_define_body &&
280+
current_node->token_enum() != PP_elsif &&
281+
current_node->token_enum() != PP_else &&
282+
current_node->token_enum() != PP_endif) {
283+
current_variant_.sequence.push_back(*current_node);
284+
}
285+
286+
// Checks if the current token is a `ifdef/`ifndef/`elsif.
287+
if (current_node->token_enum() == PP_ifdef ||
288+
current_node->token_enum() == PP_ifndef ||
289+
current_node->token_enum() == PP_elsif) {
290+
int macro_id = GetMacroIDOfConditional(current_node);
291+
bool negated = (current_node->token_enum() == PP_ifndef);
292+
// Checks if this macro is already visited (either defined/undefined).
293+
if (current_variant_.visited.test(macro_id)) {
294+
bool assume_condition_is_true =
295+
(negated ^ current_variant_.macros_mask.test(macro_id));
296+
if (auto status = DepthFirstSearch(
297+
receiver, edges_[current_node][!assume_condition_is_true]);
298+
!status.ok()) {
299+
std::cerr << "ERROR: DepthFirstSearch fails.";
300+
return status;
301+
}
302+
} else {
303+
current_variant_.visited.flip(macro_id);
304+
// This macro wans't visited before, then we can check both edges.
305+
// Assume the condition is true.
306+
if (negated)
307+
current_variant_.macros_mask.reset(macro_id);
308+
else
309+
current_variant_.macros_mask.set(macro_id);
310+
if (auto status = DepthFirstSearch(receiver, edges_[current_node][0]);
311+
!status.ok()) {
312+
std::cerr << "ERROR: DepthFirstSearch fails.";
313+
return status;
314+
}
315+
316+
// Assume the condition is false.
317+
if (!negated)
318+
current_variant_.macros_mask.reset(macro_id);
319+
else
320+
current_variant_.macros_mask.set(macro_id);
321+
if (auto status = DepthFirstSearch(receiver, edges_[current_node][1]);
322+
!status.ok()) {
323+
std::cerr << "ERROR: DepthFirstSearch fails.";
324+
return status;
325+
}
326+
// Undo the change to allow for backtracking.
327+
current_variant_.visited.flip(macro_id);
328+
}
329+
} else {
330+
// Do recursive search through every possible edge.
331+
// Expected to be only one edge in this case.
332+
for (auto next_node : edges_[current_node]) {
333+
if (auto status = FlowTree::DepthFirstSearch(receiver, next_node);
334+
!status.ok()) {
335+
std::cerr << "ERROR: DepthFirstSearch fails\n";
336+
return status;
337+
}
338+
}
339+
}
340+
// If the current node is the last one, push the completed current_variant_
341+
// then it is ready to be sent.
342+
if (current_node == source_sequence_.end() - 1) {
343+
wants_more_ &= receiver(current_variant_);
344+
}
345+
if (current_node->token_enum() != PP_Identifier &&
346+
current_node->token_enum() != PP_ifndef &&
347+
current_node->token_enum() != PP_ifdef &&
348+
current_node->token_enum() != PP_define &&
349+
current_node->token_enum() != PP_define_body &&
350+
current_node->token_enum() != PP_elsif &&
351+
current_node->token_enum() != PP_else &&
352+
current_node->token_enum() != PP_endif) {
353+
// Remove tokens to back track into other variants.
354+
current_variant_.sequence.pop_back();
355+
}
356+
return absl::OkStatus();
357+
}
358+
359+
} // namespace verilog

0 commit comments

Comments
 (0)