Fix unintended side-effect insertion on associative array read (#8077)

This commit is contained in:
Adam Kostrzewski 2026-08-14 15:23:44 +02:00 committed by GitHub
parent 0a3657517d
commit 649598c74c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 153 additions and 15 deletions

View File

@ -570,7 +570,8 @@ public:
idxWidths.push_back(idx_width);
indices.insert(indices.end(), integral_index.begin(), integral_index.end());
record_arr_table(var.at(key), indexed_name, dimension - 1, indices, idxWidths);
record_arr_table(var.atWrite(key), indexed_name, dimension - 1, indices,
idxWidths);
// Cleanup indices and widths
idxWidths.pop_back();
@ -644,7 +645,7 @@ public:
std::string result = oss.str();
result.insert(result.begin(), int(idx_width / 4) - result.size(), '0');
record_struct_arr(var.at(key), name + "." + result, dimension - 1, indices,
record_struct_arr(var.atWrite(key), name + "." + result, dimension - 1, indices,
idxWidths);
}
}
@ -772,7 +773,7 @@ public:
bool basicStdRandomization(VlAssocArray<T_Key, T_Value>& value, size_t width) {
T_Key key;
for (int exists = value.first(key); exists; exists = value.next(key)) {
basicStdRandomization(value.at(key), width);
basicStdRandomization(value.atWrite(key), width);
}
return true;
}

View File

@ -327,7 +327,7 @@ VerilatedDeserialize& operator>>(VerilatedDeserialize& os, VlAssocArray<T_Key, T
T_Value value;
os >> index;
os >> value;
rhs.at(index) = value;
rhs.atWrite(index) = value;
}
return os;
}

View File

@ -1120,9 +1120,7 @@ public:
return 1;
}
// Setting. Verilog: assoc[index] = v
// Can't just overload operator[] or provide a "at" reference to set,
// because we need to be able to insert only when the value is set
T_Value& at(const T_Key& index) {
T_Value& atWrite(const T_Key& index) {
const auto it = m_map.find(index);
if (it == m_map.end()) {
std::pair<typename Map::iterator, bool> pit = m_map.emplace(index, m_defaultValue);
@ -1138,7 +1136,7 @@ public:
}
// Setting as a chained operation
VlAssocArray& set(const T_Key& index, const T_Value& value) {
at(index) = value;
atWrite(index) = value;
return *this;
}
VlAssocArray& setDefault(const T_Value& value) {
@ -1395,7 +1393,7 @@ void VL_READMEM_N(bool hex, int bits, const std::string& filename,
QData addr;
std::string data;
if (rmem.get(addr /*ref*/, data /*ref*/)) {
rmem.setData(&(obj.at(addr)), data);
rmem.setData(&(obj.atWrite(addr)), data);
} else {
break;
}

View File

@ -4895,9 +4895,11 @@ public:
bool cleanRhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
bool isGateOptimizable() const override { return false; } // AssocSel creates on miss
bool isGateOptimizable() const override {
return !isLValue(); // AssocSel creates on miss
}
bool isPredictOptimizable() const override { return false; }
bool isPure() override { return false; } // AssocSel creates on miss
bool isPure() override { return !isLValue(); } // AssocSel creates on miss
bool sameNode(const AstNode* /*samep*/) const override { return true; }
int instrCount() const override { return widthInstrs(); }
};

View File

@ -2053,6 +2053,8 @@ bool AstNodeExpr::isLValue() const {
return varrefp->access().isWriteOrRW();
} else if (const AstMemberSel* const memberselp = VN_CAST(this, MemberSel)) {
return memberselp->access().isWriteOrRW();
} else if (const AstStructSel* const structselp = VN_CAST(this, StructSel)) {
return structselp->fromp()->isLValue();
} else if (const AstSel* const selp = VN_CAST(this, Sel)) {
return selp->fromp()->isLValue();
} else if (const AstNodeSel* const nodeSelp = VN_CAST(this, NodeSel)) {

View File

@ -485,7 +485,7 @@ void EmitCFunc::emitVarReset(const string& prefix, AstVar* varp, bool constructi
const auto& mapr = initarp->map();
for (const auto& itr : mapr) {
AstNode* const valuep = itr.second->valuep();
emitSetVarConstant(newPrefix + ".at(" + cvtToStr(itr.first) + ")",
emitSetVarConstant(newPrefix + ".atWrite(" + cvtToStr(itr.first) + ")",
VN_AS(valuep, Const));
}
} else if (VN_IS(dtypep, WildcardArrayDType)) {
@ -496,7 +496,7 @@ void EmitCFunc::emitVarReset(const string& prefix, AstVar* varp, bool constructi
const auto& mapr = initarp->map();
for (const auto& itr : mapr) {
AstNode* const valuep = itr.second->valuep();
emitSetVarConstant(newPrefix + ".at(" + cvtToStr(itr.first) + ")",
emitSetVarConstant(newPrefix + ".atWrite(" + cvtToStr(itr.first) + ")",
VN_AS(valuep, Const));
}
} else if (AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {

View File

@ -732,7 +732,8 @@ public:
}
void visit(AstAssocSel* nodep) override {
iterateAndNextConstNull(nodep->fromp());
putnbs(nodep, ".at(");
const std::string atFunc = nodep->isLValue() ? ".atWrite(" : ".at(";
putnbs(nodep, atFunc);
AstAssocArrayDType* const adtypep
= VN_AS(nodep->fromp()->dtypep()->skipRefp(), AssocArrayDType);
UASSERT_OBJ(adtypep, nodep, "Associative select on non-associative type");
@ -741,7 +742,8 @@ public:
}
void visit(AstWildcardSel* nodep) override {
iterateAndNextConstNull(nodep->fromp());
putnbs(nodep, ".at(");
const std::string atFunc = nodep->isLValue() ? ".atWrite(" : ".at(";
putnbs(nodep, atFunc);
AstWildcardArrayDType* const adtypep
= VN_AS(nodep->fromp()->dtypep()->skipRefp(), WildcardArrayDType);
UASSERT_OBJ(adtypep, nodep, "Wildcard select on non-wildcard-associative type");

View File

@ -0,0 +1,18 @@
#!/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')
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,62 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// 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 Antmicro
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
module t;
// dist
class Cls;
rand bit dict[int unsigned];
function void call_rand;
void'(randomize() with {
dict[0] dist {
1 :/ 1
};
});
endfunction
endclass
// foreach
int dict1d[int];
int dict2d[int][string];
int dict3d[int][string][int];
Cls cls;
initial begin
// 1D
foreach (dict1d[b]) begin
$error(b); // should never reach
end
`checkd(dict1d.size(), 0);
// 2D
foreach (dict2d[0][b]) begin
$error(b); // should never reach
end
`checkd(dict2d.size(), 0);
// 3D
foreach (dict3d[0][i]) begin
foreach (dict3d[0][i][j]) begin
$error(i, j); // should never reach
end
end
`checkd(dict3d.size(), 0);
cls = new;
cls.call_rand();
`checkd(cls.dict.size(), 0);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/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')
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,35 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// 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 Antmicro
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
module t;
initial begin
int a;
int dict1d[*];
int dict2d[*][*];
int dict3d[*][*][*];
int dictmix[int][*];
`checkd(dict1d[0], 0);
`checkd(dict1d.size(), 0);
`checkd(dict2d[0][0], 0);
`checkd(dict2d.size(), 0);
`checkd(dict3d[0][0][0], 0);
`checkd(dict3d.size(), 0);
`checkd(dictmix[0][0], 0);
`checkd(dictmix.size(), 0);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule