Optimize conditional merging across some impure statements (#7159)

- Allow reordering pure statements with DPI import calls iff no public
  variables (including those read via a DPI export) are involved. This
  ensures the DPI import can't observe the reordering
- Allow reordering of pure statements with AstDisplay and AstStop. This
  requires an assumption that AstDisplay and AstStop will not read or
  write model state other than via a VarRef explicitly present int the
  Ast.
Overall this allows eliminating a lot of conditionals around assertions,
which were previously not possible.
This commit is contained in:
Geza Lore
2026-03-01 05:47:05 -05:00
committed by GitHub
parent 26eac21432
commit 77ce9cec1e
10 changed files with 1394 additions and 1339 deletions
+2
View File
@@ -621,6 +621,8 @@ public:
init(text, setwidth);
}
ASTGEN_MEMBERS_AstCExpr;
void dump(std::ostream& str = std::cout) const override;
void dumpJson(std::ostream& str = std::cout) const override;
// METHODS
bool cleanOut() const override { return true; }
std::string emitC() override { V3ERROR_NA_RETURN(""); }
+8
View File
@@ -1869,6 +1869,14 @@ void AstCellInlineScope::dumpJson(std::ostream& str) const {
dumpJsonStrFunc(str, origModName);
dumpJsonGen(str);
}
void AstCExpr::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
if (m_pure) str << " [PURE]";
}
void AstCExpr::dumpJson(std::ostream& str) const {
dumpJsonBoolIf(str, "pure", m_pure);
dumpJsonGen(str);
}
bool AstClass::isCacheableChild(const AstNode* nodep) {
return VN_IS(nodep, Var) || VN_IS(nodep, Typedef)
|| (VN_IS(nodep, Constraint) && !VN_AS(nodep, Constraint)->isExternProto())
+68 -10
View File
@@ -137,12 +137,19 @@ struct StmtProperties final {
std::set<const AstVar*> m_rdVars; // Variables read by this statement
std::set<const AstVar*> m_wrVars; // Variables written by this statement
bool m_isFence = false; // Nothing should move across this statement, nor should it be merged
bool m_sideEffect = false; // Statement may have side effect, without access to model state
bool m_implPubRd = false; // Statement may implicitly read public state (without VarRef)
bool m_implPubWr = false; // Statement may implicitly write public state (without VarRef)
bool m_explPubRef = false; // Statement explicitly references public state via VarRef
AstNodeStmt* m_prevWithSameCondp = nullptr; // Previous node in same list, with same condition
bool writesConditionVar() const {
bool writesConditionVar(bool condPubWritable) const {
// This relies on MarkVarsVisitor having been called on the condition node
for (const AstVar* const varp : m_wrVars) {
if (varp->user1()) return true;
}
// If the condition contains a public variable, check if it might be written
if (condPubWritable && m_implPubWr) return true;
// Otherwise not written
return false;
}
};
@@ -231,8 +238,12 @@ class CodeMotionAnalysisVisitor final : public VNVisitorConst {
// Add all rd/wr vars to outer statement
outerPropsp->m_rdVars.insert(m_propsp->m_rdVars.cbegin(), m_propsp->m_rdVars.cend());
outerPropsp->m_wrVars.insert(m_propsp->m_wrVars.cbegin(), m_propsp->m_wrVars.cend());
// If this statement is impure, the enclosing statement is also impure
if (m_propsp->m_isFence) outerPropsp->m_isFence = true;
// Propagate flags to enclosing statement
outerPropsp->m_isFence |= m_propsp->m_isFence;
outerPropsp->m_sideEffect |= m_propsp->m_sideEffect;
outerPropsp->m_implPubRd |= m_propsp->m_implPubRd;
outerPropsp->m_implPubWr |= m_propsp->m_implPubWr;
outerPropsp->m_explPubRef |= m_propsp->m_explPubRef;
}
}
@@ -242,11 +253,44 @@ class CodeMotionAnalysisVisitor final : public VNVisitorConst {
// Gather read and written variables
if (access.isReadOrRW()) m_propsp->m_rdVars.insert(varp);
if (access.isWriteOrRW()) m_propsp->m_wrVars.insert(varp);
if (varp->isSigPublic() || varp->isWrittenByDpi() || varp->isReadByDpi()) {
m_propsp->m_explPubRef = true;
}
}
void checkProperties(AstNode* nodep) {
// Ignore StmtExpr. It interferes with special casing DPI calls below,
// and it only checks if the child expr is impure, which is checked
// explicitly during the analysis.
if (VN_IS(nodep, StmtExpr)) return;
// Call to a DPI import
if (const AstNodeCCall* const ccallp = VN_CAST(nodep, NodeCCall)) {
AstCFunc* const funcp = ccallp->funcp();
if (funcp->dpiImportWrapper()) {
if (!funcp->dpiPure()) {
// Assume has side effect and accesses public state
m_propsp->m_sideEffect = true;
m_propsp->m_implPubRd = true;
m_propsp->m_implPubWr = true;
}
return;
}
}
// Side effect, but assume does not access public state
if (VN_IS(nodep, Display) || VN_IS(nodep, Stop)) {
m_propsp->m_sideEffect = true;
return;
}
// If impure, or branch, mark statement as fence
if (!nodep->isPure() || nodep->isBrancher()) m_propsp->m_isFence = true;
}
void analyzeNode(AstNode* nodep) {
// If impure, or branch, mark statement as fence
if (m_propsp && (!nodep->isPure() || nodep->isBrancher())) m_propsp->m_isFence = true;
// Check properties of this node if under a statement
if (m_propsp) checkProperties(nodep);
// Analyze children
iterateChildrenConst(nodep);
}
@@ -310,6 +354,13 @@ class CodeMotionOptimizeVisitor final : public VNVisitor {
// Don't move across fences
if (aProps.m_isFence) return false;
if (bProps.m_isFence) return false;
// Don't swap side effecting statements, but can move others across them
if (aProps.m_sideEffect && bProps.m_sideEffect) return false;
// Don't swap if there is a hazard around public state - must observe ordering
const bool bPubRef = bProps.m_implPubWr || bProps.m_implPubRd || bProps.m_explPubRef;
if (aProps.m_implPubWr && bPubRef) return false;
const bool aPubRef = aProps.m_implPubWr || aProps.m_implPubRd || aProps.m_explPubRef;
if (aPubRef && bProps.m_implPubWr) return false;
// If either statement writes a variable that the other reads, they are not swappable
if (!areDisjoint(aProps.m_rdVars, bProps.m_wrVars)) return false;
if (!areDisjoint(bProps.m_rdVars, aProps.m_wrVars)) return false;
@@ -446,6 +497,7 @@ class MergeCondVisitor final : public VNVisitor {
AstNodeExpr* m_mgCondp = nullptr; // The condition of the first node
const AstNode* m_mgLastp = nullptr; // Last node in merged sequence
const AstNode* m_mgNextp = nullptr; // Next node in list being examined
bool m_mgCondPubWritable = false; // True if m_mgCondp contains a public writable variable
uint32_t m_listLenght = 0; // Length of current list
std::queue<AstNode*>* m_workQueuep = nullptr; // Node lists (via AstNode::nextp()) to merge
@@ -717,6 +769,7 @@ class MergeCondVisitor final : public VNVisitor {
m_mgCondp = nullptr;
m_mgLastp = nullptr;
m_mgNextp = nullptr;
m_mgCondPubWritable = false;
AstNode::user1ClearTree(); // Clear marked variables
AstNode::user2ClearTree();
// Merge recursively within the branches of an un-merged AstNodeIF
@@ -753,14 +806,19 @@ class MergeCondVisitor final : public VNVisitor {
// Set up head of new list if node is first in list
if (!m_mgFirstp) {
UASSERT_OBJ(condp, nodep, "Cannot start new list without condition");
// Mark variable references in the condition
condp->foreach([](const AstVarRef* nodep) { nodep->varp()->user1(1); });
// Mark variable references in the condition, record if a public variable is involved
condp->foreach([&](const AstVarRef* nodep) {
AstVar* const varp = nodep->varp();
varp->user1(1);
if (varp->isSigPublic() || varp->isWrittenByDpi()) m_mgCondPubWritable = true;
});
// Now check again if mergeable. We need this to pick up assignments to conditions,
// e.g.: 'c = c ? a : b' at the beginning of the list, which is in fact not mergeable
// because it updates the condition. We simply bail on these.
if ((*m_stmtPropertiesp)(nodep).writesConditionVar()) {
if ((*m_stmtPropertiesp)(nodep).writesConditionVar(m_mgCondPubWritable)) {
// Clear marked variables
AstNode::user1ClearTree();
m_mgCondPubWritable = false;
// We did not add to the list
return false;
}
@@ -773,7 +831,7 @@ class MergeCondVisitor final : public VNVisitor {
AstNodeStmt* const backp = VN_CAST(m_mgFirstp->backp(), NodeStmt);
if (!backp || backp->nextp() != m_mgFirstp) break; // Don't move up the tree
const StmtProperties& props = (*m_stmtPropertiesp)(backp);
if (props.m_isFence || props.writesConditionVar()) break;
if (props.m_isFence || props.writesConditionVar(m_mgCondPubWritable)) break;
if (isSimplifiableNode(backp)) {
++m_listLenght;
m_mgFirstp = backp;
@@ -824,7 +882,7 @@ class MergeCondVisitor final : public VNVisitor {
if (props.m_isFence) return false; // Fence node never mergeable
// If the statement writes a condition variable of a pending merge,
// we must end the pending merge
if (m_mgFirstp && props.writesConditionVar()) mergeEnd();
if (m_mgFirstp && props.writesConditionVar(m_mgCondPubWritable)) mergeEnd();
return true; // Now surely mergeable
}
+2 -1
View File
@@ -765,7 +765,8 @@ class TaskVisitor final : public VNVisitor {
// __Vscopep
ccallp->addArgsp(snp);
// __Vfilenamep
ccallp->addArgsp(new AstCExpr{flp, "\"" + flp->filenameEsc() + "\"", 64});
ccallp->addArgsp(
new AstCExpr{flp, AstCExpr::Pure{}, "\"" + flp->filenameEsc() + "\"", 64});
// __Vlineno
ccallp->addArgsp(new AstConst(flp, flp->lineno()));
}