1 #include "tree_merger.hh"
2 #include "../execution.hh"
3 #include "../tree/structure.hh"
4 #include "../core.hh"
5 
6 #include "../utils/utils.hh"
7 #include "../utils/tree_utils.hh"
8 
9 #include <QStack>
10 
11 namespace cpprofiler
12 {
13 namespace analysis
14 {
15 
16 using namespace tree;
17 
TreeMerger(const Execution & ex_l_,const Execution & ex_r_,std::shared_ptr<tree::NodeTree> tree,std::shared_ptr<analysis::MergeResult> res,std::shared_ptr<std::vector<OriginalLoc>> orig_locs)18 TreeMerger::TreeMerger(const Execution &ex_l_,
19                        const Execution &ex_r_,
20                        std::shared_ptr<tree::NodeTree> tree,
21                        std::shared_ptr<analysis::MergeResult> res,
22                        std::shared_ptr<std::vector<OriginalLoc>> orig_locs)
23     : ex_l(ex_l_), ex_r(ex_r_),
24       tree_l(ex_l.tree()),
25       tree_r(ex_r.tree()),
26       res_tree(tree),
27       merge_result(res),
28       orig_locs_(orig_locs)
29 {
30 
31     connect(this, &QThread::finished, this, &QObject::deleteLater);
32 }
33 
~TreeMerger()34 TreeMerger::~TreeMerger()
35 {
36 }
37 
find_and_replace_all(std::string & str,std::string substr_old,std::string substr_new)38 static void find_and_replace_all(std::string &str, std::string substr_old, std::string substr_new)
39 {
40     auto pos = str.find(substr_old);
41     while (pos != std::string::npos)
42     {
43         str.replace(pos, std::string(substr_old).length(), substr_new);
44         pos = str.find(substr_old);
45     }
46 }
47 
labelsEqual(std::string lhs,std::string rhs)48 static bool labelsEqual(std::string lhs, std::string rhs)
49 {
50     /// NOTE(maxim): removes whitespaces before comparing;
51     /// this will be necessary as long as Chuffed and Gecode don't agree
52     /// on whether to put whitespaces around operators (Gecode uses ' '
53     /// for parsing logbrancher while Chuffed uses them as a delimiter
54     /// between literals)
55 
56     if (lhs.substr(0, 3) == "[i]" || lhs.substr(0, 3) == "[f]")
57     {
58         lhs = lhs.substr(3);
59     }
60 
61     if (rhs.substr(0, 3) == "[i]" || rhs.substr(0, 3) == "[f]")
62     {
63         rhs = rhs.substr(3);
64     }
65 
66     lhs.erase(remove_if(lhs.begin(), lhs.end(), isspace), lhs.end());
67     rhs.erase(remove_if(rhs.begin(), rhs.end(), isspace), rhs.end());
68 
69     find_and_replace_all(lhs, "==", "=");
70     find_and_replace_all(rhs, "==", "=");
71 
72     // qDebug() << "comparing: " << lhs.c_str() << " " << rhs.c_str();
73 
74     if (lhs.compare(rhs) != 0)
75     {
76         return false;
77     }
78 
79     return true;
80 }
81 
compareNodes(NodeID n1,const NodeTree & nt1,NodeID n2,const NodeTree & nt2,bool with_labels)82 static bool compareNodes(NodeID n1, const NodeTree &nt1,
83                          NodeID n2, const NodeTree &nt2,
84                          bool with_labels)
85 {
86 
87     if (n1 == NodeID::NoNode || n2 == NodeID::NoNode)
88         return false;
89 
90     if (nt1.getStatus(n1) != nt2.getStatus(n2))
91         return false;
92 
93     if (with_labels)
94     {
95         const auto &label1 = nt1.getLabel(n1);
96         const auto &label2 = nt2.getLabel(n2);
97         if (!labelsEqual(label1, label2))
98             return false;
99     }
100 
101     return true;
102 }
103 
104 /// Copy the subtree rooted at nid_s of nt_s as a subtree rooted at nid in nt
copy_tree_into(NodeTree & nt,NodeID nid,const NodeTree & nt_s,NodeID nid_s)105 static void copy_tree_into(NodeTree &nt, NodeID nid, const NodeTree &nt_s, NodeID nid_s)
106 {
107 
108     QStack<NodeID> stack;
109     QStack<NodeID> stack_s;
110     stack.push(nid);
111     stack_s.push(nid_s);
112 
113     while (stack_s.size() > 0)
114     {
115         auto node_s = stack_s.pop();
116         auto node = stack.pop();
117 
118         auto kids = nt_s.childrenCount(node_s);
119         auto status = nt_s.getStatus(node_s);
120 
121         /// TODO: make sure only the reference to a label is stored
122         auto label = nt_s.getLabel(node_s);
123 
124         nt.promoteNode(node, kids, status, label);
125 
126         for (auto alt = 0; alt < kids; ++alt)
127         {
128             stack.push(nt.getChild(node, alt));
129             stack_s.push(nt_s.getChild(node_s, alt));
130         }
131     }
132 }
133 
create_pentagon(NodeTree & nt,NodeID nid,const NodeTree & nt_l,NodeID nid_l,const NodeTree & nt_r,NodeID nid_r)134 void create_pentagon(NodeTree &nt, NodeID nid,
135                      const NodeTree &nt_l, NodeID nid_l,
136                      const NodeTree &nt_r, NodeID nid_r)
137 {
138 
139     nt.promoteNode(nid, 2, NodeStatus::MERGED);
140 
141     /// copy the subtree of nt_l into target_l
142     if (nid_l != NodeID::NoNode)
143     {
144         auto target_l = nt.getChild(nid, 0);
145         copy_tree_into(nt, target_l, nt_l, nid_l);
146     }
147 
148     /// copy the subtree of nt_r into target_r
149     if (nid_r != NodeID::NoNode)
150     {
151         auto target_r = nt.getChild(nid, 1);
152         copy_tree_into(nt, target_r, nt_r, nid_r);
153     }
154 }
155 
run()156 void TreeMerger::run()
157 {
158 
159     print("Merging: running...");
160 
161     /// It is quite unlikely, but this can dead-lock (needs consistent ordering)
162     utils::MutexLocker locker_l(&tree_l.treeMutex());
163     utils::MutexLocker locker_r(&tree_r.treeMutex());
164     utils::MutexLocker locker_res(&res_tree->treeMutex());
165 
166     QStack<NodeID> stack_l, stack_r, stack;
167 
168     auto root_l = tree_l.getRoot();
169     auto root_r = tree_r.getRoot();
170 
171     stack_l.push(root_l);
172     stack_r.push(root_r);
173 
174     auto root = res_tree->createRoot(0);
175 
176     stack.push(root);
177 
178     while (stack_l.size() > 0)
179     {
180 
181         auto node_l = stack_l.pop();
182         auto node_r = stack_r.pop();
183         auto target = stack.pop();
184 
185         bool equal = compareNodes(node_l, tree_l, node_r, tree_r, false);
186 
187         if (equal)
188         {
189 
190             const auto kids_l = tree_l.childrenCount(node_l);
191             const auto kids_r = tree_r.childrenCount(node_r);
192 
193             const auto min_kids = std::min(kids_l, kids_r);
194             const auto max_kids = std::max(kids_l, kids_r);
195 
196             { /// The merged tree will always have the number of children of the 'larger' tree
197                 auto status = tree_l.getStatus(node_l);
198                 auto label = tree_l.getLabel(node_l);
199                 res_tree->promoteNode(target, max_kids, status, label);
200             }
201 
202             if (min_kids == max_kids)
203             {
204                 /// ----- MERGE COMPLETELY -----
205                 for (auto i = max_kids - 1; i >= 0; --i)
206                 {
207                     stack_l.push(tree_l.getChild(node_l, i));
208                     stack_r.push(tree_r.getChild(node_r, i));
209                     stack.push(res_tree->getChild(target, i));
210                 }
211             }
212             else
213             {
214                 /// ----- MERGE PARTIALLY -----
215 
216                 /// For every "extra" child
217                 for (auto i = max_kids - 1; i >= min_kids; --i)
218                 {
219 
220                     if (kids_l > kids_r)
221                     {
222                         const auto kid_l = tree_l.getChild(node_l, i);
223                         const auto status = tree_l.getStatus(kid_l);
224 
225                         /// NOTE(maxim): this is most likely the case of replaying with skipped nodes,
226                         /// so should not be compared (the same below)
227                         if (status == NodeStatus::UNDETERMINED || status == NodeStatus::SKIPPED)
228                         {
229                             continue;
230                         }
231 
232                         stack_l.push(kid_l);
233                         stack_r.push(NodeID::NoNode);
234                         stack.push(res_tree->getChild(target, i));
235                     }
236                     else
237                     {
238                         const auto kid_r = tree_r.getChild(node_r, i);
239                         const auto status = tree_r.getStatus(kid_r);
240 
241                         if (status == NodeStatus::UNDETERMINED || status == NodeStatus::SKIPPED)
242                         {
243                             continue;
244                         }
245 
246                         stack_l.push(NodeID::NoNode);
247                         stack_r.push(kid_r);
248                         stack.push(res_tree->getChild(target, i));
249                     }
250                 }
251 
252                 /// For every child in common
253                 for (auto i = min_kids - 1; i >= 0; --i)
254                 {
255                     stack_l.push(tree_l.getChild(node_l, i));
256                     stack_r.push(tree_r.getChild(node_r, i));
257                     stack.push(res_tree->getChild(target, i));
258                 }
259             }
260         }
261         else
262         {
263             create_pentagon(*res_tree, target, tree_l, node_l, tree_r, node_r);
264 
265             auto count_left = utils::count_descendants(tree_l, node_l);
266             auto count_right = utils::count_descendants(tree_r, node_r);
267             auto pen_item = PentagonItem{target, count_left, count_right};
268 
269             merge_result->push_back(pen_item);
270         }
271     }
272 
273     print("Merging: done");
274 }
275 
276 } // namespace analysis
277 } // namespace cpprofiler