Support disable fork (#4125) (#4569)

This commit is contained in:
Aleksander Kiryk
2023-10-16 14:02:29 +02:00
committed by GitHub
parent 25dde58297
commit ad3bcbb1bb
26 changed files with 441 additions and 87 deletions
+102 -43
View File
@@ -12,11 +12,27 @@
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
// TimingSuspendableVisitor locates all C++ functions and processes that contain timing controls,
// and marks them as suspendable. If a process calls a suspendable function, then it is also marked
// as suspendable. If a function calls or overrides a suspendable function, it is also marked as
// suspendable. TimingSuspendableVisitor creates a dependency graph to propagate this property. It
// does not perform any AST transformations.
// TimingSuspendableVisitor does not perform any AST transformations.
// Instead it propagates two types of flags:
// - flag "suspendable": (for detecting what will need to become a coroutine)
// The visitor locates all C++ functions and processes that contain timing controls,
// and marks them as suspendable. If a process calls a suspendable function,
// then it is also marked as suspendable. If a function calls or overrides
// a suspendable function, it is also marked as suspendable.
// TimingSuspendableVisitor creates a dependency graph to propagate this property.
// - flag "needs process": (for detecting what needs a VlProcess argument in signature)
// The visitor distinguishes 4 types of nodes:
// - T_ALLOCS_PROC: nodes that can allocate VlProcess (forks, always, initial etc.),
// - T_FORCES_PROC: nodes that make it necessary for the previous type to allocate VlProcess
// (like process::self which then wraps it, allowing use inside Verilog).
// - T_NEEDS_PROC: nodes that should obtain VlProcess if it will be allocated
// (all of the previous type + timing controls, so they could update process state),
// - T_HAS_PROC: nodes that are going to be emitted with a VlProcess argument.
// T_FORCES_PROC and T_NEEDS_PROC are propagated upwards up to the nodes of type T_ALLOCS_PROC,
// this is to detect which processes have to allocate VlProcess. Then nodes of these processes
// get marked as T_HAS_PROC and the flag is propagated downwards through nodes
// type T_NEEDS_PROC. Using only nodes type T_NEEDS_PROC assures the flags are only propagated
// through paths leading to nodes that actually use VlProcess.
//
// TimingControlVisitor is the one that actually performs transformations:
// - for each intra-assignment timing control:
@@ -70,8 +86,10 @@ VL_DEFINE_DEBUG_FUNCTIONS;
enum NodeFlag : uint8_t {
T_SUSPENDEE = 1 << 0, // Suspendable (due to dependence on another suspendable)
T_SUSPENDER = 1 << 1, // Suspendable (has timing control)
T_HAS_PROC = 1 << 2, // Has an associated std::process
T_CALLS_PROC_SELF = 1 << 3, // Calls std::process::self
T_ALLOCS_PROC = 1 << 2, // Can allocate VlProcess
T_FORCES_PROC = 1 << 3, // Forces VlProcess allocation
T_NEEDS_PROC = 1 << 4, // Needs access to VlProcess if it's allocated
T_HAS_PROC = 1 << 5, // Has VlProcess argument in the signature
};
enum ForkType : uint8_t {
@@ -86,6 +104,11 @@ enum PropagationType : uint8_t {
P_SIGNATURE = 3, // Propagation required to maintain C++ function's signature requirements
};
// Add timing flag to a node
static void addFlags(AstNode* const nodep, uint8_t flags) { nodep->user2(nodep->user2() | flags); }
// Check if a node has ALL of the expected flags set
static bool hasFlags(AstNode* const nodep, uint8_t flags) { return !(~nodep->user2() & flags); }
// ######################################################################
// Detect nodes affected by timing and/or requiring a process
@@ -126,8 +149,8 @@ private:
class SuspendDepVtx final : public DepVtx {
VL_RTTI_IMPL(SuspendDepVtx, DepVtx)
string dotColor() const override {
if (nodep()->user2() & T_SUSPENDER) return "red";
if (nodep()->user2() & T_SUSPENDEE) return "blue";
if (hasFlags(nodep(), T_SUSPENDER)) return "red";
if (hasFlags(nodep(), T_SUSPENDEE)) return "blue";
return "black";
}
@@ -140,8 +163,9 @@ private:
class NeedsProcDepVtx final : public DepVtx {
VL_RTTI_IMPL(NeedsProcDepVtx, DepVtx)
string dotColor() const override {
if (nodep()->user2() & T_CALLS_PROC_SELF) return "red";
if (nodep()->user2() & T_HAS_PROC) return "blue";
if (hasFlags(nodep(), T_HAS_PROC)) return "blue";
if (hasFlags(nodep(), T_NEEDS_PROC)) return "green";
if (hasFlags(nodep(), T_FORCES_PROC)) return "red";
return "black";
}
@@ -198,10 +222,10 @@ private:
if (!nodep->user5p()) nodep->user5p(new NeedsProcDepVtx{&m_procGraph, nodep, classp});
return nodep->user5u().to<NeedsProcDepVtx*>();
}
// Set timing flag of a node
// Pass timing flag between nodes
bool passFlag(const AstNode* from, AstNode* to, NodeFlag flag) {
if ((from->user2() & flag) && !(to->user2() & flag)) {
to->user2(to->user2() | flag);
addFlags(to, flag);
return true;
}
return false;
@@ -216,6 +240,15 @@ private:
}
}
template <typename Predicate>
void propagateFlagsIf(DepVtx* const vxp, NodeFlag flag, Predicate p) {
auto* const parentp = vxp->nodep();
for (V3GraphEdge* edgep = vxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
auto* const depVxp = static_cast<DepVtx*>(edgep->top());
AstNode* const depp = depVxp->nodep();
if (p(edgep) && passFlag(parentp, depp, flag)) propagateFlagsIf(depVxp, flag, p);
}
}
template <typename Predicate>
void propagateFlagsReversedIf(DepVtx* const vxp, NodeFlag flag, Predicate p) {
auto* const parentp = vxp->nodep();
for (V3GraphEdge* edgep = vxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
@@ -236,17 +269,22 @@ private:
void visit(AstNodeProcedure* nodep) override {
VL_RESTORER(m_procp);
m_procp = nodep;
if (nodep->needProcess()) nodep->user2(T_HAS_PROC | T_CALLS_PROC_SELF);
getNeedsProcDepVtx(nodep);
addFlags(nodep, T_ALLOCS_PROC);
if (VN_IS(nodep, Always)) {
UINFO(1, "Always does " << (nodep->needProcess() ? "" : "NOT ") << "need process\n");
}
iterateChildren(nodep);
}
void visit(AstDisableFork* nodep) override {
visit(static_cast<AstNode*>(nodep));
addFlags(m_procp, T_FORCES_PROC | T_NEEDS_PROC);
}
void visit(AstCFunc* nodep) override {
VL_RESTORER(m_procp);
m_procp = nodep;
iterateChildren(nodep);
if (nodep->needProcess()) nodep->user2(T_HAS_PROC | T_CALLS_PROC_SELF);
if (nodep->needProcess()) addFlags(nodep, T_FORCES_PROC | T_NEEDS_PROC);
DepVtx* const sVxp = getSuspendDepVtx(nodep);
DepVtx* const pVxp = getNeedsProcDepVtx(nodep);
if (!m_classp) return;
@@ -287,9 +325,12 @@ private:
new V3GraphEdge{&m_suspGraph, getSuspendDepVtx(nodep->funcp()),
getSuspendDepVtx(m_procp), m_underFork ? P_FORK : P_CALL};
if (!m_underFork)
new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep->funcp()),
getNeedsProcDepVtx(m_procp), P_CALL};
new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep->funcp()),
getNeedsProcDepVtx(m_procp), P_CALL};
if (m_underFork && !(m_underFork & F_MIGHT_SUSPEND)) {
addFlags(nodep, T_NEEDS_PROC | T_ALLOCS_PROC);
}
iterateChildren(nodep);
}
@@ -301,9 +342,12 @@ private:
new V3GraphEdge{&m_suspGraph, getSuspendDepVtx(nodep), getSuspendDepVtx(m_procp),
m_underFork ? P_FORK : P_CALL};
if (!m_underFork)
new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep), getNeedsProcDepVtx(m_procp),
P_CALL};
new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep), getNeedsProcDepVtx(m_procp),
P_CALL};
if (m_underFork && !(m_underFork & F_MIGHT_SUSPEND)) {
addFlags(nodep, T_NEEDS_PROC | T_ALLOCS_PROC);
}
m_procp = nodep;
m_underFork = 0;
@@ -316,7 +360,7 @@ private:
// so that transformForks() in V3SchedTiming gets called and
// removes all forks and begins
if (nodep->isTimingControl() && m_procp) {
m_procp->user2(T_SUSPENDEE | T_SUSPENDER);
addFlags(m_procp, T_SUSPENDEE | T_SUSPENDER);
m_underFork |= F_MIGHT_SUSPEND;
}
m_underFork |= F_MIGHT_NEED_PROC;
@@ -325,7 +369,7 @@ private:
void visit(AstNode* nodep) override {
if (nodep->isTimingControl()) {
v3Global.setUsesTiming();
if (m_procp) m_procp->user2(T_SUSPENDEE | T_SUSPENDER);
if (m_procp) addFlags(m_procp, T_SUSPENDEE | T_SUSPENDER | T_NEEDS_PROC);
}
iterateChildren(nodep);
}
@@ -342,22 +386,35 @@ public:
// Propagate suspendability
for (V3GraphVertex* vxp = m_suspGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
DepVtx* const depVxp = static_cast<DepVtx*>(vxp);
if (depVxp->nodep()->user2() & T_SUSPENDEE) propagateFlags(depVxp, T_SUSPENDEE);
if (hasFlags(depVxp->nodep(), T_SUSPENDEE)) propagateFlags(depVxp, T_SUSPENDEE);
}
if (dumpGraphLevel() >= 6) m_suspGraph.dumpDotFilePrefixed("timing_deps");
// Propagate process
// Propagate T_HAS_PROCESS
for (V3GraphVertex* vxp = m_procGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
DepVtx* const depVxp = static_cast<DepVtx*>(vxp);
if (depVxp->nodep()->user2() & T_HAS_PROC) propagateFlags(depVxp, T_HAS_PROC);
}
// Propagate process downwards (from caller to callee) for suspendable calls
for (V3GraphVertex* vxp = m_suspGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
DepVtx* const depVxp = static_cast<DepVtx*>(vxp);
if (depVxp->nodep()->user2() & T_HAS_PROC)
propagateFlagsReversedIf(depVxp, T_HAS_PROC, [&](const V3GraphEdge* e) -> bool {
return (e->weight() != P_FORK)
&& (static_cast<DepVtx*>(e->top())->nodep()->user2() & T_SUSPENDEE);
// Find processes that'll allocate VlProcess
if (hasFlags(depVxp->nodep(), T_FORCES_PROC)) {
propagateFlagsIf(depVxp, T_FORCES_PROC, [&](const V3GraphEdge* e) -> bool {
return !hasFlags(static_cast<DepVtx*>(e->fromp())->nodep(), T_ALLOCS_PROC);
});
}
// Mark nodes on paths between processes and statements that use VlProcess
if (hasFlags(depVxp->nodep(), T_NEEDS_PROC)) {
propagateFlagsIf(depVxp, T_NEEDS_PROC, [&](const V3GraphEdge* e) -> bool {
return !hasFlags(static_cast<DepVtx*>(e->top())->nodep(), T_ALLOCS_PROC);
});
}
}
for (V3GraphVertex* vxp = m_procGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
DepVtx* const depVxp = static_cast<DepVtx*>(vxp);
// Mark nodes that will be emitted with a VlProcess argument
if (hasFlags(depVxp->nodep(), T_ALLOCS_PROC | T_FORCES_PROC)) {
addFlags(depVxp->nodep(), T_HAS_PROC);
propagateFlagsReversedIf(depVxp, T_HAS_PROC, [&](const V3GraphEdge* e) -> bool {
return hasFlags(static_cast<DepVtx*>(e->fromp())->nodep(), T_NEEDS_PROC);
});
}
}
if (dumpGraphLevel() >= 6) m_procGraph.dumpDotFilePrefixed("proc_deps");
}
@@ -586,7 +643,7 @@ private:
void addProcessInfo(AstCMethodHard* const methodp) const {
FileLine* const flp = methodp->fileline();
AstCExpr* const ap = new AstCExpr{
flp, m_procp && (m_procp->user2() & T_HAS_PROC) ? "vlProcess" : "nullptr", 0};
flp, m_procp && (hasFlags(m_procp, T_HAS_PROC)) ? "vlProcess" : "nullptr", 0};
ap->dtypeSetVoid();
methodp->addPinsp(ap);
}
@@ -697,8 +754,8 @@ private:
VL_RESTORER(m_procp);
m_procp = nodep;
iterateChildren(nodep);
if (nodep->user2() & T_SUSPENDEE) nodep->setSuspendable();
if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess();
if (hasFlags(nodep, T_SUSPENDEE)) nodep->setSuspendable();
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
}
void visit(AstInitial* nodep) override {
visit(static_cast<AstNodeProcedure*>(nodep));
@@ -719,11 +776,11 @@ private:
// Workaround for killing `always` processes (doing that is pretty much UB)
// TODO: Disallow killing `always` at runtime (throw an error)
if (nodep->user2() & T_HAS_PROC) nodep->user2(nodep->user2() | T_SUSPENDEE);
if (hasFlags(nodep, T_HAS_PROC)) addFlags(nodep, T_SUSPENDEE);
iterateChildren(nodep);
if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess();
if (!(nodep->user2() & T_SUSPENDEE)) return;
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
if (!hasFlags(nodep, T_SUSPENDEE)) return;
nodep->setSuspendable();
FileLine* const flp = nodep->fileline();
AstSenTree* const sensesp = m_activep->sensesp();
@@ -744,8 +801,8 @@ private:
VL_RESTORER(m_procp);
m_procp = nodep;
iterateChildren(nodep);
if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess();
if (!(nodep->user2() & T_SUSPENDEE)) return;
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
if (!(hasFlags(nodep, T_SUSPENDEE))) return;
nodep->rtnType("VlCoroutine");
// If in a class, create a shared pointer to 'this'
@@ -768,7 +825,7 @@ private:
}
}
void visit(AstNodeCCall* nodep) override {
if ((nodep->funcp()->user2() & T_SUSPENDEE) && !nodep->user1SetOnce()) { // If suspendable
if (hasFlags(nodep->funcp(), T_SUSPENDEE) && !nodep->user1SetOnce()) { // If suspendable
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
AstCAwait* const awaitp = new AstCAwait{nodep->fileline(), nodep};
@@ -1048,6 +1105,7 @@ private:
void visit(AstBegin* nodep) override {
VL_RESTORER(m_procp);
m_procp = nodep;
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
iterateChildren(nodep);
}
void visit(AstFork* nodep) override {
@@ -1061,6 +1119,7 @@ private:
while (stmtp) {
if (!VN_IS(stmtp, Begin)) {
auto* const beginp = new AstBegin{stmtp->fileline(), "", nullptr};
if (hasFlags(stmtp, T_HAS_PROC)) addFlags(beginp, T_HAS_PROC);
stmtp->replaceWith(beginp);
beginp->addStmtsp(stmtp);
stmtp = beginp;