intrin.cpp
11.8 KB
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
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2020 Intel Corporation
#include "precomp.hpp"
#include <ade/util/algorithm.hpp>
#include <ade/util/zip_range.hpp>
#include <opencv2/gapi/streaming/desync.hpp>// GDesync intrinsic
#include "compiler/gmodel.hpp"
#include "compiler/passes/passes.hpp"
namespace desync {
namespace {
// Drop the desynchronized node `nh` from the graph, reconnect the
// graph structure properly. This is a helper function which is used
// in both drop(g) and apply(g) passes.
//
// @return a vector of new edge handles connecting the "main" graph
// with its desynchronized part.
std::vector<ade::EdgeHandle> drop(cv::gimpl::GModel::Graph &g,
ade::NodeHandle nh) {
using namespace cv::gimpl;
// What we need to do here:
// 1. Connect the readers of its produced data objects
// to the input data objects of desync;
// 2. Drop the data object it produces.
// 3. Drop the desync operation itself;
std::vector<ade::NodeHandle> in_data_objs = GModel::orderedInputs(g, nh);
std::vector<ade::NodeHandle> out_data_objs = GModel::orderedOutputs(g, nh);
std::vector<ade::EdgeHandle> new_links;
GAPI_Assert(in_data_objs.size() == out_data_objs.size());
GAPI_DbgAssert(ade::util::all_of
(out_data_objs,
[&](const ade::NodeHandle &oh) {
return g.metadata(oh).contains<Data>();
}));
// (1)
for (auto &&it: ade::util::zip(ade::util::toRange(in_data_objs),
ade::util::toRange(out_data_objs))) {
auto these_new_links = GModel::redirectReaders(g,
std::get<1>(it),
std::get<0>(it));
new_links.insert(new_links.end(),
these_new_links.begin(),
these_new_links.end());
}
// (2)
for (auto &&old_out_nh : out_data_objs) {
g.erase(old_out_nh);
}
// (3)
g.erase(nh);
return new_links;
}
// Tracing a desynchronizing subgraph is somewhat tricky and happens
// in both directions: downwards and upwards.
//
// The downward process is the basic one: we start with a "desync"
// OP node and go down to the graph using the "output" edges. We check
// if all nodes on this path [can] belong to this desynchronized path
// and don't overlap with others.
//
// An important contract to maintain is that the desynchronized part
// can't have any input references from the "main" graph part or any
// other desynchronized part in the graph. This contract is validated
// by checking every node's input which must belong to the same
// desynchronized part.
//
// Here is the pitfall of this check:
//
// v
// GMat_0
// v
// +----------+
// | desync() | <- This point originates the traceDown process
// +----------+
// v
// GMat_0' <- This node will be tagged for this desync at
// :--------. step 0/1
// v : <- The order how output nodes are visited is not
// +----------+ : specified, we can visit Op2() first (as there
// | Op1() | : is a direct link) bypassing visiting and tagging
// +----------+ : Op1() and GMat_1
// v :
// GMat_1 :
// : .---'
// v v <- When we visit Op2() via the 2nd edge on this
// +----------+ graph, we check if all inputs belong to the same
// | Op2() | desynchronized graph and GMat_1 fails this check
// +----------+ (since the traceDown() process haven't visited
// it yet).
//
// Cases like this originate the traceUp() process: if we find an
// input node in our desynchronized path which doesn't belong to this
// path YET, it is not 100% a problem, and we need to trace it back
// (upwards) to see if it is really a case.
// This recursive function checks the desync_id in the graph upwards.
// The process doesn't continue for nodes which have a valid
// desync_id already.
// The process only continues for nodes which have no desync_id
// assigned. If there's no such nodes anymore, the procedure is
// considered complete and a list of nodes to tag is returned to the
// caller.
//
// If NO inputs of this node have a valid desync_id, the desync
// invariant is broken and the function throws.
void traceUp(cv::gimpl::GModel::Graph &g,
const ade::NodeHandle &nh,
int desync_id,
std::vector<ade::NodeHandle> &path) {
using namespace cv::gimpl;
GAPI_Assert(!nh->inNodes().empty()
&& "traceUp: a desynchronized part of the graph is not isolated?");
if (g.metadata(nh).contains<DesyncPath>()) {
// We may face nodes which have DesyncPath already visited during
// this recursive process (e.g. via some other output or branch in the
// subgraph)
if (g.metadata(nh).get<DesyncPath>().index != desync_id) {
GAPI_Assert(false && "Desynchronization can't be nested!");
}
return; // This object belongs to the desync path - exit early.
}
// Regardless of the result, put this nh to the path
path.push_back(nh);
// Check if the input nodes are OK
std::vector<ade::NodeHandle> nodes_to_trace;
nodes_to_trace.reserve(nh->inNodes().size());
for (auto &&in_nh : nh->inNodes()) {
if (g.metadata(in_nh).contains<DesyncPath>()) {
// We may face nodes which have DesyncPath already visited during
// this recursive process (e.g. via some other output or branch in the
// subgraph)
GAPI_Assert(g.metadata(in_nh).get<DesyncPath>().index == desync_id
&& "Desynchronization can't be nested!");
} else {
nodes_to_trace.push_back(in_nh);
}
}
// If there are nodes to trace, continue the recursion
for (auto &&up_nh : nodes_to_trace) {
traceUp(g, up_nh, desync_id, path);
}
}
// This recursive function propagates the desync_id down to the graph
// starting at nh, and also checks:
// - if this desync path is isolated;
// - if this desync path is not overlapped.
// It also originates the traceUp() process at the points of
// uncertainty (as described in the comment above).
void traceDown(cv::gimpl::GModel::Graph &g,
const ade::NodeHandle &nh,
int desync_id) {
using namespace cv::gimpl;
if (g.metadata(nh).contains<DesyncPath>()) {
// We may face nodes which have DesyncPath already visited during
// this recursive process (e.g. via some other output or branch in the
// subgraph)
GAPI_Assert(g.metadata(nh).get<DesyncPath>().index == desync_id
&& "Desynchronization can't be nested!");
} else {
g.metadata(nh).set(DesyncPath{desync_id});
}
// All inputs of this data object must belong to the same
// desync path.
for (auto &&in_nh : nh->inNodes()) {
// If an input object is not assigned to this desync path,
// it does not means that the object doesn't belong to
// this path. Check it.
std::vector<ade::NodeHandle> path_up;
traceUp(g, in_nh, desync_id, path_up);
// We get here on success. Just set the proper tags for
// the identified input path.
for (auto &&up_nh : path_up) {
g.metadata(up_nh).set(DesyncPath{desync_id});
}
}
// Propagate the tag & check down
for (auto &&out_nh : nh->outNodes()) {
traceDown(g, out_nh, desync_id);
}
}
// Streaming case: ensure the graph has proper isolation of the
// desynchronized parts, set proper Edge metadata hints for
// GStreamingIntrinExecutable
void apply(cv::gimpl::GModel::Graph &g) {
using namespace cv::gimpl;
// Stage 0. Trace down the desync operations in the graph.
// Tag them with their unique (per graph) identifiers.
int total_desync = 0;
for (auto &&nh : g.nodes()) {
if (g.metadata(nh).get<NodeType>().t == NodeType::OP) {
const auto &op = g.metadata(nh).get<Op>();
if (op.k.name == cv::gapi::streaming::detail::GDesync::id()) {
GAPI_Assert(!g.metadata(nh).contains<DesyncPath>()
&& "Desynchronization can't be nested!");
const int this_desync_id = total_desync++;
g.metadata(nh).set(DesyncPath{this_desync_id});
for (auto &&out_nh: nh->outNodes()) {
traceDown(g, out_nh, this_desync_id);
}
} // if (desync)
} // if(OP)
} // for(nodes)
// Tracing is done for all desync ops in the graph now.
// Stage 1. Drop the desync operations from the graph, but mark
// the desynchronized edges a special way.
// The desynchronized edge is the edge which connects a main
// subgraph data with a desynchronized subgraph data.
std::vector<ade::NodeHandle> nodes(g.nodes().begin(), g.nodes().end());
for (auto &&nh : nodes) {
if (nh == nullptr) {
// Some nodes could be dropped already during the procedure
// thanks ADE their NodeHandles updated automatically
continue;
}
if (g.metadata(nh).get<NodeType>().t == NodeType::OP) {
const auto &op = g.metadata(nh).get<Op>();
if (op.k.name == cv::gapi::streaming::detail::GDesync::id()) {
auto index = g.metadata(nh).get<DesyncPath>().index;
auto new_links = drop(g, nh);
for (auto &&eh : new_links) {
g.metadata(eh).set(DesyncEdge{index});
}
} // if (desync)
} // if (Op)
} // for(nodes)
// Stage 2. Put a synchronized tag if there were changes applied
if (total_desync > 0) {
g.metadata().set(Desynchronized{});
}
}
// Probably the simplest case: desync makes no sense in the regular
// compilation process, so just drop all its occurences in the graph,
// reconnecting nodes properly.
void drop(cv::gimpl::GModel::Graph &g) {
// FIXME: LOG here that we're dropping the desync operations as
// they have no sense when compiling in the regular mode.
using namespace cv::gimpl;
std::vector<ade::NodeHandle> nodes(g.nodes().begin(), g.nodes().end());
for (auto &&nh : nodes) {
if (nh == nullptr) {
// Some nodes could be dropped already during the procedure
// thanks ADE their NodeHandles updated automatically
continue;
}
if (g.metadata(nh).get<NodeType>().t == NodeType::OP) {
const auto &op = g.metadata(nh).get<Op>();
if (op.k.name == cv::gapi::streaming::detail::GDesync::id()) {
drop(g, nh);
} // if (desync)
} // if (Op)
} // for(nodes)
}
} // anonymous namespace
} // namespace desync
void cv::gimpl::passes::intrinDesync(ade::passes::PassContext &ctx) {
GModel::Graph gr(ctx.graph);
if (!gr.metadata().contains<HasIntrinsics>())
return;
gr.metadata().contains<Streaming>()
? desync::apply(gr) // Streaming compilation
: desync::drop(gr); // Regular compilation
}
// Clears the HasIntrinsics flag if all intrinsics have been handled.
void cv::gimpl::passes::intrinFinalize(ade::passes::PassContext &ctx) {
GModel::Graph gr(ctx.graph);
for (auto &&nh : gr.nodes()) {
if (gr.metadata(nh).get<NodeType>().t == NodeType::OP) {
const auto &op = gr.metadata(nh).get<Op>();
if (is_intrinsic(op.k.name)) {
return;
}
}
}
// If reached here, really clear the flag
gr.metadata().erase<HasIntrinsics>();
}