| 1 | // This file is part of OpenCV project. |
| 2 | // It is subject to the license terms in the LICENSE file found in the top-level directory |
| 3 | // of this distribution and at http://opencv.org/license.html. |
| 4 | // |
| 5 | // Copyright (C) 2018-2020 Intel Corporation |
| 6 | |
| 7 | |
| 8 | #include "precomp.hpp" |
| 9 | |
| 10 | #include <vector> |
| 11 | #include <stack> |
| 12 | #include <unordered_map> |
| 13 | |
| 14 | #include <ade/util/algorithm.hpp> // any_of |
| 15 | #include <ade/util/zip_range.hpp> // zip_range, indexed |
| 16 | |
| 17 | #include <ade/graph.hpp> |
| 18 | #include <ade/passes/check_cycles.hpp> |
| 19 | |
| 20 | #include "api/gcomputation_priv.hpp" |
| 21 | #include "api/gnode_priv.hpp" // FIXME: why it is here? |
| 22 | #include "api/gproto_priv.hpp" // FIXME: why it is here? |
| 23 | #include "api/gcall_priv.hpp" // FIXME: why it is here? |
| 24 | |
| 25 | #include "api/gbackend_priv.hpp" // Backend basic API (newInstance, etc) |
| 26 | |
| 27 | #include "compiler/gmodel.hpp" |
| 28 | #include "compiler/gmodelbuilder.hpp" |
| 29 | #include "compiler/gcompiler.hpp" |
| 30 | #include "compiler/gcompiled_priv.hpp" |
| 31 | #include "compiler/gstreaming_priv.hpp" |
| 32 | #include "compiler/passes/passes.hpp" |
| 33 | #include "compiler/passes/pattern_matching.hpp" |
| 34 | |
| 35 | #include "executor/gexecutor.hpp" |
| 36 | #include "executor/gthreadedexecutor.hpp" |
| 37 | #include "executor/gstreamingexecutor.hpp" |
| 38 | #include "backends/common/gbackend.hpp" |
| 39 | #include "backends/common/gmetabackend.hpp" |
| 40 | #include "backends/streaming/gstreamingbackend.hpp" // cv::gimpl::streaming::kernels() |
| 41 | |
| 42 | // <FIXME:> |
| 43 | #if !defined(GAPI_STANDALONE) |
| 44 | #include <opencv2/gapi/cpu/core.hpp> // Also directly refer to Core, |
| 45 | #include <opencv2/gapi/cpu/imgproc.hpp> // ...Imgproc |
| 46 | #include <opencv2/gapi/cpu/video.hpp> // ...and Video kernel implementations |
| 47 | #include <opencv2/gapi/render/render.hpp> // render::ocv::backend() |
| 48 | #endif // !defined(GAPI_STANDALONE) |
| 49 | // </FIXME:> |
| 50 | |
| 51 | #include <opencv2/gapi/gcompoundkernel.hpp> // compound::backend() |
| 52 | |
| 53 | #include "logger.hpp" |
| 54 | |
| 55 | namespace |
| 56 | { |
| 57 | cv::GKernelPackage getKernelPackage(cv::GCompileArgs &args) |
| 58 | { |
| 59 | auto withAuxKernels = [](const cv::GKernelPackage& pkg) { |
| 60 | cv::GKernelPackage aux_pkg; |
| 61 | for (const auto &b : pkg.backends()) { |
| 62 | aux_pkg = cv::gapi::combine(lhs: aux_pkg, rhs: b.priv().auxiliaryKernels()); |
| 63 | } |
| 64 | // Always include built-in meta<> and copy implementation |
| 65 | return cv::gapi::combine(a: pkg, |
| 66 | b: aux_pkg, |
| 67 | rest: cv::gimpl::meta::kernels(), |
| 68 | rest: cv::gimpl::streaming::kernels()); |
| 69 | }; |
| 70 | |
| 71 | auto has_use_only = cv::gapi::getCompileArg<cv::gapi::use_only>(args); |
| 72 | if (has_use_only) |
| 73 | return withAuxKernels(has_use_only.value().pkg); |
| 74 | |
| 75 | static auto ocv_pkg = |
| 76 | #if !defined(GAPI_STANDALONE) |
| 77 | cv::gapi::combine(a: cv::gapi::core::cpu::kernels(), |
| 78 | b: cv::gapi::imgproc::cpu::kernels(), |
| 79 | rest: cv::gapi::video::cpu::kernels(), |
| 80 | rest: cv::gapi::render::ocv::kernels(), |
| 81 | rest: cv::gapi::streaming::kernels()); |
| 82 | #else |
| 83 | cv::GKernelPackage(); |
| 84 | #endif // !defined(GAPI_STANDALONE) |
| 85 | |
| 86 | auto user_pkg = cv::gapi::getCompileArg<cv::GKernelPackage>(args); |
| 87 | auto user_pkg_with_aux = withAuxKernels(user_pkg.value_or(default_value: cv::GKernelPackage{})); |
| 88 | return cv::gapi::combine(lhs: ocv_pkg, rhs: user_pkg_with_aux); |
| 89 | } |
| 90 | |
| 91 | cv::gapi::GNetPackage getNetworkPackage(cv::GCompileArgs &args) |
| 92 | { |
| 93 | return cv::gapi::getCompileArg<cv::gapi::GNetPackage>(args) |
| 94 | .value_or(default_value: cv::gapi::GNetPackage{}); |
| 95 | } |
| 96 | |
| 97 | cv::util::optional<std::string> getGraphDumpDirectory(cv::GCompileArgs& args) |
| 98 | { |
| 99 | auto dump_info = cv::gapi::getCompileArg<cv::graph_dump_path>(args); |
| 100 | if (!dump_info.has_value()) |
| 101 | { |
| 102 | const std::string path = cv::utils::getConfigurationParameterString(name: "GRAPH_DUMP_PATH" ); |
| 103 | return !path.empty() |
| 104 | ? cv::util::make_optional(value: path) |
| 105 | : cv::util::optional<std::string>(); |
| 106 | } |
| 107 | else |
| 108 | { |
| 109 | return cv::util::make_optional(value&: dump_info.value().m_dump_path); |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | template<typename C> |
| 114 | cv::GKernelPackage auxKernelsFrom(const C& c) { |
| 115 | cv::GKernelPackage result; |
| 116 | for (const auto &b : c) { |
| 117 | result = cv::gapi::combine(result, b.priv().auxiliaryKernels()); |
| 118 | } |
| 119 | return result; |
| 120 | } |
| 121 | |
| 122 | using adeGraphs = std::vector<std::unique_ptr<ade::Graph>>; |
| 123 | |
| 124 | // Creates ADE graphs (patterns and substitutes) from pkg's transformations |
| 125 | void makeTransformationGraphs(const cv::GKernelPackage& pkg, |
| 126 | adeGraphs& patterns, |
| 127 | adeGraphs& substitutes) { |
| 128 | const auto& transforms = pkg.get_transformations(); |
| 129 | const auto size = transforms.size(); |
| 130 | if (0u == size) return; |
| 131 | |
| 132 | // pre-generate all required graphs |
| 133 | patterns.resize(new_size: size); |
| 134 | substitutes.resize(new_size: size); |
| 135 | for (auto it : ade::util::zip(ranges: ade::util::toRange(val: transforms), |
| 136 | ranges: ade::util::toRange(val&: patterns), |
| 137 | ranges: ade::util::toRange(val&: substitutes))) { |
| 138 | const auto& t = std::get<0>(t&: it); |
| 139 | auto& p = std::get<1>(t&: it); |
| 140 | auto& s = std::get<2>(t&: it); |
| 141 | p = cv::gimpl::GCompiler::makeGraph(t.pattern().priv()); |
| 142 | s = cv::gimpl::GCompiler::makeGraph(t.substitute().priv()); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | void checkTransformations(const cv::GKernelPackage& pkg, |
| 147 | const adeGraphs& patterns, |
| 148 | const adeGraphs& substitutes) { |
| 149 | const auto& transforms = pkg.get_transformations(); |
| 150 | const auto size = transforms.size(); |
| 151 | if (0u == size) return; |
| 152 | |
| 153 | GAPI_Assert(size == patterns.size()); |
| 154 | GAPI_Assert(size == substitutes.size()); |
| 155 | |
| 156 | const auto empty = [] (const cv::gimpl::SubgraphMatch& m) { |
| 157 | return m.inputDataNodes.empty() && m.startOpNodes.empty() |
| 158 | && m.finishOpNodes.empty() && m.outputDataNodes.empty() |
| 159 | && m.inputTestDataNodes.empty() && m.outputTestDataNodes.empty(); |
| 160 | }; |
| 161 | |
| 162 | // **FIXME**: verify other types of endless loops. now, only checking if pattern exists in |
| 163 | // substitute within __the same__ transformation |
| 164 | for (size_t i = 0; i < size; ++i) { |
| 165 | const auto& p = patterns[i]; |
| 166 | const auto& s = substitutes[i]; |
| 167 | |
| 168 | auto matchInSubstitute = cv::gimpl::findMatches(patternGraph: *p, compGraph: *s); |
| 169 | if (!empty(matchInSubstitute)) { |
| 170 | std::stringstream ss; |
| 171 | ss << "Error: (in transformation with description: '" |
| 172 | << transforms[i].description |
| 173 | << "') pattern is detected inside substitute" ; |
| 174 | throw std::runtime_error(ss.str()); |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | } // anonymous namespace |
| 179 | |
| 180 | |
| 181 | // GCompiler implementation //////////////////////////////////////////////////// |
| 182 | |
| 183 | cv::gimpl::GCompiler::GCompiler(const cv::GComputation &c, |
| 184 | GMetaArgs &&metas, |
| 185 | GCompileArgs &&args) |
| 186 | : m_c(c), m_metas(std::move(metas)), m_args(std::move(args)) |
| 187 | { |
| 188 | using namespace std::placeholders; |
| 189 | |
| 190 | auto kernels_to_use = getKernelPackage(args&: m_args); |
| 191 | auto networks_to_use = getNetworkPackage(args&: m_args); |
| 192 | std::unordered_set<cv::gapi::GBackend> all_backends; |
| 193 | const auto take = [&](std::vector<cv::gapi::GBackend> &&v) { |
| 194 | all_backends.insert(first: v.begin(), last: v.end()); |
| 195 | }; |
| 196 | take(kernels_to_use.backends()); |
| 197 | take(networks_to_use.backends()); |
| 198 | |
| 199 | m_all_kernels = cv::gapi::combine(lhs: kernels_to_use, |
| 200 | rhs: auxKernelsFrom(c: all_backends)); |
| 201 | // NB: The expectation in the line above is that |
| 202 | // NN backends (present here via network package) always add their |
| 203 | // inference kernels via auxiliary...() |
| 204 | |
| 205 | // sanity check transformations |
| 206 | { |
| 207 | adeGraphs patterns, substitutes; |
| 208 | // FIXME: returning vectors of unique_ptrs from makeTransformationGraphs results in |
| 209 | // compile error (at least) on GCC 9 with usage of copy-ctor of std::unique_ptr, so |
| 210 | // using initialization by lvalue reference instead |
| 211 | makeTransformationGraphs(pkg: m_all_kernels, patterns, substitutes); |
| 212 | checkTransformations(pkg: m_all_kernels, patterns, substitutes); |
| 213 | |
| 214 | // NB: saving generated patterns to m_all_patterns to be used later in passes |
| 215 | m_all_patterns = std::move(patterns); |
| 216 | } |
| 217 | |
| 218 | auto dump_path = getGraphDumpDirectory(args&: m_args); |
| 219 | |
| 220 | m_e.addPassStage(stageName: "init" ); |
| 221 | m_e.addPass(stageName: "init" , passName: "check_cycles" , pass: ade::passes::CheckCycles()); |
| 222 | m_e.addPass(stageName: "init" , passName: "apply_transformations" , |
| 223 | pass: std::bind(f&: passes::applyTransformations, args: _1, args: std::cref(t: m_all_kernels), |
| 224 | args: std::cref(t: m_all_patterns))); // Note: and re-using patterns here |
| 225 | m_e.addPass(stageName: "init" , passName: "expand_kernels" , |
| 226 | pass: std::bind(f&: passes::expandKernels, args: _1, |
| 227 | args&: m_all_kernels)); // NB: package is copied |
| 228 | m_e.addPass(stageName: "init" , passName: "topo_sort" , pass: ade::passes::TopologicalSort()); |
| 229 | m_e.addPass(stageName: "init" , passName: "init_islands" , pass&: passes::initIslands); |
| 230 | m_e.addPass(stageName: "init" , passName: "check_islands" , pass&: passes::checkIslands); |
| 231 | // TODO: |
| 232 | // - Check basic graph validity (i.e., all inputs are connected) |
| 233 | // - Complex dependencies (i.e. parent-child) unrolling |
| 234 | // - etc, etc, etc |
| 235 | |
| 236 | // Remove GCompoundBackend to avoid calling setupBackend() with it in the list |
| 237 | m_all_kernels.remove(backend: cv::gapi::compound::backend()); |
| 238 | |
| 239 | m_e.addPassStage(stageName: "kernels" ); |
| 240 | m_e.addPass(stageName: "kernels" , passName: "bind_net_params" , |
| 241 | pass: std::bind(f&: passes::bindNetParams, args: _1, |
| 242 | args&: networks_to_use)); |
| 243 | m_e.addPass(stageName: "kernels" , passName: "resolve_kernels" , |
| 244 | pass: std::bind(f&: passes::resolveKernels, args: _1, |
| 245 | args: std::ref(t&: m_all_kernels))); // NB: and not copied here |
| 246 | // (no compound backend present here) |
| 247 | m_e.addPass(stageName: "kernels" , passName: "check_islands_content" , pass&: passes::checkIslandsContent); |
| 248 | |
| 249 | // Special stage for intrinsics handling |
| 250 | m_e.addPassStage(stageName: "intrin" ); |
| 251 | m_e.addPass(stageName: "intrin" , passName: "desync" , pass&: passes::intrinDesync); |
| 252 | m_e.addPass(stageName: "intrin" , passName: "finalizeIntrin" , pass&: passes::intrinFinalize); |
| 253 | |
| 254 | //Input metas may be empty when a graph is compiled for streaming |
| 255 | m_e.addPassStage(stageName: "meta" ); |
| 256 | if (!m_metas.empty()) |
| 257 | { |
| 258 | m_e.addPass(stageName: "meta" , passName: "initialize" , pass: std::bind(f&: passes::initMeta, args: _1, args: std::ref(t: m_metas))); |
| 259 | m_e.addPass(stageName: "meta" , passName: "propagate" , pass: std::bind(f&: passes::inferMeta, args: _1, args: false)); |
| 260 | m_e.addPass(stageName: "meta" , passName: "finalize" , pass&: passes::storeResultingMeta); |
| 261 | // moved to another stage, FIXME: two dumps? |
| 262 | // m_e.addPass("meta", "dump_dot", passes::dumpDotStdout); |
| 263 | } |
| 264 | // Special stage for backend-specific transformations |
| 265 | // FIXME: document passes hierarchy and order for backend developers |
| 266 | m_e.addPassStage(stageName: "transform" ); |
| 267 | |
| 268 | m_e.addPassStage(stageName: "exec" ); |
| 269 | m_e.addPass(stageName: "exec" , passName: "fuse_islands" , pass&: passes::fuseIslands); |
| 270 | m_e.addPass(stageName: "exec" , passName: "sync_islands" , pass&: passes::syncIslandTags); |
| 271 | |
| 272 | // FIXME: Since a set of passes is shared between |
| 273 | // GCompiled/GStreamingCompiled, this pass is added here unconditionally |
| 274 | // (even if it is not actually required to produce a GCompiled). |
| 275 | // FIXME: add a better way to do that! |
| 276 | m_e.addPass(stageName: "exec" , passName: "add_streaming" , pass&: passes::addStreaming); |
| 277 | |
| 278 | // Note: Must be called after addStreaming as addStreaming pass |
| 279 | // can possibly add new nodes to the IslandModel |
| 280 | m_e.addPass(stageName: "exec" , passName: "sort_islands" , pass&: passes::topoSortIslands); |
| 281 | |
| 282 | if (dump_path.has_value()) |
| 283 | { |
| 284 | m_e.addPass(stageName: "exec" , passName: "dump_dot" , pass: std::bind(f&: passes::dumpGraph, args: _1, |
| 285 | args&: dump_path.value())); |
| 286 | } |
| 287 | |
| 288 | // FIXME: This should be called for "ActiveBackends" only (see metadata). |
| 289 | // However, ActiveBackends are known only after passes are actually executed. |
| 290 | // At these stage, they are not executed yet. |
| 291 | ade::ExecutionEngineSetupContext ectx(m_e); |
| 292 | auto backends = m_all_kernels.backends(); |
| 293 | for (auto &b : backends) |
| 294 | { |
| 295 | b.priv().addBackendPasses(ectx); |
| 296 | if (!m_metas.empty()) |
| 297 | { |
| 298 | b.priv().addMetaSensitiveBackendPasses(ectx); |
| 299 | } |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | void cv::gimpl::GCompiler::validateInputMeta() |
| 304 | { |
| 305 | // FIXME: implement testing/accessor methods at the Priv's API level? |
| 306 | if (!util::holds_alternative<GComputation::Priv::Expr>(v: m_c.priv().m_shape)) |
| 307 | { |
| 308 | GAPI_LOG_WARNING(NULL, "Metadata validation is not implemented yet for" |
| 309 | " deserialized graphs!" ); |
| 310 | return; |
| 311 | } |
| 312 | const auto &c_expr = util::get<cv::GComputation::Priv::Expr>(v: m_c.priv().m_shape); |
| 313 | if (m_metas.size() != c_expr.m_ins.size()) |
| 314 | { |
| 315 | util::throw_error(e: std::logic_error |
| 316 | ("COMPILE: GComputation interface / metadata mismatch! " |
| 317 | "(expected " + std::to_string(val: c_expr.m_ins.size()) + ", " |
| 318 | "got " + std::to_string(val: m_metas.size()) + " meta arguments)" )); |
| 319 | } |
| 320 | |
| 321 | const auto meta_matches = [](const GMetaArg &meta, const GProtoArg &proto) { |
| 322 | switch (proto.index()) |
| 323 | { |
| 324 | // FIXME: Auto-generate methods like this from traits: |
| 325 | case GProtoArg::index_of<cv::GMat>(): |
| 326 | case GProtoArg::index_of<cv::GMatP>(): |
| 327 | return util::holds_alternative<cv::GMatDesc>(v: meta); |
| 328 | |
| 329 | case GProtoArg::index_of<cv::GFrame>(): |
| 330 | return util::holds_alternative<cv::GFrameDesc>(v: meta); |
| 331 | |
| 332 | case GProtoArg::index_of<cv::GScalar>(): |
| 333 | return util::holds_alternative<cv::GScalarDesc>(v: meta); |
| 334 | |
| 335 | case GProtoArg::index_of<cv::detail::GArrayU>(): |
| 336 | return util::holds_alternative<cv::GArrayDesc>(v: meta); |
| 337 | |
| 338 | case GProtoArg::index_of<cv::detail::GOpaqueU>(): |
| 339 | return util::holds_alternative<cv::GOpaqueDesc>(v: meta); |
| 340 | |
| 341 | default: |
| 342 | GAPI_Error("InternalError" ); |
| 343 | } |
| 344 | return false; // should never happen |
| 345 | }; |
| 346 | |
| 347 | GAPI_LOG_DEBUG(nullptr, "Total count: " << m_metas.size()); |
| 348 | for (const auto meta_arg_idx : ade::util::indexed(conts: ade::util::zip(ranges: m_metas, ranges: c_expr.m_ins))) |
| 349 | { |
| 350 | const auto &meta = std::get<0>(t: ade::util::value(val: meta_arg_idx)); |
| 351 | const auto &proto = std::get<1>(t: ade::util::value(val: meta_arg_idx)); |
| 352 | |
| 353 | const auto index = ade::util::index(val: meta_arg_idx); |
| 354 | GAPI_LOG_DEBUG(nullptr, "Process index: " << index); |
| 355 | |
| 356 | // check types validity |
| 357 | if (!meta_matches(meta, proto)) |
| 358 | { |
| 359 | util::throw_error(e: std::logic_error |
| 360 | ("GComputation object type / metadata descriptor mismatch " |
| 361 | "(argument " + std::to_string(val: index) + ")" )); |
| 362 | // FIXME: report what we've got and what we've expected |
| 363 | } |
| 364 | |
| 365 | // check value consistency |
| 366 | gimpl::proto::validate_input_meta_arg(meta); //may throw |
| 367 | } |
| 368 | // All checks are ok |
| 369 | } |
| 370 | |
| 371 | void cv::gimpl::GCompiler::validateOutProtoArgs() |
| 372 | { |
| 373 | // FIXME: implement testing/accessor methods at the Priv's API level? |
| 374 | if (!util::holds_alternative<GComputation::Priv::Expr>(v: m_c.priv().m_shape)) |
| 375 | { |
| 376 | GAPI_LOG_WARNING(NULL, "Output parameter validation is not implemented yet for" |
| 377 | " deserialized graphs!" ); |
| 378 | return; |
| 379 | } |
| 380 | const auto &c_expr = util::get<cv::GComputation::Priv::Expr>(v: m_c.priv().m_shape); |
| 381 | for (const auto out_pos : ade::util::indexed(conts: c_expr.m_outs)) |
| 382 | { |
| 383 | const auto &node = proto::origin_of(arg: ade::util::value(val: out_pos)).node; |
| 384 | if (node.shape() != cv::GNode::NodeShape::CALL) |
| 385 | { |
| 386 | auto pos = ade::util::index(val: out_pos); |
| 387 | util::throw_error(e: std::logic_error |
| 388 | ("Computation output " + std::to_string(val: pos) + |
| 389 | " is not a result of any operation" )); |
| 390 | } |
| 391 | } |
| 392 | } |
| 393 | |
| 394 | cv::gimpl::GCompiler::GPtr cv::gimpl::GCompiler::generateGraph() |
| 395 | { |
| 396 | if (!m_metas.empty()) |
| 397 | { |
| 398 | // Metadata may be empty if we're compiling our graph for streaming |
| 399 | validateInputMeta(); |
| 400 | } |
| 401 | validateOutProtoArgs(); |
| 402 | auto g = makeGraph(m_c.priv()); |
| 403 | if (!m_metas.empty()) |
| 404 | { |
| 405 | GModel::Graph(*g).metadata().set(OriginalInputMeta{.inputMeta: m_metas}); |
| 406 | } |
| 407 | // FIXME: remove m_args, remove GCompileArgs from backends' method signatures, |
| 408 | // rework backends to access GCompileArgs from graph metadata |
| 409 | GModel::Graph(*g).metadata().set(CompileArgs{.args: m_args}); |
| 410 | return g; |
| 411 | } |
| 412 | |
| 413 | void cv::gimpl::GCompiler::runPasses(ade::Graph &g) |
| 414 | { |
| 415 | m_e.runPasses(graph&: g); |
| 416 | GAPI_LOG_INFO(NULL, "All compiler passes are successful" ); |
| 417 | } |
| 418 | |
| 419 | void cv::gimpl::GCompiler::compileIslands(ade::Graph &g) |
| 420 | { |
| 421 | compileIslands(g, args: m_args); |
| 422 | } |
| 423 | |
| 424 | void cv::gimpl::GCompiler::compileIslands(ade::Graph &g, const cv::GCompileArgs &args) |
| 425 | { |
| 426 | GModel::Graph gm(g); |
| 427 | std::shared_ptr<ade::Graph> gptr(gm.metadata().get<IslandModel>().model); |
| 428 | GIslandModel::Graph gim(*gptr); |
| 429 | |
| 430 | GIslandModel::compileIslands(g&: gim, orig_g: g, args); |
| 431 | } |
| 432 | |
| 433 | cv::GCompiled cv::gimpl::GCompiler::produceCompiled(GPtr &&pg) |
| 434 | { |
| 435 | // This is the final compilation step. Here: |
| 436 | // - An instance of GExecutor is created. Depending on the platform, |
| 437 | // build configuration, etc, a GExecutor may be: |
| 438 | // - a naive single-thread graph interpreter; |
| 439 | // - a std::thread-based thing |
| 440 | // - a TBB-based thing, etc. |
| 441 | // - All this stuff is wrapped into a GCompiled object and returned |
| 442 | // to user. |
| 443 | |
| 444 | // Note: this happens in the last pass ("compile_islands"): |
| 445 | // - Each GIsland of GIslandModel instantiates its own, |
| 446 | // backend-specific executable object |
| 447 | // - Every backend gets a subgraph to execute, and builds |
| 448 | // an execution plan for it (backend-specific execution) |
| 449 | // ...before call to produceCompiled(); |
| 450 | |
| 451 | GModel::ConstGraph cgr(*pg); |
| 452 | const auto &outMetas = GModel::ConstGraph(*pg).metadata() |
| 453 | .get<OutputMeta>().outMeta; |
| 454 | // FIXME: select which executor will be actually used, |
| 455 | // make GExecutor abstract. |
| 456 | |
| 457 | auto use_threaded_exec = cv::gapi::getCompileArg<cv::use_threaded_executor>(args: m_args); |
| 458 | std::unique_ptr<GAbstractExecutor> pE; |
| 459 | if (use_threaded_exec) { |
| 460 | const auto num_threads = use_threaded_exec.value().num_threads; |
| 461 | GAPI_LOG_INFO(NULL, "Threaded executor with " << num_threads << " thread(s) will be used" ); |
| 462 | pE.reset(p: new GThreadedExecutor(num_threads, std::move(pg))); |
| 463 | } else { |
| 464 | pE.reset(p: new GExecutor(std::move(pg))); |
| 465 | } |
| 466 | GCompiled compiled; |
| 467 | compiled.priv().setup(metaArgs: m_metas, outMetas, pE: std::move(pE)); |
| 468 | |
| 469 | return compiled; |
| 470 | } |
| 471 | |
| 472 | cv::GStreamingCompiled cv::gimpl::GCompiler::produceStreamingCompiled(GPtr &&pg) |
| 473 | { |
| 474 | GStreamingCompiled compiled; |
| 475 | GMetaArgs outMetas; |
| 476 | |
| 477 | // FIXME: the whole below construct is ugly, need to revise |
| 478 | // how G*Compiled learns about its meta. |
| 479 | if (!m_metas.empty()) |
| 480 | { |
| 481 | outMetas = GModel::ConstGraph(*pg).metadata().get<OutputMeta>().outMeta; |
| 482 | } |
| 483 | |
| 484 | GModel::ConstGraph cgr(*pg); |
| 485 | |
| 486 | std::unique_ptr<GStreamingExecutor> pE(new GStreamingExecutor(std::move(pg), |
| 487 | m_args)); |
| 488 | if (!m_metas.empty() && !outMetas.empty()) |
| 489 | { |
| 490 | compiled.priv().setup(metaArgs: m_metas, outMetas, pE: std::move(pE)); |
| 491 | } |
| 492 | else if (m_metas.empty() && outMetas.empty()) |
| 493 | { |
| 494 | // Otherwise, set it up with executor object only |
| 495 | compiled.priv().setup(std::move(pE)); |
| 496 | } |
| 497 | else GAPI_Error("Impossible happened -- please report a bug" ); |
| 498 | return compiled; |
| 499 | } |
| 500 | |
| 501 | cv::GCompiled cv::gimpl::GCompiler::compile() |
| 502 | { |
| 503 | std::unique_ptr<ade::Graph> pG = generateGraph(); |
| 504 | runPasses(g&: *pG); |
| 505 | compileIslands(g&: *pG); |
| 506 | return produceCompiled(pg: std::move(pG)); |
| 507 | } |
| 508 | |
| 509 | cv::GStreamingCompiled cv::gimpl::GCompiler::compileStreaming() |
| 510 | { |
| 511 | // FIXME: self-note to DM: now keep these compile()/compileStreaming() in sync! |
| 512 | std::unique_ptr<ade::Graph> pG = generateGraph(); |
| 513 | GModel::Graph(*pG).metadata().set(Streaming{}); |
| 514 | runPasses(g&: *pG); |
| 515 | if (!m_metas.empty()) |
| 516 | { |
| 517 | // If the metadata has been passed, compile our islands! |
| 518 | compileIslands(g&: *pG); |
| 519 | } |
| 520 | return produceStreamingCompiled(pg: std::move(pG)); |
| 521 | } |
| 522 | |
| 523 | void cv::gimpl::GCompiler::runMetaPasses(ade::Graph &g, const cv::GMetaArgs &metas) |
| 524 | { |
| 525 | auto pass_ctx = ade::passes::PassContext{.graph: g}; |
| 526 | cv::gimpl::passes::initMeta(ctx&: pass_ctx, metas); |
| 527 | cv::gimpl::passes::inferMeta(ctx&: pass_ctx, meta_is_initialized: true); |
| 528 | cv::gimpl::passes::storeResultingMeta(ctx&: pass_ctx); |
| 529 | |
| 530 | // Also run meta-sensitive backend-specific passes, if there's any. |
| 531 | // FIXME: This may be hazardous if our backend are not very robust |
| 532 | // in their passes -- how can we guarantee correct functioning in the |
| 533 | // future? |
| 534 | ade::ExecutionEngine engine; |
| 535 | engine.addPassStage(stageName: "exec" ); // FIXME: Need a better decision on how we replicate |
| 536 | // our main compiler stages here. |
| 537 | ade::ExecutionEngineSetupContext ectx(engine); |
| 538 | |
| 539 | // NB: &&b or &b doesn't work here since "backends" is a set. Nevermind |
| 540 | for (auto b : GModel::Graph(g).metadata().get<ActiveBackends>().backends) |
| 541 | { |
| 542 | b.priv().addMetaSensitiveBackendPasses(ectx); |
| 543 | } |
| 544 | engine.runPasses(graph&: g); |
| 545 | } |
| 546 | |
| 547 | // Creates ADE graph from input/output proto args OR from its |
| 548 | // deserialized form |
| 549 | cv::gimpl::GCompiler::GPtr cv::gimpl::GCompiler::makeGraph(const cv::GComputation::Priv &priv) { |
| 550 | std::unique_ptr<ade::Graph> pG(new ade::Graph); |
| 551 | ade::Graph& g = *pG; |
| 552 | |
| 553 | if (cv::util::holds_alternative<cv::GComputation::Priv::Expr>(v: priv.m_shape)) { |
| 554 | auto c_expr = cv::util::get<cv::GComputation::Priv::Expr>(v: priv.m_shape); |
| 555 | cv::gimpl::GModel::Graph gm(g); |
| 556 | cv::gimpl::GModel::init(g&: gm); |
| 557 | cv::gimpl::GModelBuilder builder(g); |
| 558 | auto proto_slots = builder.put(ins: c_expr.m_ins, outs: c_expr.m_outs); |
| 559 | |
| 560 | // Store Computation's protocol in metadata |
| 561 | cv::gimpl::Protocol p; |
| 562 | std::tie(args&: p.inputs, args&: p.outputs, args&: p.in_nhs, args&: p.out_nhs) = proto_slots; |
| 563 | gm.metadata().set(p); |
| 564 | } else if (cv::util::holds_alternative<cv::GComputation::Priv::Dump>(v: priv.m_shape)) { |
| 565 | auto c_dump = cv::util::get<cv::GComputation::Priv::Dump>(v: priv.m_shape); |
| 566 | cv::gapi::s11n::reconstruct(s: c_dump, g); |
| 567 | } |
| 568 | return pG; |
| 569 | } |
| 570 | |