forked from chipsalliance/verible
-
Notifications
You must be signed in to change notification settings - Fork 0
/
flow_tree.cc
347 lines (318 loc) · 12.4 KB
/
flow_tree.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// 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 <vector>
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "common/util/logging.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.");
}
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;
}
default:
LOG(FATAL) << "IsConditional() not catching " << current_token_enum;
}
} 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()) {
LOG(ERROR) << "ERROR: DepthFirstSearch fails. " << status;
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()) {
LOG(ERROR) << "ERROR: DepthFirstSearch fails. " << status;
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()) {
LOG(ERROR) << "ERROR: DepthFirstSearch fails. " << status;
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()) {
LOG(ERROR) << "ERROR: DepthFirstSearch fails. " << status;
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