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.
5 // Copyright (C) 2018-2019 Intel Corporation
12 #include <unordered_map>
14 #include <ade/util/algorithm.hpp> // any_of
15 #include <ade/util/zip_range.hpp> // zip_range, indexed
17 #include <ade/graph.hpp>
18 #include <ade/passes/check_cycles.hpp>
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 #include "api/gapi_priv.hpp" // FIXME: why it is here?
25 #include "api/gbackend_priv.hpp" // Backend basic API (newInstance, etc)
27 #include "compiler/gmodel.hpp"
28 #include "compiler/gmodelbuilder.hpp"
29 #include "compiler/gcompiler.hpp"
30 #include "compiler/gcompiled_priv.hpp"
31 #include "compiler/passes/passes.hpp"
33 #include "executor/gexecutor.hpp"
34 #include "backends/common/gbackend.hpp"
37 #if !defined(GAPI_STANDALONE)
38 #include "opencv2/gapi/cpu/core.hpp" // Also directly refer to Core
39 #include "opencv2/gapi/cpu/imgproc.hpp" // ...and Imgproc kernel implementations
40 #endif // !defined(GAPI_STANDALONE)
43 #include "opencv2/gapi/gcompoundkernel.hpp" // compound::backend()
49 cv::gapi::GKernelPackage getKernelPackage(cv::GCompileArgs &args)
52 #if !defined(GAPI_STANDALONE)
53 combine(cv::gapi::core::cpu::kernels(),
54 cv::gapi::imgproc::cpu::kernels(),
55 cv::unite_policy::KEEP);
57 cv::gapi::GKernelPackage();
58 #endif // !defined(GAPI_STANDALONE)
59 auto user_pkg = cv::gimpl::getCompileArg<cv::gapi::GKernelPackage>(args);
60 return combine(ocv_pkg, user_pkg.value_or(cv::gapi::GKernelPackage{}), cv::unite_policy::REPLACE);
63 cv::util::optional<std::string> getGraphDumpDirectory(cv::GCompileArgs& args)
65 auto dump_info = cv::gimpl::getCompileArg<cv::graph_dump_path>(args);
66 if (!dump_info.has_value())
68 const char* path = std::getenv("GRAPH_DUMP_PATH");
70 ? cv::util::make_optional(std::string(path))
71 : cv::util::optional<std::string>();
75 return cv::util::make_optional(dump_info.value().m_dump_path);
78 } // anonymous namespace
81 // GCompiler implementation ////////////////////////////////////////////////////
83 cv::gimpl::GCompiler::GCompiler(const cv::GComputation &c,
86 : m_c(c), m_metas(std::move(metas)), m_args(std::move(args))
88 using namespace std::placeholders;
89 m_all_kernels = getKernelPackage(m_args);
90 auto lookup_order = getCompileArg<gapi::GLookupOrder>(m_args).value_or(gapi::GLookupOrder());
91 auto dump_path = getGraphDumpDirectory(m_args);
93 m_e.addPassStage("init");
94 m_e.addPass("init", "check_cycles", ade::passes::CheckCycles());
95 m_e.addPass("init", "expand_kernels", std::bind(passes::expandKernels, _1,
96 m_all_kernels)); // NB: package is copied
97 m_e.addPass("init", "topo_sort", ade::passes::TopologicalSort());
98 m_e.addPass("init", "init_islands", passes::initIslands);
99 m_e.addPass("init", "check_islands", passes::checkIslands);
101 // - Check basic graph validity (i.e., all inputs are connected)
102 // - Complex dependencies (i.e. parent-child) unrolling
105 // Remove GCompoundBackend to avoid calling setupBackend() with it in the list
106 m_all_kernels.remove(cv::gapi::compound::backend());
108 m_e.addPassStage("kernels");
109 m_e.addPass("kernels", "resolve_kernels", std::bind(passes::resolveKernels, _1,
110 std::ref(m_all_kernels), // NB: and not copied here
112 m_e.addPass("kernels", "check_islands_content", passes::checkIslandsContent);
114 m_e.addPassStage("meta");
115 m_e.addPass("meta", "initialize", std::bind(passes::initMeta, _1, std::ref(m_metas)));
116 m_e.addPass("meta", "propagate", std::bind(passes::inferMeta, _1, false));
117 m_e.addPass("meta", "finalize", passes::storeResultingMeta);
118 // moved to another stage, FIXME: two dumps?
119 // m_e.addPass("meta", "dump_dot", passes::dumpDotStdout);
121 // Special stage for backend-specific transformations
122 // FIXME: document passes hierarchy and order for backend developers
123 m_e.addPassStage("transform");
125 m_e.addPassStage("exec");
126 m_e.addPass("exec", "fuse_islands", passes::fuseIslands);
127 m_e.addPass("exec", "sync_islands", passes::syncIslandTags);
129 if (dump_path.has_value())
131 m_e.addPass("exec", "dump_dot", std::bind(passes::dumpGraph, _1,
135 // Process backends at the last moment (after all G-API passes are added).
136 ade::ExecutionEngineSetupContext ectx(m_e);
137 auto backends = m_all_kernels.backends();
138 for (auto &b : backends)
140 b.priv().addBackendPasses(ectx);
144 void cv::gimpl::GCompiler::validateInputMeta()
146 if (m_metas.size() != m_c.priv().m_ins.size())
148 util::throw_error(std::logic_error
149 ("COMPILE: GComputation interface / metadata mismatch! "
150 "(expected " + std::to_string(m_c.priv().m_ins.size()) + ", "
151 "got " + std::to_string(m_metas.size()) + " meta arguments)"));
154 const auto meta_matches = [](const GMetaArg &meta, const GProtoArg &proto) {
155 switch (proto.index())
157 // FIXME: Auto-generate methods like this from traits:
158 case GProtoArg::index_of<cv::GMat>():
159 return util::holds_alternative<cv::GMatDesc>(meta);
161 case GProtoArg::index_of<cv::GScalar>():
162 return util::holds_alternative<cv::GScalarDesc>(meta);
164 case GProtoArg::index_of<cv::detail::GArrayU>():
165 return util::holds_alternative<cv::GArrayDesc>(meta);
170 return false; // should never happen
173 for (const auto &meta_arg_idx : ade::util::indexed(ade::util::zip(m_metas, m_c.priv().m_ins)))
175 const auto &meta = std::get<0>(ade::util::value(meta_arg_idx));
176 const auto &proto = std::get<1>(ade::util::value(meta_arg_idx));
178 if (!meta_matches(meta, proto))
180 const auto index = ade::util::index(meta_arg_idx);
181 util::throw_error(std::logic_error
182 ("GComputation object type / metadata descriptor mismatch "
183 "(argument " + std::to_string(index) + ")"));
184 // FIXME: report what we've got and what we've expected
190 void cv::gimpl::GCompiler::validateOutProtoArgs()
192 for (const auto &out_pos : ade::util::indexed(m_c.priv().m_outs))
194 const auto &node = proto::origin_of(ade::util::value(out_pos)).node;
195 if (node.shape() != cv::GNode::NodeShape::CALL)
197 auto pos = ade::util::index(out_pos);
198 util::throw_error(std::logic_error
199 ("Computation output " + std::to_string(pos) +
200 " is not a result of any operation"));
205 cv::gimpl::GCompiler::GPtr cv::gimpl::GCompiler::generateGraph()
208 validateOutProtoArgs();
210 // Generate ADE graph from expression-based computation
211 std::unique_ptr<ade::Graph> pG(new ade::Graph);
215 cv::gimpl::GModel::init(gm);
216 cv::gimpl::GModelBuilder builder(g);
217 auto proto_slots = builder.put(m_c.priv().m_ins, m_c.priv().m_outs);
218 GAPI_LOG_INFO(NULL, "Generated graph: " << g.nodes().size() << " nodes" << std::endl);
220 // Store Computation's protocol in metadata
222 std::tie(p.inputs, p.outputs, p.in_nhs, p.out_nhs) = proto_slots;
223 gm.metadata().set(p);
228 void cv::gimpl::GCompiler::runPasses(ade::Graph &g)
231 GAPI_LOG_INFO(NULL, "All compiler passes are successful");
234 void cv::gimpl::GCompiler::compileIslands(ade::Graph &g)
237 std::shared_ptr<ade::Graph> gptr(gm.metadata().get<IslandModel>().model);
238 GIslandModel::Graph gim(*gptr);
240 // Run topological sort on GIslandModel first
241 auto pass_ctx = ade::passes::PassContext{*gptr};
242 ade::passes::TopologicalSort{}(pass_ctx);
244 // Now compile islands
245 GIslandModel::compileIslands(gim, g, m_args);
248 cv::GCompiled cv::gimpl::GCompiler::produceCompiled(GPtr &&pg)
250 // This is the final compilation step. Here:
251 // - An instance of GExecutor is created. Depening on the platform,
252 // build configuration, etc, a GExecutor may be:
253 // - a naive single-thread graph interpreter;
254 // - a std::thread-based thing
255 // - a TBB-based thing, etc.
256 // - All this stuff is wrapped into a GCompiled object and returned
259 // Note: this happens in the last pass ("compile_islands"):
260 // - Each GIsland of GIslandModel instantiates its own,
261 // backend-specific executable object
262 // - Every backend gets a subgraph to execute, and builds
263 // an execution plan for it (backend-specific execution)
264 // ...before call to produceCompiled();
266 const auto &outMetas = GModel::ConstGraph(*pg).metadata()
267 .get<OutputMeta>().outMeta;
268 std::unique_ptr<GExecutor> pE(new GExecutor(std::move(pg)));
269 // FIXME: select which executor will be actually used,
270 // make GExecutor abstract.
273 compiled.priv().setup(m_metas, outMetas, std::move(pE));
277 cv::GCompiled cv::gimpl::GCompiler::compile()
279 std::unique_ptr<ade::Graph> pG = generateGraph();
282 return produceCompiled(std::move(pG));