Optimize $sformatf into $sformat (#7701)

Turn `x = $sformatf(...)` into `$sformat(x, ...)`. The former requires
checking and running a destructor for `x` at the call site, the later
does it in the callee VL_SFORMAT. This reduces the size of the call
site, which can be significant e.g. in the presence of many assertions.

Also added a rewrite of `$sformat(x, "const-string")` back into `x =
"const-string"` for the cases where the `$sformatf` would have been
folded into a constant string.
This commit is contained in:
Geza Lore 2026-06-03 08:43:05 +01:00 committed by GitHub
parent efb83c55de
commit 715f5f0c13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 45 additions and 1 deletions

View File

@ -2352,7 +2352,7 @@ class AstSFormatF final : public AstNodeExpr {
// @astgen op1 := exprsp : List[AstNodeExpr]
// @astgen op2 := scopeNamep : Optional[AstScopeName]
string m_text;
const bool m_hidden; // Under display, etc
bool m_hidden; // Under display, etc
bool m_exprFormat
= false; // Runtime Node* format, false = text() format code, false = possibly r
bool m_optionalFormat
@ -2411,6 +2411,7 @@ public:
return false;
}
bool hidden() const { return m_hidden; }
void hidden(bool flag) { m_hidden = flag; }
bool exprFormat() const { return m_exprFormat; }
void exprFormat(bool flag) { m_exprFormat = flag; }
bool optionalFormat() const { return m_optionalFormat; }

View File

@ -1137,6 +1137,12 @@ public:
fmtp(newp);
this->lhsp(lhsp);
}
AstSFormat(FileLine* fl, AstSFormatF* fmtp, AstNodeExpr* lhsp)
: ASTGEN_SUPER_SFormat(fl) {
this->fmtp(fmtp);
this->lhsp(lhsp);
fmtp->hidden(true);
}
ASTGEN_MEMBERS_AstSFormat;
const char* broken() const override {
BROKEN_RTN(!fmtp());

View File

@ -2233,6 +2233,22 @@ class ConstVisitor final : public VNVisitor {
return true;
}
bool replaceAssignSFormatF(AstNodeAssign* nodep) {
// Rewrite 'x = sformatf(...)' into 'sformat(x, ...)', which is more efficient
// at the call site as it does not need to check if 'x' needs to be freed first.
// This is a somewhat common pattern after lowering assertions.
if (!VN_IS(nodep, Assign)) return false;
AstVarRef* const lhsp = VN_CAST(nodep->lhsp(), VarRef);
if (!lhsp) return false;
AstSFormatF* const fmtp = VN_CAST(nodep->rhsp(), SFormatF);
if (!fmtp) return false;
lhsp->unlinkFrBack();
fmtp->unlinkFrBack();
nodep->replaceWith(new AstSFormat{nodep->fileline(), fmtp, lhsp});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return true;
}
bool varNotReferenced(AstNode* nodep, AstVar* varp, int level = 0) {
// Return true if varp never referenced under node.
// Return false if referenced, or tree too deep to be worth it, or side effects
@ -2607,6 +2623,8 @@ class ConstVisitor final : public VNVisitor {
}
} else if (m_doV && replaceAssignMultiSel(nodep)) {
return true;
} else if (replaceAssignSFormatF(nodep)) {
return true;
}
return false;
}
@ -3822,6 +3840,25 @@ class ConstVisitor final : public VNVisitor {
return;
}
}
void visit(AstSFormat* nodep) override {
iterateChildren(nodep);
if (!m_doNConst) return;
if (m_doNConst) {
// If it's a constant string written to a string variable, inline it as an assignment
AstSFormatF* const fmtp = nodep->fmtp();
AstNodeExpr* const lhsp = nodep->lhsp();
AstBasicDType* const basicp = VN_CAST(lhsp->dtypep()->skipRefp(), BasicDType);
if (basicp && basicp->isString() && !fmtp->exprsp()
&& fmtp->text().find('%') == string::npos) {
AstConst* const strp
= new AstConst{fmtp->fileline(), AstConst::String{}, fmtp->text()};
lhsp->unlinkFrBack();
nodep->replaceWith(new AstAssign{nodep->fileline(), lhsp, strp});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
}
}
void visit(AstNodeFTask* nodep) override {
VL_RESTORER(m_underRecFunc);
if (nodep->recursive()) m_underRecFunc = true;