verilator/src/V3LinkJump.cpp

657 lines
30 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Replace return/continue with jumps
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// 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
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
// V3LinkJump's Transformations:
//
// Each module:
// Look for BEGINs
// BEGIN(VAR...) -> VAR ... {renamed}
// FOR -> WHILEs
//
// Add JumpLabel which branches to after statements within JumpLabel
// RETURN -> JUMPBLOCK(statements with RETURN changed to JUMPGO, ..., JUMPLABEL)
// WHILE(... BREAK) -> JUMPBLOCK(WHILE(... statements with BREAK changed to JUMPGO),
// ... JUMPLABEL)
// WHILE(... CONTINUE) -> WHILE(JUMPBLOCK(... statements with CONTINUE changed to JUMPGO,
// ... JUMPPABEL))
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3LinkJump.h"
#include "V3AstUserAllocator.h"
#include "V3Error.h"
#include "V3UniqueNames.h"
#include <unordered_map>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
class LinkJumpVisitor final : public VNVisitor {
// NODE STATE
// AstBegin/etc::user1() -> AstJumpBlock*, for body of this loop
// AstFinish::user1() -> bool, processed
// AstNode::user2() -> AstJumpBlock*, for this block
// AstNodeBegin::user3() -> bool, true if contains a fork
const VNUser1InUse m_user1InUse;
const VNUser2InUse m_user2InUse;
const VNUser3InUse m_user3InUse;
// STATE
AstNodeModule* m_modp = nullptr; // Current module
AstNodeFTask* m_ftaskp = nullptr; // Current function/task
AstNode* m_loopp = nullptr; // Current loop
AstRandSequence* m_randsequencep = nullptr; // Current randsequence
bool m_loopInc = false; // In loop increment
bool m_inFork = false; // Under fork
int m_modRepeatNum = 0; // Repeat counter
VOptionBool m_unrollFull; // Pragma full, disable, or default unrolling
std::vector<AstNodeBlock*> m_blockStack; // All begin blocks above current node
V3UniqueNames m_queueNames{
"__VprocessQueue"}; // Names for queues needed for 'disable' handling
std::unordered_map<const AstTask*, AstVar*> m_taskDisableQueues; // Per-task process queues
std::unordered_map<const AstBegin*, AstVar*> m_beginDisableQueues; // Per-begin process queues
std::unordered_map<const AstFork*, AstVar*> m_forkDisableQueues; // Per-fork process queues
std::unordered_map<const AstTask*, AstBegin*>
m_taskDisableBegins; // Per-task process wrappers
std::unordered_map<const AstBegin*, AstBegin*>
m_beginDisableBegins; // Per-begin process wrappers
// METHODS
// Get (and create if necessary) the JumpBlock for this statement
AstJumpBlock* getJumpBlock(AstNode* nodep, bool endOfIter) {
// Wrap 'nodep' in JumpBlock. If loop, wrap the body instead if endOfIter is true
UINFO(4, "Create JumpBlock for " << nodep);
// Made it previously? We always jump to the end, so this works out
if (endOfIter) {
if (nodep->user1p()) return VN_AS(nodep->user1p(), JumpBlock);
} else {
if (nodep->user2p()) return VN_AS(nodep->user2p(), JumpBlock);
}
AstNode* underp = nullptr;
bool under_and_next = true;
if (AstBegin* const blockp = VN_CAST(nodep, Begin)) {
UASSERT_OBJ(!endOfIter, nodep, "No endOfIter for Begin");
underp = blockp->stmtsp();
} else if (AstNodeFTask* const fTaskp = VN_CAST(nodep, NodeFTask)) {
UASSERT_OBJ(!endOfIter, nodep, "No endOfIter for FTask");
underp = fTaskp->stmtsp();
} else if (AstForeach* const foreachp = VN_CAST(nodep, Foreach)) {
if (endOfIter) {
underp = foreachp->bodyp();
// Keep a LoopTest **at the front** outside the jump block
if (VN_IS(underp, LoopTest)) underp = underp->nextp();
} else {
underp = nodep;
under_and_next = false; // IE we skip the entire foreach
}
} else if (AstLoop* const loopp = VN_CAST(nodep, Loop)) {
if (endOfIter) {
underp = loopp->stmtsp();
} else {
underp = nodep;
under_and_next = false; // IE we skip the entire loop
}
} else {
nodep->v3fatalSrc("Unknown jump point for break/disable/continue");
return nullptr;
}
// Skip over variables as we'll just move them in a moment
// Also this would otherwise prevent us from using a label twice
// see t_func_return test.
while (underp && VN_IS(underp, Var)) underp = underp->nextp();
UASSERT_OBJ(underp, nodep, "Break/disable/continue not under expected statement");
UINFO(5, " Underpoint is " << underp);
// If already wrapped, we are done ...
if (!underp->nextp() || !under_and_next) {
if (AstJumpBlock* const blockp = VN_CAST(underp, JumpBlock)) return blockp;
}
// Move underp stuff to be under a new AstJumpBlock
VNRelinker repHandle;
if (under_and_next) {
underp->unlinkFrBackWithNext(&repHandle);
} else {
underp->unlinkFrBack(&repHandle);
}
AstJumpBlock* const blockp = new AstJumpBlock{nodep->fileline(), underp};
if (endOfIter) {
nodep->user1p(blockp);
} else {
nodep->user2p(blockp);
}
repHandle.relink(blockp);
// Keep any AstVars under the function not under the new JumpLabel
for (AstNode *nextp, *varp = underp; varp; varp = nextp) {
nextp = varp->nextp();
if (VN_IS(varp, Var)) blockp->addHereThisAsNext(varp->unlinkFrBack());
}
return blockp;
}
void addPrefixToBlocksRecurse(const std::string& prefix, AstNode* const nodep) {
// Add a prefix to blocks
// Used to not have blocks with duplicated names
if (AstBegin* const beginp = VN_CAST(nodep, Begin)) {
if (beginp->name() != "") beginp->name(prefix + beginp->name());
}
if (AstNode* const refp = nodep->op1p()) addPrefixToBlocksRecurse(prefix, refp);
if (AstNode* const refp = nodep->op2p()) addPrefixToBlocksRecurse(prefix, refp);
if (AstNode* const refp = nodep->op3p()) addPrefixToBlocksRecurse(prefix, refp);
if (AstNode* const refp = nodep->op4p()) addPrefixToBlocksRecurse(prefix, refp);
if (AstNode* const refp = nodep->nextp()) addPrefixToBlocksRecurse(prefix, refp);
}
bool existsBlockAbove(const std::string& name) const {
for (const AstNodeBlock* const stackp : vlstd::reverse_view(m_blockStack)) {
if (stackp->name() == name) return true;
}
return false;
}
static AstNodeModule* findOwnerModulep(AstNode* nodep) {
for (AstNode* curp = nodep; curp; curp = curp->aboveLoopp()) {
if (AstNodeModule* const modp = VN_CAST(curp, NodeModule)) return modp;
}
nodep->v3fatalSrc("Disable target is not under a module");
return nullptr; // LCOV_EXCL_LINE
}
static std::string targetInstancePath(const AstDisable* const nodep) {
if (const AstVarXRef* const xrefp = VN_CAST(nodep->targetRefp(), VarXRef)) {
return xrefp->dotted();
}
return "";
}
static std::string queueDotted(const AstDisable* const nodep,
const AstVar* const processQueuep) {
return processQueuep->isTemp() ? targetInstancePath(nodep) : "";
}
AstBegin* innerForkBranchp(const AstNodeBlock* const targetp) const {
AstBegin* innerForkBranchp = nullptr;
AstNodeBlock* prevBlockp = nullptr;
for (AstNodeBlock* const blockp : vlstd::reverse_view(m_blockStack)) {
if (!innerForkBranchp && VN_IS(blockp, Fork)) {
innerForkBranchp = VN_CAST(prevBlockp, Begin);
}
if (blockp == targetp) return innerForkBranchp;
prevBlockp = blockp;
}
return nullptr;
}
static AstStmtExpr* getQueuePushProcessSelfp(AstVarRef* const queueRefp) {
// Constructs queue.push_back(std::process::self()) statement
FileLine* const flp = queueRefp->fileline();
return new AstStmtExpr{
flp,
new AstMethodCall{flp, queueRefp, "push_back",
new AstArg{flp, "", v3Global.rootp()->stdPackageProcessSelfp(flp)}}};
}
static AstNodeVarRef* newQueueRefp(FileLine* const fl, AstVar* const processQueuep,
const VAccess& access, const std::string& dotted = "") {
if (dotted != "") return new AstVarXRef{fl, processQueuep, dotted, access};
if (!processQueuep->lifetime().isStatic() || processQueuep->isTemp()) {
return new AstVarRef{fl, processQueuep, access};
}
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
return new AstVarRef{fl, topPkgp, processQueuep, access};
}
static AstStmtExpr* getQueuePushProcessSelfp(FileLine* const fl, AstVar* const processQueuep) {
AstVarRef* const queueWriteRefp
= VN_AS(newQueueRefp(fl, processQueuep, VAccess::WRITE), VarRef);
return getQueuePushProcessSelfp(queueWriteRefp);
}
static AstStmtExpr* getQueueKillStmtp(FileLine* const fl, AstVar* const processQueuep,
const std::string& dotted = "") {
AstNodeVarRef* const queueRefp
= newQueueRefp(fl, processQueuep, VAccess::READWRITE, dotted);
AstTaskRef* killQueueCall = nullptr;
for (AstNode* itemp = v3Global.rootp()->stdPackageProcessp()->stmtsp(); itemp;
itemp = itemp->nextp()) {
if (itemp->name() == "killQueue") {
killQueueCall
= new AstTaskRef{fl, VN_AS(itemp, Task), new AstArg{fl, "", queueRefp}};
break;
}
}
UASSERT(killQueueCall, "Should be found");
killQueueCall->classOrPackagep(v3Global.rootp()->stdPackageProcessp());
return new AstStmtExpr{fl, killQueueCall};
}
static void prependStmtsp(AstBegin* const nodep, AstNode* const stmtp) {
if (AstNode* const origStmtsp = nodep->stmtsp()) {
origStmtsp->unlinkFrBackWithNext();
stmtp->addNext(origStmtsp);
}
nodep->addStmtsp(stmtp);
}
AstBegin* getOrCreateTaskDisableBeginp(AstTask* const taskp, FileLine* const fl) {
const auto it = m_taskDisableBegins.find(taskp);
if (it != m_taskDisableBegins.end()) return it->second;
AstBegin* const taskBodyp = new AstBegin{fl, "", nullptr, false};
// Disable-by-name rewrites kill this detached task-body process, so mark it as process
// backed to ensure fork/join kill-accounting hooks are always emitted.
taskBodyp->setNeedProcess();
if (taskp->stmtsp()) taskBodyp->addStmtsp(taskp->stmtsp()->unlinkFrBackWithNext());
AstFork* const forkp = new AstFork{fl, VJoinType::JOIN};
forkp->addForksp(taskBodyp);
taskp->addStmtsp(forkp);
m_taskDisableBegins.emplace(taskp, taskBodyp);
return taskBodyp;
}
AstVar* newProcessQueuep(AstNode* const nodep, FileLine* const fl, const VVarType& varType) {
AstVar* const processQueuep = new AstVar{
fl, varType, m_queueNames.get(nodep->name()), VFlagChildDType{},
new AstQueueDType{
fl, VFlagChildDType{},
new AstClassRefDType{fl, v3Global.rootp()->stdPackageProcessp(), nullptr},
nullptr}};
processQueuep->processQueue(true);
processQueuep->setIgnoreSchedWrite();
return processQueuep;
}
AstVar* getProcessQueuep(AstNode* const nodep, FileLine* const fl) {
AstNodeModule* const ownerp = findOwnerModulep(nodep);
if (VN_IS(ownerp, Package) || VN_IS(ownerp, Class)) {
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
AstVar* const processQueuep = newProcessQueuep(nodep, fl, VVarType::VAR);
processQueuep->lifetime(VLifetime::STATIC_EXPLICIT);
topPkgp->addStmtsp(processQueuep);
return processQueuep;
}
AstVar* const processQueuep = newProcessQueuep(nodep, fl, VVarType::MODULETEMP);
processQueuep->lifetime(VLifetime::STATIC_EXPLICIT);
ownerp->addStmtsp(processQueuep);
return processQueuep;
}
AstVar* getOrCreateTaskDisableQueuep(AstTask* const taskp, FileLine* const fl) {
const auto it = m_taskDisableQueues.find(taskp);
if (it != m_taskDisableQueues.end()) return it->second;
AstVar* const processQueuep = getProcessQueuep(taskp, fl);
AstStmtExpr* const pushCurrentProcessp = getQueuePushProcessSelfp(fl, processQueuep);
AstBegin* const taskBodyp = getOrCreateTaskDisableBeginp(taskp, fl);
prependStmtsp(taskBodyp, pushCurrentProcessp);
m_taskDisableQueues.emplace(taskp, processQueuep);
return processQueuep;
}
AstBegin* getOrCreateBeginDisableBeginp(AstBegin* const beginp, FileLine* const fl) {
const auto it = m_beginDisableBegins.find(beginp);
if (it != m_beginDisableBegins.end()) return it->second;
AstBegin* const beginBodyp = new AstBegin{fl, "", nullptr, false};
// Disable-by-name rewrites kill this detached block-body process, so mark it as process
// backed to ensure fork/join kill-accounting hooks are always emitted.
beginBodyp->setNeedProcess();
if (beginp->stmtsp()) beginBodyp->addStmtsp(beginp->stmtsp()->unlinkFrBackWithNext());
AstFork* const forkp = new AstFork{fl, VJoinType::JOIN};
forkp->addForksp(beginBodyp);
beginp->addStmtsp(forkp);
m_beginDisableBegins.emplace(beginp, beginBodyp);
return beginBodyp;
}
void prependForkBranchQueuePushes(AstFork* const forkp, AstVar* const processQueuep,
FileLine* const fl, bool needProcess) {
for (AstBegin* branchp = forkp->forksp(); branchp;
branchp = VN_AS(branchp->nextp(), Begin)) {
if (needProcess) branchp->setNeedProcess();
prependStmtsp(branchp, getQueuePushProcessSelfp(fl, processQueuep));
}
}
void prependNestedForkBranchQueuePushes(AstNode* const nodep, AstVar* const processQueuep,
FileLine* const fl, bool needProcess) {
nodep->foreach([&](AstFork* const forkp) {
prependForkBranchQueuePushes(forkp, processQueuep, fl, needProcess);
});
}
AstVar* getOrCreateBeginDisableQueuep(AstBegin* const beginp, FileLine* const fl) {
const auto it = m_beginDisableQueues.find(beginp);
if (it != m_beginDisableQueues.end()) return it->second;
AstVar* const processQueuep = getProcessQueuep(beginp, fl);
AstStmtExpr* const pushCurrentProcessp = getQueuePushProcessSelfp(fl, processQueuep);
AstBegin* const beginBodyp = getOrCreateBeginDisableBeginp(beginp, fl);
prependStmtsp(beginBodyp, pushCurrentProcessp);
// Named-block disable must also terminate detached descendants created by forks
// under the block, so track each fork branch process in the same queue.
prependNestedForkBranchQueuePushes(beginBodyp, processQueuep, fl, false);
m_beginDisableQueues.emplace(beginp, processQueuep);
return processQueuep;
}
AstVar* getOrCreateForkDisableQueuep(AstFork* const forkp, FileLine* const fl) {
const auto it = m_forkDisableQueues.find(forkp);
if (it != m_forkDisableQueues.end()) return it->second;
AstVar* const processQueuep = getProcessQueuep(forkp, fl);
prependForkBranchQueuePushes(forkp, processQueuep, fl, true);
// Disabling a fork must also terminate detached descendants created by nested forks
// under each branch, so track nested fork branch processes in the same queue.
for (AstBegin* branchp = forkp->forksp(); branchp;
branchp = VN_AS(branchp->nextp(), Begin)) {
prependNestedForkBranchQueuePushes(branchp, processQueuep, fl, true);
}
m_forkDisableQueues.emplace(forkp, processQueuep);
return processQueuep;
}
AstStmtExpr* insertKillStmtp(AstDisable* const nodep, AstVar* const processQueuep) {
FileLine* const fl = nodep->fileline();
AstStmtExpr* const killStmtp
= getQueueKillStmtp(fl, processQueuep, queueDotted(nodep, processQueuep));
nodep->addNextHere(killStmtp);
return killStmtp;
}
void addJumpAfterKill(AstStmtExpr* const killStmtp, AstNode* const targetp) {
AstJumpBlock* const jmpBlockp = getJumpBlock(targetp, false);
killStmtp->addNextHere(new AstJumpGo{killStmtp->fileline(), jmpBlockp});
}
void handleDisableOnFork(AstDisable* const nodep) {
// The support utilizes the process::kill()` method. For each disabled fork a queue of
// processes is declared. At the beginning of each fork branch (and each nested fork
// branch) its process handle is pushed to the queue. The `disable` statement is replaced
// with calling `kill()` on each element of the queue.
FileLine* const fl = nodep->fileline();
AstFork* const targetp = VN_AS(nodep->targetp(), Fork);
if (m_ftaskp) {
if (!m_ftaskp->exists(
[targetp](const AstNodeBlock* blockp) -> bool { return blockp == targetp; })) {
// Disabling a fork, which is within the same task, is not a problem
nodep->v3warn(E_UNSUPPORTED, "Unsupported: disabling fork from task / function");
}
}
AstVar* const processQueuep = getOrCreateForkDisableQueuep(targetp, fl);
AstStmtExpr* const killStmtp = insertKillStmtp(nodep, processQueuep);
// 'process::kill' does not immediately kill the current process executing the disable
// statement (because it's in the running state). If the disable runs under the target
// fork, jump to the end of the innermost enclosing fork branch (the branch that holds the
// running process) so statements after the disable do not execute. Targeting the innermost
// branch keeps the jump inside a single emitted coroutine even when the disable sits in a
// nested sub-fork; targeting the outer target fork's branch would cross a coroutine
// boundary once forks are split into separate functions.
if (AstBegin* const branchp = innerForkBranchp(targetp)) {
addJumpAfterKill(killStmtp, branchp);
}
}
// VISITORS
void visit(AstNodeModule* nodep) override {
if (nodep->dead()) return;
VL_RESTORER(m_modp);
VL_RESTORER(m_modRepeatNum);
m_modp = nodep;
m_modRepeatNum = 0;
iterateChildren(nodep);
}
void visit(AstNodeFTask* nodep) override {
VL_RESTORER(m_ftaskp);
m_ftaskp = nodep;
iterateChildren(nodep);
}
void visit(AstBegin* nodep) override {
UINFO(8, " " << nodep);
VL_RESTORER(m_unrollFull);
m_blockStack.push_back(nodep);
iterateChildren(nodep);
m_blockStack.pop_back();
}
void visit(AstFork* nodep) override {
UINFO(8, " " << nodep);
VL_RESTORER(m_unrollFull);
VL_RESTORER(m_inFork);
m_inFork = true;
// Mark all upper blocks, can stop once see one set to avoid O(n^2)
for (AstNodeBlock* const blockp : vlstd::reverse_view(m_blockStack)) {
if (blockp->user3SetOnce()) break;
}
m_blockStack.push_back(nodep);
iterateChildren(nodep);
m_blockStack.pop_back();
}
void visit(AstStmtPragma* nodep) override {
if (nodep->pragp()->pragType() == VPragmaType::UNROLL_DISABLE) {
m_unrollFull = VOptionBool::OPT_FALSE;
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
} else if (nodep->pragp()->pragType() == VPragmaType::UNROLL_FULL) {
m_unrollFull = VOptionBool::OPT_TRUE;
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
} else {
iterateChildren(nodep);
}
}
void visit(AstRandSequence* nodep) override {
VL_RESTORER(m_randsequencep);
m_randsequencep = nodep;
iterateChildren(nodep);
}
void visit(AstRepeat* nodep) override {
// So later optimizations don't need to deal with them,
// REPEAT(count,body) -> loop=count,WHILE(loop>0) { body, loop-- }
// Note var can be signed or unsigned based on original number.
AstNodeExpr* const countp = nodep->countp()->unlinkFrBackWithNext();
const string name = "__Vrepeat"s + cvtToStr(m_modRepeatNum++);
AstBegin* const beginp = new AstBegin{nodep->fileline(), "", nullptr, true};
// Spec says value is integral, if negative is ignored
AstVar* const varp
= new AstVar{nodep->fileline(), VVarType::BLOCKTEMP, name, nodep->findIntDType()};
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
varp->usedLoopIdx(true);
beginp->addStmtsp(varp);
AstNode* initsp = new AstAssign{
nodep->fileline(), new AstVarRef{nodep->fileline(), varp, VAccess::WRITE}, countp};
AstNode* const decp = new AstAssign{
nodep->fileline(), new AstVarRef{nodep->fileline(), varp, VAccess::WRITE},
new AstSub{nodep->fileline(), new AstVarRef{nodep->fileline(), varp, VAccess::READ},
new AstConst{nodep->fileline(), 1}}};
AstNodeExpr* const zerosp = new AstConst{nodep->fileline(), AstConst::Signed32{}, 0};
AstNodeExpr* const condp = new AstGtS{
nodep->fileline(), new AstVarRef{nodep->fileline(), varp, VAccess::READ}, zerosp};
AstNode* const bodysp = nodep->stmtsp();
if (bodysp) bodysp->unlinkFrBackWithNext();
FileLine* const flp = nodep->fileline();
AstLoop* const loopp = new AstLoop{flp};
loopp->addStmtsp(new AstLoopTest{flp, loopp, condp});
loopp->addStmtsp(bodysp);
loopp->addContsp(decp);
if (!m_unrollFull.isDefault()) loopp->unroll(m_unrollFull);
m_unrollFull = VOptionBool::OPT_DEFAULT_FALSE;
beginp->addStmtsp(initsp);
beginp->addStmtsp(loopp);
// Replacement AstBegin will be iterated next
nodep->replaceWith(beginp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void visit(AstLoop* nodep) override {
if (!m_unrollFull.isDefault()) nodep->unroll(m_unrollFull);
if (m_modp->hasParameterList() || m_modp->hasGParam()) {
nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDLOOP, true);
}
m_unrollFull = VOptionBool::OPT_DEFAULT_FALSE;
VL_RESTORER(m_loopp);
VL_RESTORER(m_loopInc);
m_loopp = nodep;
m_loopInc = false;
iterateAndNextNull(nodep->stmtsp());
m_loopInc = true;
iterateAndNextNull(nodep->contsp());
// Move contsp into stmtsp, no longer needed to keep separately
if (nodep->contsp()) nodep->addStmtsp(nodep->contsp()->unlinkFrBackWithNext());
}
void visit(AstNodeForeach* nodep) override {
VL_RESTORER(m_loopp);
m_loopp = nodep;
iterateAndNextNull(nodep->bodyp());
}
void visit(AstReturn* nodep) override {
iterateChildren(nodep);
const AstFunc* const funcp = VN_CAST(m_ftaskp, Func);
if (m_randsequencep) {
nodep->replaceWith(new AstRSReturn{nodep->fileline()});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
} else if (m_inFork) {
nodep->v3error("Return isn't legal under fork (IEEE 1800-2023 9.2.3)");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
} else if (!m_ftaskp) {
nodep->v3error("Return isn't underneath a task or function");
} else if (funcp && !nodep->lhsp() && !funcp->isConstructor()) {
nodep->v3error("Return underneath a function should have return value");
} else if (!funcp && nodep->lhsp()) {
nodep->v3error("Return underneath a task shouldn't have return value");
} else {
if (funcp && nodep->lhsp()) {
// Set output variable to return value
nodep->addHereThisAsNext(new AstAssign{
nodep->fileline(),
new AstVarRef{nodep->fileline(), VN_AS(funcp->fvarp(), Var), VAccess::WRITE},
nodep->lhsp()->unlinkFrBackWithNext()});
}
// Jump to the end of the function call
AstJumpBlock* const blockp = getJumpBlock(m_ftaskp, false);
nodep->addHereThisAsNext(new AstJumpGo{nodep->fileline(), blockp});
}
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstBreak* nodep) override {
iterateChildren(nodep);
if (!m_loopp && m_randsequencep) {
nodep->replaceWith(new AstRSBreak{nodep->fileline()});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
} else if (!m_loopp) {
nodep->v3error("break isn't underneath a loop");
} else {
// Jump to the end of the loop
AstJumpBlock* const blockp = getJumpBlock(m_loopp, false);
nodep->addNextHere(new AstJumpGo{nodep->fileline(), blockp});
}
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstContinue* nodep) override {
iterateChildren(nodep);
if (!m_loopp) {
nodep->v3error("continue isn't underneath a loop");
} else {
// Jump to the end of this iteration
// If a "for" loop then need to still do the post-loop increment
AstJumpBlock* const blockp = getJumpBlock(m_loopp, true);
nodep->addNextHere(new AstJumpGo{nodep->fileline(), blockp});
}
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstDisable* nodep) override {
UINFO(8, " DISABLE " << nodep);
AstNode* const targetp = nodep->targetp();
if (!targetp) {
// Linking errors on the disable target are already reported upstream.
// Drop this node to avoid cascading into an internal assertion.
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
if (AstTask* const taskp = VN_CAST(targetp, Task)) {
AstVar* const processQueuep = getOrCreateTaskDisableQueuep(taskp, nodep->fileline());
AstStmtExpr* const killStmtp = insertKillStmtp(nodep, processQueuep);
// process::kill does not terminate the currently running process immediately.
// If we disable the current task by name from inside itself, jump to its end.
if (m_ftaskp == taskp) {
AstNode* jumpTargetp = taskp;
const auto it = m_taskDisableBegins.find(taskp);
if (it != m_taskDisableBegins.end()) jumpTargetp = it->second;
addJumpAfterKill(killStmtp, jumpTargetp);
}
} else if (VN_IS(targetp, Fork)) {
handleDisableOnFork(nodep);
} else if (AstBegin* const beginp = VN_CAST(targetp, Begin)) {
if (existsBlockAbove(beginp->name())) {
if (!beginp->user3()) {
// Jump to the end of the named block
AstJumpBlock* const blockp = getJumpBlock(beginp, false);
nodep->addNextHere(new AstJumpGo{nodep->fileline(), blockp});
} else {
AstVar* const processQueuep
= getOrCreateBeginDisableQueuep(beginp, nodep->fileline());
AstStmtExpr* const killStmtp = insertKillStmtp(nodep, processQueuep);
// process::kill does not terminate the currently running process immediately.
// If disable executes inside a fork branch of this named block, jump to the
// end of that branch to prevent statements after disable from executing.
AstBegin* const branchp = innerForkBranchp(beginp);
AstBegin* const jumpTargetp
= branchp ? branchp : m_beginDisableBegins.at(beginp);
addJumpAfterKill(killStmtp, jumpTargetp);
}
} else {
AstVar* const processQueuep
= getOrCreateBeginDisableQueuep(beginp, nodep->fileline());
insertKillStmtp(nodep, processQueuep);
}
} else {
nodep->v3fatalSrc("Disable linked with node of unhandled type "
<< targetp->prettyTypeName());
}
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstFinish* nodep) override {
if (nodep->user1SetOnce()) return; // Process once
iterateChildren(nodep);
if (m_inFork) {
nodep->replaceWith(new AstFinishFork{nodep->fileline()});
VL_DO_DANGLING(nodep->deleteTree(), nodep);
} else if (m_loopp) {
// Jump to the end of the loop (post-finish)
AstJumpBlock* const blockp = getJumpBlock(m_loopp, false);
nodep->addNextHere(new AstJumpGo{nodep->fileline(), blockp});
}
}
void visit(AstVarRef* nodep) override {
if (m_loopInc && nodep->varp()) nodep->varp()->usedLoopIdx(true);
}
void visit(AstConst*) override {}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit LinkJumpVisitor(AstNetlist* nodep) { iterate(nodep); }
~LinkJumpVisitor() override = default;
};
//######################################################################
// Task class functions
void V3LinkJump::linkJump(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ LinkJumpVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("linkjump", 0, dumpTreeEitherLevel() >= 3);
}