2024-03-09 12:43:09 +00:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
|
|
|
//*************************************************************************
|
2024-03-10 15:58:58 +00:00
|
|
|
// DESCRIPTION: Verilator: Multi-threaded code partitioning and ordering
|
2024-03-09 12:43:09 +00:00
|
|
|
//
|
|
|
|
|
// Code available from: https://verilator.org
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
2026-01-26 20:24:34 -05:00
|
|
|
// This program is free software; you can redistribute it and/or modify it
|
|
|
|
|
// under the terms of either the GNU Lesser General Public License Version 3
|
|
|
|
|
// or the Perl Artistic License Version 2.0.
|
|
|
|
|
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
|
2024-03-09 12:43:09 +00:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
|
|
|
|
// Parallel code ordering
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
|
|
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
|
|
2026-07-31 15:03:26 +01:00
|
|
|
#include "V3Ast.h"
|
2026-08-18 08:50:50 +02:00
|
|
|
#include "V3AstUserAllocator.h"
|
2025-06-27 20:38:01 -04:00
|
|
|
#include "V3Control.h"
|
2026-08-18 08:50:50 +02:00
|
|
|
#include "V3Error.h"
|
2024-03-16 14:02:17 +00:00
|
|
|
#include "V3ExecGraph.h"
|
2024-03-09 12:43:09 +00:00
|
|
|
#include "V3Graph.h"
|
|
|
|
|
#include "V3GraphStream.h"
|
|
|
|
|
#include "V3OrderCFuncEmitter.h"
|
|
|
|
|
#include "V3OrderInternal.h"
|
2026-07-31 15:03:26 +01:00
|
|
|
#include "V3OrderMTaskGraph.h"
|
2024-03-09 12:43:09 +00:00
|
|
|
|
2026-08-18 08:50:50 +02:00
|
|
|
#include <map>
|
2024-03-10 15:58:58 +00:00
|
|
|
#include <memory>
|
2024-03-09 12:43:09 +00:00
|
|
|
#include <unordered_map>
|
2026-08-18 08:50:50 +02:00
|
|
|
#include <vector>
|
2024-03-09 12:43:09 +00:00
|
|
|
|
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
|
|
2026-08-18 08:50:50 +02:00
|
|
|
//######################################################################
|
|
|
|
|
// Data hazard checker
|
|
|
|
|
|
|
|
|
|
// Reports read-write and write-write pairs on the same variable that
|
|
|
|
|
// are not ordered in the MTask graph.
|
|
|
|
|
static void checkDataHazards(OrderMTaskGraph& mTaskGraph) {
|
|
|
|
|
// Expensive, so only with '--debug-partition'
|
|
|
|
|
if (!mTaskGraph.slowAsserts()) return;
|
|
|
|
|
|
|
|
|
|
// Order MTasks by their stable ids, so the report is deterministic
|
|
|
|
|
struct MTaskIdLessThan final {
|
|
|
|
|
bool operator()(const LogicMTask* ap, const LogicMTask* bp) const { return *ap < *bp; }
|
|
|
|
|
};
|
|
|
|
|
struct VarInfo final {
|
|
|
|
|
bool m_seen = false; // Variable already appended to 'vscps'
|
|
|
|
|
// How each MTask accesses the variable, merged over the logic within that MTask
|
|
|
|
|
std::map<LogicMTask*, VAccess, MTaskIdLessThan> m_byMTask;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// AstVarScope::user1 -> VarInfo instance for the variable (via 'varInfos')
|
|
|
|
|
const VNUser1InUse user1InUse;
|
|
|
|
|
AstUser1Allocator<AstVarScope, VarInfo> varInfos;
|
|
|
|
|
|
|
|
|
|
// The variables accessed (in enumerated order, for stability).
|
|
|
|
|
std::vector<AstVarScope*> vscps;
|
|
|
|
|
|
|
|
|
|
// Gather how each MTask accesses each variable
|
|
|
|
|
for (V3GraphVertex& vtx : mTaskGraph.vertices()) {
|
|
|
|
|
LogicMTask& mtask = static_cast<LogicMTask&>(vtx);
|
|
|
|
|
for (const OrderMoveVertex& mVtx : mtask.vertexList()) {
|
|
|
|
|
const OrderLogicVertex* const lVtxp = mVtx.logicp();
|
|
|
|
|
if (!lVtxp) continue; // A variable vertex, which performs no access itself
|
|
|
|
|
for (const OrderLogicVertex::VarAccess& acc : lVtxp->varAccesses()) {
|
|
|
|
|
AstVarScope* const vscp = acc.m_vscp;
|
|
|
|
|
VarInfo& varInfo = varInfos(vscp);
|
|
|
|
|
if (!varInfo.m_seen) {
|
|
|
|
|
varInfo.m_seen = true;
|
|
|
|
|
vscps.push_back(vscp);
|
|
|
|
|
}
|
|
|
|
|
const auto pair = varInfo.m_byMTask.emplace(&mtask, acc.m_access);
|
|
|
|
|
// Merge the access kinds if this MTask already accessed this variable
|
|
|
|
|
if (!pair.second && pair.first->second != acc.m_access) {
|
|
|
|
|
pair.first->second = VAccess::READWRITE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Report every unordered pair of accessors where at least one side writes
|
|
|
|
|
AstVarScope* firstHazardp = nullptr; // First variable with a hazard, for the error below
|
|
|
|
|
for (AstVarScope* const vscp : vscps) {
|
|
|
|
|
const auto& byMTask = varInfos(vscp).m_byMTask;
|
|
|
|
|
for (auto aIt = byMTask.begin(); aIt != byMTask.end(); ++aIt) {
|
|
|
|
|
for (auto bIt = std::next(aIt); bIt != byMTask.end(); ++bIt) {
|
|
|
|
|
// Concurrent reads are not a hazard
|
|
|
|
|
if (aIt->second.isReadOnly() && bIt->second.isReadOnly()) continue;
|
|
|
|
|
LogicMTask* const ap = aIt->first;
|
|
|
|
|
LogicMTask* const bp = bIt->first;
|
|
|
|
|
if (mTaskGraph.pathExists(ap, bp, nullptr)) continue;
|
|
|
|
|
if (mTaskGraph.pathExists(bp, ap, nullptr)) continue;
|
|
|
|
|
// LCOV_EXCL_START
|
|
|
|
|
if (!firstHazardp) firstHazardp = vscp;
|
|
|
|
|
UINFO(0, "Data hazard: " << vscp->name() << " " << aIt->second.ascii() << " by mt"
|
|
|
|
|
<< ap->id() << ", " << bIt->second.ascii() << " by mt"
|
|
|
|
|
<< bp->id() << " (unordered)");
|
|
|
|
|
// LCOV_EXCL_STOP
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Fail if any hazards were found
|
|
|
|
|
if (firstHazardp) firstHazardp->v3fatalSrc("Data hazards found"); // LCOV_EXCL_BR_LINE
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-10 15:58:58 +00:00
|
|
|
//######################################################################
|
2026-07-31 15:03:26 +01:00
|
|
|
// Partitioner implementation
|
2024-03-10 15:58:58 +00:00
|
|
|
|
2026-07-31 15:03:26 +01:00
|
|
|
// Partitioner takes the fine-grained OrderMoveGraph from V3Order and collapses
|
|
|
|
|
// it into a coarse-grained graph of LogicMTask's, each of which contains of set
|
|
|
|
|
// of the logic nodes from the fine-grained graph.
|
|
|
|
|
|
|
|
|
|
static std::unique_ptr<OrderMTaskGraph> partition(OrderMoveGraph& moveGraph) {
|
|
|
|
|
// Build the initial MTask graph. Initially, each MTask just wraps one OrderMoveVertex. We will
|
|
|
|
|
// merge MTasks together and eventually each MTask will wrap a large number of OrderMoveVertex
|
|
|
|
|
// (and the logic nodes therein).
|
|
|
|
|
std::unique_ptr<OrderMTaskGraph> mTaskGraphp = OrderMTaskGraph::build(moveGraph);
|
|
|
|
|
mTaskGraphp->hashGraphDebug("initial MTask graph");
|
|
|
|
|
|
2026-08-18 08:50:50 +02:00
|
|
|
// Add edges to eliminate data hazards
|
2026-07-31 15:03:26 +01:00
|
|
|
OrderMTaskGraph::fixDataHazards(*mTaskGraphp);
|
|
|
|
|
mTaskGraphp->hashGraphDebug("MTask graph after fixDataHazards()");
|
|
|
|
|
|
|
|
|
|
// Merge MTask nodes together, repeatedly, until the critical path budget is reached. Coarsens
|
|
|
|
|
// the graph, usually by several orders of magnitude. Some tests disable this for stability,
|
|
|
|
|
// it should always be enabled in production.
|
|
|
|
|
if (v3Global.opt.threadsCoarsen()) {
|
|
|
|
|
const int nThreads = v3Global.opt.threads();
|
|
|
|
|
UASSERT(nThreads >= 2, "Should not reach Partitioner when --threads <= 1");
|
|
|
|
|
|
|
|
|
|
// Set critical path limit to roughly totalGraphCost / nThreads. Actually set it slighly
|
|
|
|
|
// lower, by a hardcoded fudge factor. This results in a smaller graph, which helps reduce
|
|
|
|
|
// fragmentation when scheduling them. TODO: What does this sentence mean?
|
|
|
|
|
const uint64_t fudgeNum = 3;
|
|
|
|
|
const uint64_t fudgeDen = 5;
|
|
|
|
|
const uint64_t limit = (mTaskGraphp->totalCost() * fudgeNum) / (nThreads * fudgeDen);
|
|
|
|
|
UINFO(4, "Partitioner set critical path limit = " << limit);
|
|
|
|
|
|
|
|
|
|
OrderMTaskGraph::contract(*mTaskGraphp, limit);
|
|
|
|
|
mTaskGraphp->hashGraphDebug("MTask graph after contract()");
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-18 08:50:50 +02:00
|
|
|
// Remove MTasks that have no logic in them, rerouting the edges
|
|
|
|
|
mTaskGraphp->removeEmptyMTasks();
|
|
|
|
|
mTaskGraphp->hashGraphDebug("MTask graph after removeEmptyMTasks()");
|
|
|
|
|
|
|
|
|
|
// Note this is OrderMTaskGraph::removeTransitiveEdges, which maintains graph consistency
|
2026-07-31 15:03:26 +01:00
|
|
|
mTaskGraphp->removeTransitiveEdges();
|
|
|
|
|
mTaskGraphp->hashGraphDebug("MTask graph after removeTransitiveEdges()");
|
|
|
|
|
|
2026-08-18 08:50:50 +02:00
|
|
|
// Check for data hazards the partitioning left unordered
|
|
|
|
|
checkDataHazards(*mTaskGraphp);
|
|
|
|
|
|
|
|
|
|
// Set OrderMoveVertex::userp to indicate the mtask it is part of.
|
2026-07-31 15:03:26 +01:00
|
|
|
moveGraph.userClearVertices();
|
|
|
|
|
for (V3GraphVertex* const vtxp : mTaskGraphp->vertices().unlinkable()) {
|
|
|
|
|
LogicMTask* const mtaskp = vtxp->as<LogicMTask>();
|
|
|
|
|
OrderMoveVertex::List& vertexList = mtaskp->vertexList();
|
|
|
|
|
while (OrderMoveVertex* const mVtxp = vertexList.unlinkFront()) mVtxp->userp(mtaskp);
|
2024-03-10 15:58:58 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-31 15:03:26 +01:00
|
|
|
// Return the resulting MTask graph
|
|
|
|
|
return mTaskGraphp;
|
|
|
|
|
}
|
2024-03-10 15:58:58 +00:00
|
|
|
|
2025-03-24 23:39:29 +01:00
|
|
|
//######################################################################
|
2026-07-31 15:03:26 +01:00
|
|
|
// DpiThreadsVisitor - Finds number of threads used by an ExecMTask
|
2025-03-24 23:39:29 +01:00
|
|
|
|
|
|
|
|
class DpiThreadsVisitor final : public VNVisitorConst {
|
|
|
|
|
int m_threads = 1; // Max number of threads used by this mtask
|
|
|
|
|
|
|
|
|
|
// METHODS
|
|
|
|
|
void visit(AstCFunc* nodep) override {
|
2025-06-27 20:38:01 -04:00
|
|
|
m_threads = std::max(m_threads, V3Control::getHierWorkers(nodep->cname()));
|
2025-03-24 23:39:29 +01:00
|
|
|
iterateChildrenConst(nodep);
|
|
|
|
|
}
|
2025-05-20 18:15:09 +02:00
|
|
|
void visit(AstNodeCCall* nodep) override { iterateConst(nodep->funcp()); }
|
2025-03-24 23:39:29 +01:00
|
|
|
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
|
|
|
|
|
|
|
|
|
|
// CONSTRUCTORS
|
2025-11-03 07:32:03 +01:00
|
|
|
explicit DpiThreadsVisitor(AstCFunc* nodep) { iterateConst(nodep); }
|
2025-03-24 23:39:29 +01:00
|
|
|
~DpiThreadsVisitor() override = default;
|
|
|
|
|
VL_UNCOPYABLE(DpiThreadsVisitor);
|
2024-03-10 18:15:45 +00:00
|
|
|
|
|
|
|
|
public:
|
2026-07-31 15:03:26 +01:00
|
|
|
// Number of threads occupied by the given MTask
|
|
|
|
|
static int apply(const ExecMTask* mTaskp) {
|
|
|
|
|
return DpiThreadsVisitor{mTaskp->funcp()}.m_threads;
|
2024-03-10 18:15:45 +00:00
|
|
|
}
|
2024-03-10 15:58:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//######################################################################
|
2026-07-31 15:03:26 +01:00
|
|
|
// Entry point
|
2024-03-10 15:58:58 +00:00
|
|
|
|
2026-07-31 15:03:26 +01:00
|
|
|
AstNodeStmt* V3Order::createParallel(OrderMoveGraph& moveGraph, const std::string& tag,
|
|
|
|
|
bool slow) {
|
2024-03-09 21:19:35 +00:00
|
|
|
UINFO(2, " Constructing parallel code for '" + tag + "'");
|
|
|
|
|
|
2026-07-31 15:03:26 +01:00
|
|
|
// For nondeterminism debugging
|
|
|
|
|
moveGraph.hashGraphDebug("V3Order::createParallel input OrderMoveGraph");
|
|
|
|
|
moveGraph.orderGraph().hashGraphDebug("V3Order::createParallel input OrderGraph");
|
2024-03-09 12:43:09 +00:00
|
|
|
|
2025-10-31 19:29:11 +01:00
|
|
|
// Partition moveGraph into LogicMTask's. The partitioner will set userp() on each logic
|
|
|
|
|
// vertex in the moveGraph to the MTask it belongs to.
|
2026-07-31 15:03:26 +01:00
|
|
|
const std::unique_ptr<OrderMTaskGraph> mTaskGraphp = partition(moveGraph);
|
2025-10-31 19:29:11 +01:00
|
|
|
if (dumpGraphLevel() >= 9) moveGraph.dumpDotFilePrefixed(tag + "_ordermv_mtasks");
|
2024-03-17 13:15:39 +00:00
|
|
|
|
|
|
|
|
// Some variable OrderMoveVertices are not assigned to an MTask. Reroute and delete these.
|
2025-10-31 19:29:11 +01:00
|
|
|
for (V3GraphVertex* const vtxp : moveGraph.vertices().unlinkable()) {
|
2024-03-17 13:15:39 +00:00
|
|
|
OrderMoveVertex* const mVtxp = vtxp->as<OrderMoveVertex>();
|
|
|
|
|
if (!mVtxp->userp()) {
|
|
|
|
|
UASSERT_OBJ(!mVtxp->logicp(), mVtxp, "Logic OrderMoveVertex not assigned to mtask");
|
2025-10-31 19:29:11 +01:00
|
|
|
mVtxp->rerouteEdges(&moveGraph);
|
|
|
|
|
VL_DO_DANGLING(mVtxp->unlinkDelete(&moveGraph), mVtxp);
|
2024-03-17 13:15:39 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove all edges from the move graph that cross between MTasks. Add logic to MTask lists.
|
2025-10-31 19:29:11 +01:00
|
|
|
for (V3GraphVertex& vtx : moveGraph.vertices()) {
|
2024-03-25 23:06:25 +00:00
|
|
|
OrderMoveVertex* const mVtxp = vtx.as<OrderMoveVertex>();
|
2024-03-17 13:15:39 +00:00
|
|
|
LogicMTask* const mtaskp = static_cast<LogicMTask*>(mVtxp->userp());
|
|
|
|
|
// Add to list in MTask, in MoveGraph order. This should not be necessary, but see #4993.
|
2024-03-25 23:06:25 +00:00
|
|
|
mtaskp->vertexList().linkBack(mVtxp);
|
2024-03-17 13:15:39 +00:00
|
|
|
// Remove edges crossing between MTasks
|
2024-03-25 23:06:25 +00:00
|
|
|
for (V3GraphEdge* const edgep : mVtxp->outEdges().unlinkable()) {
|
2024-03-17 13:15:39 +00:00
|
|
|
const OrderMoveVertex* const toMVtxp = edgep->top()->as<OrderMoveVertex>();
|
|
|
|
|
if (mtaskp != toMVtxp->userp()) VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-31 19:29:11 +01:00
|
|
|
if (dumpGraphLevel() >= 9) moveGraph.dumpDotFilePrefixed(tag + "_ordermv_pruned");
|
2024-03-17 13:15:39 +00:00
|
|
|
|
|
|
|
|
// Create the AstExecGraph node which represents the execution of the MTask graph.
|
2025-11-03 07:32:03 +01:00
|
|
|
FileLine* const flp = v3Global.rootp()->fileline();
|
|
|
|
|
AstScope* const scopep = v3Global.rootp()->topScopep()->scopep();
|
|
|
|
|
AstExecGraph* const execGraphp = new AstExecGraph{flp, tag};
|
2024-03-10 18:15:45 +00:00
|
|
|
V3Graph* const depGraphp = execGraphp->depGraphp();
|
2024-03-09 12:43:09 +00:00
|
|
|
|
2024-03-17 13:15:39 +00:00
|
|
|
// Translate the LogicMTask graph into the corresponding ExecMTask graph,
|
|
|
|
|
// which will outlive ordering.
|
|
|
|
|
std::unordered_map<const LogicMTask*, ExecMTask*> logicMTaskToExecMTask;
|
2025-10-31 19:29:11 +01:00
|
|
|
OrderMoveGraphSerializer serializer{moveGraph};
|
2024-03-09 12:43:09 +00:00
|
|
|
V3OrderCFuncEmitter emitter{tag, slow};
|
2026-07-31 15:03:26 +01:00
|
|
|
// Sort LogicMTask vertices by their serial IDs.
|
|
|
|
|
struct MTaskVxIdLessThan final {
|
|
|
|
|
bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const {
|
|
|
|
|
return lhsp->as<LogicMTask>()->id() < rhsp->as<LogicMTask>()->id();
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-03-10 18:15:45 +00:00
|
|
|
GraphStream<MTaskVxIdLessThan> mtaskStream{mTaskGraphp.get()};
|
2024-03-09 12:43:09 +00:00
|
|
|
while (const V3GraphVertex* const vtxp = mtaskStream.nextp()) {
|
2024-03-17 13:15:39 +00:00
|
|
|
const LogicMTask* const cMTaskp = vtxp->as<LogicMTask>();
|
|
|
|
|
LogicMTask* const mTaskp = const_cast<LogicMTask*>(cMTaskp);
|
|
|
|
|
|
2026-08-18 08:50:50 +02:00
|
|
|
// The entry and exit vertices only anchor the graph, they hold no logic and
|
|
|
|
|
// must not become ExecMTasks.
|
|
|
|
|
if (mTaskp == mTaskGraphp->entryp() || mTaskp == mTaskGraphp->exitp()) {
|
|
|
|
|
UASSERT_OBJ(mTaskp->vertexList().empty(), mTaskp,
|
|
|
|
|
"Entry and exit vertices should have no logic");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-17 13:15:39 +00:00
|
|
|
// Add initially ready vertices within this MTask to the serializer as seeds,
|
2024-03-25 23:06:25 +00:00
|
|
|
// and unlink them from the vertex list in the MTask as we go. (The serializer
|
|
|
|
|
// uses the list links in the vertex, so must unlink it here.)
|
|
|
|
|
while (OrderMoveVertex* const mVtxp = mTaskp->vertexList().unlinkFront()) {
|
|
|
|
|
if (mVtxp->inEmpty()) serializer.addSeed(mVtxp);
|
2024-03-17 13:15:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Emit all logic within the MTask as they become ready
|
|
|
|
|
OrderMoveDomScope* prevDomScopep = nullptr;
|
|
|
|
|
while (OrderMoveVertex* const mVtxp = serializer.getNext()) {
|
|
|
|
|
// We only really care about logic vertices
|
|
|
|
|
if (OrderLogicVertex* const logicp = mVtxp->logicp()) {
|
|
|
|
|
// Force a new function if the domain or scope changed, for better combining.
|
|
|
|
|
OrderMoveDomScope* const domScopep = &mVtxp->domScope();
|
|
|
|
|
if (domScopep != prevDomScopep) emitter.forceNewFunction();
|
|
|
|
|
prevDomScopep = domScopep;
|
|
|
|
|
// Emit the logic under this vertex
|
|
|
|
|
emitter.emitLogic(logicp);
|
|
|
|
|
}
|
|
|
|
|
// Can delete the vertex now
|
2025-10-31 19:29:11 +01:00
|
|
|
VL_DO_DANGLING(mVtxp->unlinkDelete(&moveGraph), mVtxp);
|
2024-03-17 13:15:39 +00:00
|
|
|
}
|
2024-03-09 12:43:09 +00:00
|
|
|
|
2024-03-17 13:15:39 +00:00
|
|
|
// Create the ExecMTask
|
2025-11-03 07:32:03 +01:00
|
|
|
ExecMTask* const execMTaskp = new ExecMTask{execGraphp, scopep, emitter.getStmts()};
|
|
|
|
|
if (!v3Global.opt.hierBlocks().empty()) {
|
2026-07-31 15:03:26 +01:00
|
|
|
execMTaskp->threads(DpiThreadsVisitor::apply(execMTaskp));
|
2025-11-03 07:32:03 +01:00
|
|
|
}
|
2024-03-17 13:15:39 +00:00
|
|
|
const bool newEntry = logicMTaskToExecMTask.emplace(mTaskp, execMTaskp).second;
|
|
|
|
|
UASSERT_OBJ(newEntry, mTaskp, "LogicMTasks should be processed in dependencyorder");
|
|
|
|
|
UINFO(3, "Final '" << tag << "' LogicMTask " << mTaskp->id() << " maps to ExecMTask"
|
2025-05-22 20:29:32 -04:00
|
|
|
<< execMTaskp->id());
|
2024-03-17 13:15:39 +00:00
|
|
|
|
2025-11-03 07:32:03 +01:00
|
|
|
// For code analysis purposes, we can pretend the AstExecGraph runs the
|
|
|
|
|
// MTasks sequentially, in some topological order that respects edges.
|
|
|
|
|
// The order they are created here happens to be just such an order.
|
|
|
|
|
AstCCall* const callp = new AstCCall{flp, execMTaskp->funcp()};
|
|
|
|
|
callp->dtypeSetVoid();
|
|
|
|
|
execGraphp->addStmtsp(callp->makeStmt());
|
|
|
|
|
|
2024-03-17 13:15:39 +00:00
|
|
|
// Add the dependency edges between ExecMTasks
|
2024-03-25 23:06:25 +00:00
|
|
|
for (const V3GraphEdge& edge : mTaskp->inEdges()) {
|
|
|
|
|
const V3GraphVertex* fromVxp = edge.fromp();
|
2024-03-10 18:15:45 +00:00
|
|
|
const LogicMTask* const fromp = fromVxp->as<const LogicMTask>();
|
2026-08-18 08:50:50 +02:00
|
|
|
// Skip the entry vertex, which has no ExecMTask
|
|
|
|
|
if (fromp == mTaskGraphp->entryp()) continue;
|
2024-03-17 13:15:39 +00:00
|
|
|
new V3GraphEdge{depGraphp, logicMTaskToExecMTask.at(fromp), execMTaskp, 1};
|
2024-03-09 12:43:09 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-17 13:15:39 +00:00
|
|
|
// Delete the remaining variable vertices
|
2025-10-31 19:29:11 +01:00
|
|
|
for (V3GraphVertex* const vtxp : moveGraph.vertices().unlinkable()) {
|
2024-03-17 13:15:39 +00:00
|
|
|
if (!vtxp->as<OrderMoveVertex>()->logicp()) {
|
2025-10-31 19:29:11 +01:00
|
|
|
VL_DO_DANGLING(vtxp->unlinkDelete(&moveGraph), vtxp);
|
2024-03-17 13:15:39 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-09 12:43:09 +00:00
|
|
|
return execGraphp;
|
|
|
|
|
}
|