Skip to content

Commit fbe7000

Browse files
committed
write_cxxrtl: avoid undefined behavior on out-of-bounds memory access.
After this commit, if NDEBUG is not defined, out-of-bounds accesses cause assertion failures for reads and writes. If NDEBUG is defined, out-of-bounds reads return zeroes, and out-of-bounds writes are ignored. This commit also adds support for memories that start with a non-zero index (`Memory::start_offset` in RTLIL).
1 parent 24d53a3 commit fbe7000

File tree

2 files changed

+78
-46
lines changed

2 files changed

+78
-46
lines changed

backends/cxxrtl/cxxrtl.cc

Lines changed: 65 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -826,61 +826,88 @@ struct CxxrtlWorker {
826826
inc_indent();
827827
}
828828
RTLIL::Memory *memory = cell->module->memories[cell->getParam(ID(MEMID)).decode_string()];
829+
std::string valid_index_temp = fresh_temporary();
830+
f << indent << "std::pair<bool, size_t> " << valid_index_temp << " = memory_index(";
831+
dump_sigspec_rhs(cell->getPort(ID(ADDR)));
832+
f << ", " << memory->start_offset << ", " << memory->size << ");\n";
829833
if (cell->type == ID($memrd)) {
830834
if (!cell->getPort(ID(EN)).is_fully_ones()) {
831835
f << indent << "if (";
832836
dump_sigspec_rhs(cell->getPort(ID(EN)));
833837
f << ") {\n";
834838
inc_indent();
835839
}
836-
if (writable_memories[memory]) {
837-
std::string addr_temp = fresh_temporary();
838-
f << indent << "const value<" << cell->getPort(ID(ADDR)).size() << "> &" << addr_temp << " = ";
839-
dump_sigspec_rhs(cell->getPort(ID(ADDR)));
840-
f << ";\n";
841-
std::string lhs_temp = fresh_temporary();
842-
f << indent << "value<" << memory->width << "> " << lhs_temp << " = "
843-
<< mangle(memory) << "[" << addr_temp << "].curr;\n";
844-
for (auto memwr_cell : transparent_for[cell]) {
845-
f << indent << "if (" << addr_temp << " == ";
846-
dump_sigspec_rhs(memwr_cell->getPort(ID(ADDR)));
847-
f << ") {\n";
848-
inc_indent();
849-
f << indent << lhs_temp << " = " << lhs_temp;
850-
f << ".update(";
851-
dump_sigspec_rhs(memwr_cell->getPort(ID(EN)));
852-
f << ", ";
853-
dump_sigspec_rhs(memwr_cell->getPort(ID(DATA)));
854-
f << ");\n";
855-
dec_indent();
856-
f << indent << "}\n";
840+
// The generated code has two bounds checks; one in an assertion, and another that guards the read.
841+
// This is done so that the code does not invoke undefined behavior under any conditions, but nevertheless
842+
// loudly crashes if an illegal condition is encountered. The assert may be turned off with -NDEBUG not
843+
// just for release builds, but also to make sure the simulator (which is presumably embedded in some
844+
// larger program) will never crash the code that calls into it.
845+
//
846+
// If assertions are disabled, out of bounds reads are defined to return zero.
847+
f << "assert(" << valid_index_temp << ".first && \"out of bounds read\");\n";
848+
f << "if(" << valid_index_temp << ".first) {\n";
849+
inc_indent();
850+
if (writable_memories[memory]) {
851+
std::string addr_temp = fresh_temporary();
852+
f << indent << "const value<" << cell->getPort(ID(ADDR)).size() << "> &" << addr_temp << " = ";
853+
dump_sigspec_rhs(cell->getPort(ID(ADDR)));
854+
f << ";\n";
855+
std::string lhs_temp = fresh_temporary();
856+
f << indent << "value<" << memory->width << "> " << lhs_temp << " = "
857+
<< mangle(memory) << "[" << valid_index_temp << ".second].curr;\n";
858+
for (auto memwr_cell : transparent_for[cell]) {
859+
f << indent << "if (" << addr_temp << " == ";
860+
dump_sigspec_rhs(memwr_cell->getPort(ID(ADDR)));
861+
f << ") {\n";
862+
inc_indent();
863+
f << indent << lhs_temp << " = " << lhs_temp;
864+
f << ".update(";
865+
dump_sigspec_rhs(memwr_cell->getPort(ID(EN)));
866+
f << ", ";
867+
dump_sigspec_rhs(memwr_cell->getPort(ID(DATA)));
868+
f << ");\n";
869+
dec_indent();
870+
f << indent << "}\n";
871+
}
872+
f << indent;
873+
dump_sigspec_lhs(cell->getPort(ID(DATA)));
874+
f << " = " << lhs_temp << ";\n";
875+
} else {
876+
f << indent;
877+
dump_sigspec_lhs(cell->getPort(ID(DATA)));
878+
f << " = " << mangle(memory) << "[" << valid_index_temp << ".second];\n";
857879
}
880+
dec_indent();
881+
f << indent << "} else {\n";
882+
inc_indent();
858883
f << indent;
859884
dump_sigspec_lhs(cell->getPort(ID(DATA)));
860-
f << " = " << lhs_temp << ";\n";
861-
} else {
862-
f << indent;
863-
dump_sigspec_lhs(cell->getPort(ID(DATA)));
864-
f << " = " << mangle(memory) << "[";
865-
dump_sigspec_rhs(cell->getPort(ID(ADDR)));
866-
f << "];\n";
867-
}
885+
f << " = value<" << memory->width << "> {};\n";
886+
dec_indent();
887+
f << indent << "}\n";
868888
if (!cell->getPort(ID(EN)).is_fully_ones()) {
869889
dec_indent();
870890
f << indent << "}\n";
871891
}
872892
} else /*if (cell->type == ID($memwr))*/ {
873893
// FIXME: handle write port priority, here and above in transparent $memrd cells
874894
log_assert(writable_memories[memory]);
875-
std::string lhs_temp = fresh_temporary();
876-
f << indent << "wire<" << memory->width << "> &" << lhs_temp << " = " << mangle(memory) << "[";
877-
dump_sigspec_rhs(cell->getPort(ID(ADDR)));
878-
f << "];\n";
879-
f << indent << lhs_temp << ".next = " << lhs_temp << ".curr.update(";
880-
dump_sigspec_rhs(cell->getPort(ID(EN)));
881-
f << ", ";
882-
dump_sigspec_rhs(cell->getPort(ID(DATA)));
883-
f << ");\n";
895+
// See above for rationale of having both the assert and the condition.
896+
//
897+
// If assertions are disabled, out of bounds writes are defined to do nothing.
898+
f << "assert(" << valid_index_temp << ".first && \"out of bounds write\");\n";
899+
f << "if (" << valid_index_temp << ".first) {\n";
900+
inc_indent();
901+
std::string lhs_temp = fresh_temporary();
902+
f << indent << "wire<" << memory->width << "> &" << lhs_temp << " = ";
903+
f << mangle(memory) << "[" << valid_index_temp << ".second];\n";
904+
f << indent << lhs_temp << ".next = " << lhs_temp << ".curr.update(";
905+
dump_sigspec_rhs(cell->getPort(ID(EN)));
906+
f << ", ";
907+
dump_sigspec_rhs(cell->getPort(ID(DATA)));
908+
f << ");\n";
909+
dec_indent();
910+
f << indent << "}\n";
884911
}
885912
if (cell->getParam(ID(CLK_ENABLE)).as_bool()) {
886913
dec_indent();

backends/cxxrtl/cxxrtl.h

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#include <cstddef>
2525
#include <cstdint>
26+
#include <cassert>
2627
#include <limits>
2728
#include <type_traits>
2829
#include <tuple>
@@ -610,23 +611,16 @@ struct memory {
610611

611612
template<size_t... InitSize>
612613
explicit memory(size_t depth, const init<InitSize> &...init) : data(depth) {
613-
// FIXME: assert(init.size() <= depth);
614614
data.resize(depth);
615615
// This utterly reprehensible construct is the most reasonable way to apply a function to every element
616616
// of a parameter pack, if the elements all have different types and so cannot be cast to an initializer list.
617617
auto _ = {std::move(std::begin(init.data), std::end(init.data), data.begin() + init.offset)...};
618618
}
619619

620620
Elem &operator [](size_t index) {
621-
// FIXME: assert(index < data.size());
621+
assert(index < data.size());
622622
return data[index];
623623
}
624-
625-
template<size_t AddrBits>
626-
Elem &operator [](const value<AddrBits> &addr) {
627-
static_assert(value<AddrBits>::chunks <= 1, "memory indexing with unreasonably large address is not supported");
628-
return (*this)[addr.data[0]];
629-
}
630624
};
631625

632626
template<size_t Width>
@@ -1099,6 +1093,17 @@ value<BitsY> mod_ss(const value<BitsA> &a, const value<BitsB> &b) {
10991093
return divmod_ss<BitsY>(a, b).second;
11001094
}
11011095

1096+
// Memory helper
1097+
template<size_t BitsAddr>
1098+
std::pair<bool, size_t> memory_index(const value<BitsAddr> &addr, size_t offset, size_t depth) {
1099+
static_assert(value<BitsAddr>::chunks <= 1, "memory address is too wide");
1100+
size_t offset_index = addr.data[0];
1101+
1102+
bool valid = (offset_index >= offset && offset_index < offset + depth);
1103+
size_t index = offset_index - offset;
1104+
return std::make_pair(valid, index);
1105+
}
1106+
11021107
} // namespace cxxrtl_yosys
11031108

11041109
#endif

0 commit comments

Comments
 (0)