splitCheck() cuts a function's top level statement list on node count
alone, ignoring AstVar declarations in that list. localizeVars() puts the
dynamic trigger temporaries there, which a 'wait fork' reaches, so the
declaration could land in one sub-function and its references in another.
Sub-functions are emitted as separate C++ functions, so the output failed
to compile:
error: '__Vtrigprevexpr_h5d9da2ce__0' was not declared in this scope
V3InlineCFuncs could also inline the sub-function holding the declaration
and free the AstVar while other sub-functions still referenced it, which
--debug reports as a broken link and which segfaults an -O3 build.
The existing "Can't split function with local variables" assertion only
checked AstCFunc::varsp(), not declarations among the statements.
Only allow a sub-function boundary where it does not separate a local
declaration from a reference to it. This keeps the temporaries function
local, as #6859 requires, while restoring the guarantee #5822 made that
splitting cannot orphan them.
Signed-off-by: Marco Brambilla <marco@hairyotter.com>
This commit is contained in:
parent
3fa88ae7f3
commit
1f164d7ee0
|
|
@ -201,6 +201,7 @@ Maarten De Braekeleer
|
||||||
Maciej Sobkowski
|
Maciej Sobkowski
|
||||||
Marcel Chang
|
Marcel Chang
|
||||||
Marco Bartoli
|
Marco Bartoli
|
||||||
|
Marco Brambilla
|
||||||
Marco Widmer
|
Marco Widmer
|
||||||
Mariusz Glebocki
|
Mariusz Glebocki
|
||||||
Markus Krause
|
Markus Krause
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,40 @@ void splitCheckFinishSubFunc(AstCFunc* ofuncp, AstCFunc* subFuncp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compute, for each top level statement of 'ofuncp', whether a new sub-function may begin there.
|
||||||
|
// Sub-functions are emitted as separate C++ functions, so they cannot see each other's automatic
|
||||||
|
// storage. A function-local AstVar declared among the top level statements must therefore end up
|
||||||
|
// in the same sub-function as every reference to it, otherwise the emitted C++ refers to an
|
||||||
|
// undeclared identifier (and the AstVar can be deleted from under the still-live references).
|
||||||
|
static std::vector<bool> splitCheckBreakable(const AstCFunc* ofuncp) {
|
||||||
|
// Gather the top level statements, and where each locally declared variable is declared
|
||||||
|
std::vector<const AstNode*> stmtps;
|
||||||
|
std::unordered_map<const AstVar*, size_t> declIdx; // Local var -> index it is declared at
|
||||||
|
for (const AstNode* nodep = ofuncp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
||||||
|
if (const AstVar* const varp = VN_CAST(nodep, Var)) declIdx.emplace(varp, stmtps.size());
|
||||||
|
stmtps.push_back(nodep);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the last statement referencing each locally declared variable
|
||||||
|
std::vector<size_t> lastUse(stmtps.size());
|
||||||
|
for (size_t i = 0; i < stmtps.size(); ++i) {
|
||||||
|
lastUse[i] = i; // A declaration is live at least where it is declared
|
||||||
|
stmtps[i]->foreach([&](const AstNodeVarRef* refp) {
|
||||||
|
const auto it = declIdx.find(refp->varp()); // 'end()' if not one of our locals
|
||||||
|
if (it != declIdx.end()) lastUse[it->second] = std::max(lastUse[it->second], i);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// A break before statement 'i' is allowed only if no local declared before 'i' is still live
|
||||||
|
std::vector<bool> breakable(stmtps.size(), true);
|
||||||
|
size_t liveEnd = 0; // Last index any so far declared local is referenced at
|
||||||
|
for (size_t i = 0; i < stmtps.size(); ++i) {
|
||||||
|
breakable[i] = liveEnd < i;
|
||||||
|
if (VN_IS(stmtps[i], Var)) liveEnd = std::max(liveEnd, lastUse[i]);
|
||||||
|
}
|
||||||
|
return breakable;
|
||||||
|
}
|
||||||
|
|
||||||
// Split large function according to --output-split-cfuncs
|
// Split large function according to --output-split-cfuncs
|
||||||
void splitCheck(AstCFunc* const ofuncp) {
|
void splitCheck(AstCFunc* const ofuncp) {
|
||||||
if (!ofuncp) return;
|
if (!ofuncp) return;
|
||||||
|
|
@ -164,6 +198,9 @@ void splitCheck(AstCFunc* const ofuncp) {
|
||||||
if (!v3Global.opt.outputSplitCFuncs() || !ofuncp->stmtsp()) return;
|
if (!v3Global.opt.outputSplitCFuncs() || !ofuncp->stmtsp()) return;
|
||||||
if (ofuncp->nodeCount() < v3Global.opt.outputSplitCFuncs()) return;
|
if (ofuncp->nodeCount() < v3Global.opt.outputSplitCFuncs()) return;
|
||||||
|
|
||||||
|
// Statement boundaries that would separate a local declaration from a reference to it
|
||||||
|
const std::vector<bool> breakable = splitCheckBreakable(ofuncp);
|
||||||
|
|
||||||
// Need to find the AstVarScopes for the function arguments. They should be in the same Scope.
|
// Need to find the AstVarScopes for the function arguments. They should be in the same Scope.
|
||||||
std::unordered_map<const AstVar*, AstVarScope*> argVscps;
|
std::unordered_map<const AstVar*, AstVarScope*> argVscps;
|
||||||
for (AstVar* argp = ofuncp->argsp(); argp; argp = VN_AS(argp->nextp(), Var)) {
|
for (AstVar* argp = ofuncp->argsp(); argp; argp = VN_AS(argp->nextp(), Var)) {
|
||||||
|
|
@ -187,13 +224,13 @@ void splitCheck(AstCFunc* const ofuncp) {
|
||||||
|
|
||||||
// Move statements one by one to the new sub-functions
|
// Move statements one by one to the new sub-functions
|
||||||
AstNode* stmtsp = ofuncp->stmtsp()->unlinkFrBackWithNext();
|
AstNode* stmtsp = ofuncp->stmtsp()->unlinkFrBackWithNext();
|
||||||
while (AstNode* const itemp = stmtsp) {
|
for (size_t index = 0; AstNode* const itemp = stmtsp; ++index) {
|
||||||
stmtsp = stmtsp->nextp();
|
stmtsp = stmtsp->nextp();
|
||||||
if (stmtsp) stmtsp->unlinkFrBackWithNext();
|
if (stmtsp) stmtsp->unlinkFrBackWithNext();
|
||||||
const size_t itemSize = static_cast<size_t>(itemp->nodeCount());
|
const size_t itemSize = static_cast<size_t>(itemp->nodeCount());
|
||||||
size += itemSize;
|
size += itemSize;
|
||||||
|
|
||||||
if (size > static_cast<size_t>(v3Global.opt.outputSplitCFuncs())) {
|
if (size > static_cast<size_t>(v3Global.opt.outputSplitCFuncs()) && breakable[index]) {
|
||||||
if (subFuncp) splitCheckFinishSubFunc(ofuncp, subFuncp, argVscps);
|
if (subFuncp) splitCheckFinishSubFunc(ofuncp, subFuncp, argVscps);
|
||||||
subFuncp = nullptr;
|
subFuncp = nullptr;
|
||||||
size = itemSize;
|
size = itemSize;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||||
|
#
|
||||||
|
# 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: 2026 Wilson Snyder
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||||
|
|
||||||
|
import vltest_bootstrap
|
||||||
|
|
||||||
|
test.scenarios('simulator')
|
||||||
|
|
||||||
|
# --output-split-cfuncs forces the process to be split into sub-functions
|
||||||
|
test.compile(verilator_flags2=["--binary", "--output-split-cfuncs", "20"])
|
||||||
|
|
||||||
|
# Confirm the split actually happened: the 'initial' timing process is emitted
|
||||||
|
# as function sub-parts (__Vtiming__0__0 / __Vtiming__0__1). Without the split
|
||||||
|
# this test would not exercise the bug it guards against -- a dynamic 'wait
|
||||||
|
# fork' trigger temporary separated from its uses across a sub-function boundary.
|
||||||
|
if test.vlt_all:
|
||||||
|
test.file_grep(test.obj_dir + "/V" + test.name + "_t__0.cpp", r'__Vtiming__0__1\b')
|
||||||
|
|
||||||
|
test.execute()
|
||||||
|
|
||||||
|
test.passes()
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// DESCRIPTION: Verilator: Verilog Test module
|
||||||
|
//
|
||||||
|
// A 'wait fork' needs a dynamic trigger temporary, which is function-local to
|
||||||
|
// the process it appears in. Splitting the process into sub-functions must not
|
||||||
|
// separate that declaration from its uses, as sub-functions are emitted as
|
||||||
|
// separate C++ functions.
|
||||||
|
//
|
||||||
|
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||||
|
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||||
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
module t;
|
||||||
|
logic clk = 1'b0;
|
||||||
|
int cnt = 0;
|
||||||
|
|
||||||
|
always #5 clk = ~clk;
|
||||||
|
|
||||||
|
task automatic phase(int n);
|
||||||
|
fork begin @(posedge clk); cnt += n; end join_none
|
||||||
|
wait fork;
|
||||||
|
endtask
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
fork @(posedge clk); join_none
|
||||||
|
wait fork;
|
||||||
|
phase(1);
|
||||||
|
fork @(negedge clk); join_none
|
||||||
|
wait fork;
|
||||||
|
phase(2);
|
||||||
|
fork @(posedge clk); join_none
|
||||||
|
wait fork;
|
||||||
|
phase(4);
|
||||||
|
if (cnt != 7) begin
|
||||||
|
$write("%%Error: cnt=%0d exp=7\n", cnt);
|
||||||
|
$stop;
|
||||||
|
end
|
||||||
|
$write("*-* All Finished *-*\n");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
Loading…
Reference in New Issue