Fix V3Simulate constant reuse (#5709)

Use a generational allocator for reusing AstConst across
V3Simulate::clear(), instead of using user1 (which is also used
to store values of nodes).

Also fix invalid lookup on array initializer
This commit is contained in:
Geza Lore 2025-01-03 10:33:29 +00:00 committed by GitHub
parent 951f5eaa82
commit 010ae580b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 77 additions and 38 deletions

View File

@ -78,8 +78,7 @@ private:
// Cleared on each always/assignw
const VNUser1InUse m_inuser1;
// AstVar/AstVarScope::user1p() -> See AuxAstVar via m_varAux
// AstConst::user1() -> bool. This AstConst (allocated by this class) is in use
// AstNode::user1p() -> See AuxAstVar via m_varAux
enum VarUsage : uint8_t { VU_NONE = 0, VU_LV = 1, VU_RV = 2, VU_LVDLY = 4 };
@ -96,6 +95,32 @@ private:
AstUser1Allocator<AstNode, AuxVariable> m_varAux;
// We want to re-use allocated constants across calls to clear(), but we want to be able
// to 'clear()' fast, so we use a generation number based allocator.
struct ConstAllocator final {
size_t m_generation = 0;
size_t m_nextFree = 0;
std::deque<AstConst*> m_constps;
AstConst* allocate(size_t currentGeneration, AstNode* nodep) {
if (m_generation != currentGeneration) {
m_generation = currentGeneration;
m_nextFree = 0;
}
UASSERT_OBJ(m_nextFree <= m_constps.size(), nodep, "Should only allocate at end");
if (m_nextFree == m_constps.size()) {
m_constps.push_back(
new AstConst{nodep->fileline(), AstConst::DTyped{}, nodep->dtypep()});
}
AstConst* const constp = m_constps[m_nextFree++];
constp->num().nodep(nodep);
return constp;
}
~ConstAllocator() {
for (AstConst* const constp : m_constps) VL_DO_DANGLING(delete constp, constp);
}
};
// STATE
// Major mode
bool m_checkOnly; ///< Checking only (no simulation) mode
@ -113,8 +138,9 @@ private:
int m_dataCount; ///< Bytes of data
AstJumpGo* m_jumpp = nullptr; ///< Jump label we're branching from
// Simulating:
std::unordered_map<const AstNodeDType*, std::deque<AstConst*>>
m_constps; ///< Lists of all AstConst* allocated per dtype
// Allocators for constants of various data types
std::unordered_map<const AstNodeDType*, ConstAllocator> m_constps;
size_t m_constGeneration = 0;
std::vector<SimStackNode*> m_callStack; ///< Call stack for verbose error messages
// Cleanup
@ -223,37 +249,8 @@ public:
// Simulation METHODS
private:
AstConst* allocConst(AstNode* nodep) {
// Save time - kept a list of allocated but unused values
// It would be more efficient to do this by size, but the extra accounting
// slows things down more than we gain.
AstConst* constp;
// Grab free list corresponding to this dtype
std::deque<AstConst*>& freeList = m_constps[nodep->dtypep()];
bool allocNewConst = true;
if (!freeList.empty()) {
constp = freeList.front();
if (!constp->user1()) {
// Front of free list is free, reuse it (otherwise allocate new node)
allocNewConst = false; // No need to allocate
// Mark the AstConst node as used, and move it to the back of the free list. This
// ensures that when all AstConst instances within the list are used, then the
// front of the list will be marked as used, in which case the enclosing 'if' will
// fail and we fall back to allocation.
constp->user1(1);
freeList.pop_front();
freeList.push_back(constp);
// configure const
constp->num().nodep(nodep);
}
}
if (allocNewConst) {
// Need to allocate new constant
constp = new AstConst{nodep->fileline(), AstConst::DTyped{}, nodep->dtypep()};
// Mark as in use, add to free list for later reuse
constp->user1(1);
freeList.push_back(constp);
}
return constp;
// Allocate a constant with this dtype. Reuse them across a 'clear()' call for efficiency.
return m_constps[nodep->dtypep()].allocate(m_constGeneration, nodep);
}
public:
@ -850,6 +847,8 @@ private:
if (!itemp) {
clearOptimizable(nodep, "Array initialization has too few elements, need element "
+ cvtToStr(offset));
} else if (AstConst* const constp = VN_CAST(itemp, Const)) {
setValue(nodep, constp);
} else {
setValue(nodep, fetchValue(itemp));
}
@ -1256,6 +1255,7 @@ public:
AstNode::user1ClearTree();
m_varAux.clear();
++m_constGeneration;
}
void mainTableCheck(AstNode* nodep) {
setMode(true /*scoped*/, true /*checking*/, false /*params*/);
@ -1274,9 +1274,6 @@ public:
mainGuts(nodep);
}
~SimulateVisitor() override {
for (const auto& pair : m_constps) {
for (AstConst* const constp : pair.second) delete constp;
}
m_constps.clear();
for (AstNode* ip : m_reclaimValuesp) delete ip;
m_reclaimValuesp.clear();

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile()
test.execute(check_finished=True)
test.passes()

View File

@ -0,0 +1,24 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
function integer fun;
integer array[0:0];
begin
array[0] = 10;
fun = array[0];
end
endfunction
module test ();
begin
localparam something = fun();
initial begin
if (something !== 10) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule