2018-09-27 02:50:39 +08:00
|
|
|
// 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) 2018 Intel Corporation
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// FIXME: "I personally hate this file"
|
|
|
|
// - Dmitry
|
|
|
|
//
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2018-09-28 23:42:09 +08:00
|
|
|
#include "precomp.hpp"
|
|
|
|
|
2018-09-27 02:50:39 +08:00
|
|
|
#include <utility> // tuple
|
|
|
|
#include <stack> // stack
|
|
|
|
#include <vector> // vector
|
|
|
|
#include <unordered_set> // unordered_set
|
|
|
|
#include <type_traits> // is_same
|
|
|
|
|
|
|
|
#include <ade/util/zip_range.hpp> // util::indexed
|
|
|
|
|
2019-04-18 02:54:47 +08:00
|
|
|
#include "api/gorigin.hpp"
|
2018-09-27 02:50:39 +08:00
|
|
|
#include "api/gproto_priv.hpp" // descriptor_of and other GProtoArg-related
|
|
|
|
#include "api/gcall_priv.hpp"
|
|
|
|
#include "api/gnode_priv.hpp"
|
|
|
|
|
|
|
|
#include "compiler/gmodelbuilder.hpp"
|
2019-04-18 02:54:47 +08:00
|
|
|
#include "compiler/gmodel_priv.hpp"
|
2018-09-27 02:50:39 +08:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: move to helpers and cover with internal tests?
|
|
|
|
template<typename T> struct GVisited
|
|
|
|
{
|
|
|
|
typedef std::unordered_set<T> VTs;
|
|
|
|
|
|
|
|
bool visited(const T& t) const { return m_visited.find(t) != m_visited.end(); }
|
|
|
|
void visit (const T& t) { m_visited.insert(t); }
|
|
|
|
const VTs& visited() const { return m_visited; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
VTs m_visited;
|
|
|
|
};
|
|
|
|
|
|
|
|
template<typename T, typename U = T> struct GVisitedTracker: protected GVisited<T>
|
|
|
|
{
|
|
|
|
typedef std::vector<U> TUs;
|
|
|
|
|
|
|
|
void visit(const T& t, const U& u) { GVisited<T>::visit(t); m_tracked.push_back(u); }
|
|
|
|
const TUs& tracked() const { return m_tracked; }
|
|
|
|
using GVisited<T>::visited;
|
|
|
|
|
|
|
|
private:
|
|
|
|
TUs m_tracked;
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
|
|
cv::gimpl::Unrolled cv::gimpl::unrollExpr(const GProtoArgs &ins,
|
|
|
|
const GProtoArgs &outs)
|
|
|
|
{
|
|
|
|
// FIXME: Who's gonna check if ins/outs are not EMPTY?
|
|
|
|
// FIXME: operator== for GObjects? (test if the same object or not)
|
|
|
|
using GObjId = const cv::GOrigin*;
|
|
|
|
|
|
|
|
GVisitedTracker<const GNode::Priv*, cv::GNode> ops;
|
|
|
|
GVisited<GObjId> reached_sources;
|
|
|
|
cv::GOriginSet origins;
|
|
|
|
|
|
|
|
// Cache input argument objects for a faster look-up
|
|
|
|
// While the only reliable way to identify a Data object is Origin
|
|
|
|
// (multiple data objects may refer to the same Origin as result of
|
|
|
|
// multuple yield() calls), input objects can be uniquely identified
|
|
|
|
// by its `priv` address. Here we rely on this to verify if the expression
|
|
|
|
// we unroll actually matches the protocol specified to us by user.
|
|
|
|
std::unordered_set<GObjId> in_objs_p;
|
|
|
|
for (const auto& in_obj : ins)
|
|
|
|
{
|
Fix modules/ typos
Found using `codespell -q 3 -S ./3rdparty -L activ,amin,ang,atleast,childs,dof,endwhile,halfs,hist,iff,nd,od,uint`
2019-08-16 06:02:09 +08:00
|
|
|
// Objects are guaranteed to remain alive while this method
|
2018-09-27 02:50:39 +08:00
|
|
|
// is working, so it is safe to keep pointers here and below
|
|
|
|
in_objs_p.insert(&proto::origin_of(in_obj));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Recursive expression traversal
|
|
|
|
std::stack<cv::GProtoArg> data_objs(std::deque<cv::GProtoArg>(outs.begin(), outs.end()));
|
|
|
|
while (!data_objs.empty())
|
|
|
|
{
|
|
|
|
const auto obj = data_objs.top();
|
|
|
|
const auto &obj_p = proto::origin_of(obj);
|
|
|
|
data_objs.pop();
|
|
|
|
|
|
|
|
const auto &origin = obj_p;
|
|
|
|
origins.insert(origin); // TODO: Put Object description here later on
|
|
|
|
|
|
|
|
// If this Object is listed in the protocol, don't dive deeper (even
|
|
|
|
// if it is in fact a result of operation). Our computation is
|
|
|
|
// bounded by this data slot, so terminate this recursion path early.
|
|
|
|
if (in_objs_p.find(&obj_p) != in_objs_p.end())
|
|
|
|
{
|
|
|
|
reached_sources.visit(&obj_p);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const cv::GNode &node = origin.node;
|
|
|
|
switch (node.shape())
|
|
|
|
{
|
|
|
|
case cv::GNode::NodeShape::EMPTY:
|
|
|
|
// TODO: Own exception type?
|
|
|
|
util::throw_error(std::logic_error("Empty node reached!"));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case cv::GNode::NodeShape::PARAM:
|
|
|
|
case cv::GNode::NodeShape::CONST_BOUNDED:
|
|
|
|
// No preceding operation to this data object - so the data object is either a GComputation
|
|
|
|
// parameter or a constant (compile-time) value
|
|
|
|
// Record it to check if protocol matches expression tree later
|
|
|
|
if (!reached_sources.visited(&obj_p))
|
|
|
|
reached_sources.visit(&obj_p);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case cv::GNode::NodeShape::CALL:
|
|
|
|
if (!ops.visited(&node.priv()))
|
|
|
|
{
|
|
|
|
// This operation hasn't been visited yet - mark it so,
|
|
|
|
// then add its operands to stack to continue recursion.
|
|
|
|
ops.visit(&node.priv(), node);
|
|
|
|
|
2019-01-16 23:15:44 +08:00
|
|
|
const cv::GCall& call = origin.node.call();
|
2018-09-27 02:50:39 +08:00
|
|
|
const cv::GCall::Priv& call_p = call.priv();
|
|
|
|
|
|
|
|
// Put the outputs object description of the node
|
|
|
|
// so that they are not lost if they are not consumed by other operations
|
|
|
|
for (const auto &it : ade::util::indexed(call_p.m_k.outShapes))
|
|
|
|
{
|
|
|
|
std::size_t port = ade::util::index(it);
|
|
|
|
GShape shape = ade::util::value(it);
|
|
|
|
|
|
|
|
GOrigin org { shape, node, port};
|
|
|
|
origins.insert(org);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto &arg : call_p.m_args)
|
|
|
|
{
|
|
|
|
if (proto::is_dynamic(arg))
|
|
|
|
{
|
|
|
|
data_objs.push(proto::rewrap(arg)); // Dive deeper
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
// Unsupported node shape
|
|
|
|
GAPI_Assert(false);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if protocol mentions data_objs which weren't reached during traversal
|
|
|
|
const auto missing_reached_sources = [&reached_sources](GObjId p) {
|
|
|
|
return reached_sources.visited().find(p) == reached_sources.visited().end();
|
|
|
|
};
|
|
|
|
if (ade::util::any_of(in_objs_p, missing_reached_sources))
|
|
|
|
{
|
|
|
|
// TODO: Own exception type or a return code?
|
|
|
|
util::throw_error(std::logic_error("Data object listed in Protocol "
|
|
|
|
"wasn\'t reached during unroll"));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if there endpoint (parameter) data_objs which are not listed in protocol
|
|
|
|
const auto missing_in_proto = [&in_objs_p](GObjId p) {
|
|
|
|
return p->node.shape() != cv::GNode::NodeShape::CONST_BOUNDED &&
|
|
|
|
in_objs_p.find(p) == in_objs_p.end();
|
|
|
|
};
|
|
|
|
if (ade::util::any_of(reached_sources.visited(), missing_in_proto))
|
|
|
|
{
|
|
|
|
// TODO: Own exception type or a return code?
|
|
|
|
util::throw_error(std::logic_error("Data object reached during unroll "
|
|
|
|
"wasn\'t found in Protocol"));
|
|
|
|
}
|
|
|
|
|
|
|
|
return cv::gimpl::Unrolled{ops.tracked(), origins};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cv::gimpl::GModelBuilder::GModelBuilder(ade::Graph &g)
|
2019-04-18 02:54:47 +08:00
|
|
|
: m_g(g), m_gm(g)
|
2018-09-27 02:50:39 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
cv::gimpl::GModelBuilder::ProtoSlots
|
|
|
|
cv::gimpl::GModelBuilder::put(const GProtoArgs &ins, const GProtoArgs &outs)
|
|
|
|
{
|
|
|
|
const auto unrolled = cv::gimpl::unrollExpr(ins, outs);
|
|
|
|
|
|
|
|
// First, put all operations and its arguments into graph.
|
|
|
|
for (const auto &op_expr_node : unrolled.all_ops)
|
|
|
|
{
|
|
|
|
GAPI_Assert(op_expr_node.shape() == GNode::NodeShape::CALL);
|
|
|
|
const GCall& call = op_expr_node.call();
|
|
|
|
const GCall::Priv& call_p = call.priv();
|
|
|
|
ade::NodeHandle call_h = put_OpNode(op_expr_node);
|
|
|
|
|
|
|
|
for (const auto &it : ade::util::indexed(call_p.m_args))
|
|
|
|
{
|
|
|
|
const auto in_port = ade::util::index(it);
|
|
|
|
const auto& in_arg = ade::util::value(it);
|
|
|
|
|
|
|
|
if (proto::is_dynamic(in_arg))
|
|
|
|
{
|
|
|
|
ade::NodeHandle data_h = put_DataNode(proto::origin_of(in_arg));
|
2019-04-18 02:54:47 +08:00
|
|
|
cv::gimpl::GModel::linkIn(m_gm, call_h, data_h, in_port);
|
2018-09-27 02:50:39 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then iterate via all "origins", instantiate (if not yet) Data graph nodes
|
|
|
|
// and connect these nodes with their producers in graph
|
|
|
|
for (const auto &origin : unrolled.all_data)
|
|
|
|
{
|
|
|
|
const cv::GNode& prod = origin.node;
|
|
|
|
GAPI_Assert(prod.shape() != cv::GNode::NodeShape::EMPTY);
|
|
|
|
|
|
|
|
ade::NodeHandle data_h = put_DataNode(origin);
|
|
|
|
if (prod.shape() == cv::GNode::NodeShape::CALL)
|
|
|
|
{
|
|
|
|
ade::NodeHandle call_h = put_OpNode(prod);
|
2019-04-18 02:54:47 +08:00
|
|
|
cv::gimpl::GModel::linkOut(m_gm, call_h, data_h, origin.port);
|
2018-09-27 02:50:39 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark graph data nodes as INPUTs and OUTPUTs respectively (according to the protocol)
|
|
|
|
for (const auto &arg : ins)
|
|
|
|
{
|
|
|
|
ade::NodeHandle nh = put_DataNode(proto::origin_of(arg));
|
2019-04-18 02:54:47 +08:00
|
|
|
m_gm.metadata(nh).get<Data>().storage = Data::Storage::INPUT;
|
2018-09-27 02:50:39 +08:00
|
|
|
}
|
|
|
|
for (const auto &arg : outs)
|
|
|
|
{
|
|
|
|
ade::NodeHandle nh = put_DataNode(proto::origin_of(arg));
|
2019-04-18 02:54:47 +08:00
|
|
|
m_gm.metadata(nh).get<Data>().storage = Data::Storage::OUTPUT;
|
2018-09-27 02:50:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// And, finally, store data object layout in meta
|
2019-04-18 02:54:47 +08:00
|
|
|
GModel::LayoutGraph lg(m_g);
|
|
|
|
lg.metadata().set(Layout{m_graph_data});
|
2018-09-27 02:50:39 +08:00
|
|
|
|
|
|
|
// After graph is generated, specify which data objects are actually
|
|
|
|
// computation entry/exit points.
|
|
|
|
using NodeDescr = std::pair<std::vector<RcDesc>,
|
|
|
|
std::vector<ade::NodeHandle> >;
|
|
|
|
|
|
|
|
const auto get_proto_slots = [&](const GProtoArgs &proto) -> NodeDescr
|
|
|
|
{
|
|
|
|
NodeDescr slots;
|
|
|
|
|
|
|
|
slots.first.reserve(proto.size());
|
|
|
|
slots.second.reserve(proto.size());
|
|
|
|
|
|
|
|
for (const auto &arg : proto)
|
|
|
|
{
|
|
|
|
ade::NodeHandle nh = put_DataNode(proto::origin_of(arg));
|
2019-04-18 02:54:47 +08:00
|
|
|
const auto &desc = m_gm.metadata(nh).get<Data>();
|
2018-09-27 02:50:39 +08:00
|
|
|
//These extra empty {} are to please GCC (-Wmissing-field-initializers)
|
|
|
|
slots.first.push_back(RcDesc{desc.rc, desc.shape, {}});
|
|
|
|
slots.second.push_back(nh);
|
|
|
|
}
|
|
|
|
return slots;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto in_slots = get_proto_slots(ins);
|
|
|
|
auto out_slots = get_proto_slots(outs);
|
|
|
|
return ProtoSlots{in_slots.first, out_slots.first,
|
|
|
|
in_slots.second, out_slots.second};
|
|
|
|
}
|
|
|
|
|
|
|
|
ade::NodeHandle cv::gimpl::GModelBuilder::put_OpNode(const cv::GNode &node)
|
|
|
|
{
|
|
|
|
const auto& node_p = node.priv();
|
|
|
|
const auto it = m_graph_ops.find(&node_p);
|
|
|
|
if (it == m_graph_ops.end())
|
|
|
|
{
|
|
|
|
GAPI_Assert(node.shape() == GNode::NodeShape::CALL);
|
|
|
|
const auto &call_p = node.call().priv();
|
2019-04-18 02:54:47 +08:00
|
|
|
auto nh = cv::gimpl::GModel::mkOpNode(m_gm, call_p.m_k, call_p.m_args, node_p.m_island);
|
2018-09-27 02:50:39 +08:00
|
|
|
m_graph_ops[&node_p] = nh;
|
|
|
|
return nh;
|
|
|
|
}
|
|
|
|
else return it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: rename to get_DataNode (and same for Op)
|
|
|
|
ade::NodeHandle cv::gimpl::GModelBuilder::put_DataNode(const GOrigin &origin)
|
|
|
|
{
|
|
|
|
const auto it = m_graph_data.find(origin);
|
|
|
|
if (it == m_graph_data.end())
|
|
|
|
{
|
2019-04-18 02:54:47 +08:00
|
|
|
auto nh = cv::gimpl::GModel::mkDataNode(m_gm, origin);
|
2018-09-27 02:50:39 +08:00
|
|
|
m_graph_data[origin] = nh;
|
|
|
|
return nh;
|
|
|
|
}
|
|
|
|
else return it->second;
|
|
|
|
}
|