verilator/src/V3Broken.cpp

342 lines
14 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Find broken links in tree
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2021 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
//
//*************************************************************************
// V3Broken's Transformations:
//
// Entire netlist
// Mark all nodes
// Check all links point to marked nodes
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Global.h"
#include "V3Broken.h"
#include "V3Ast.h"
// This visitor does not edit nodes, and is called at error-exit, so should use constant iterators
#include "V3AstConstOnly.h"
#include <algorithm>
#include <unordered_map>
//######################################################################
class BrokenTable VL_NOT_FINAL : public AstNVisitor {
// Table of brokenExists node pointers
private:
// MEMBERS
// For each node, we keep if it exists or not.
using NodeMap = std::unordered_map<const AstNode*, int>; // Performance matters (when --debug)
static NodeMap s_nodes; // Set of all nodes that exist
// BITMASK
enum { FLAG_ALLOCATED = 0x01 }; // new() and not delete()ed
enum { FLAG_IN_TREE = 0x02 }; // Is in netlist tree
enum { FLAG_LINKABLE = 0x04 }; // Is in netlist tree, can be linked to
enum { FLAG_LEAKED = 0x08 }; // Known to have been leaked
enum { FLAG_UNDER_NOW = 0x10 }; // Is in tree as parent of current node
public:
// METHODS
static void deleted(const AstNode* nodep) {
// Called by operator delete on any node - only if VL_LEAK_CHECKS
if (debug() >= 9) cout << "-nodeDel: " << cvtToHex(nodep) << endl;
const auto iter = s_nodes.find(nodep);
UASSERT_OBJ(!(iter == s_nodes.end() || !(iter->second & FLAG_ALLOCATED)),
reinterpret_cast<const AstNode*>(nodep),
"Deleting AstNode object that was never tracked or already deleted");
if (iter != s_nodes.end()) s_nodes.erase(iter);
}
#if defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 4
// GCC 4.4.* compiler warning bug, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=39390
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif
static void addNewed(const AstNode* nodep) {
// Called by operator new on any node - only if VL_LEAK_CHECKS
if (debug() >= 9) cout << "-nodeNew: " << cvtToHex(nodep) << endl;
const auto iter = s_nodes.find(nodep);
UASSERT_OBJ(!(iter != s_nodes.end() && (iter->second & FLAG_ALLOCATED)), nodep,
"Newing AstNode object that is already allocated");
if (iter == s_nodes.end()) {
int flags = FLAG_ALLOCATED; // This int needed to appease GCC 4.1.2
s_nodes.emplace(nodep, flags);
}
}
static void setUnder(const AstNode* nodep, bool flag) {
// Called by BrokenCheckVisitor when each node entered/exited
if (!okIfLinkedTo(nodep)) return;
const auto iter = s_nodes.find(nodep);
if (iter != s_nodes.end()) {
iter->second &= ~FLAG_UNDER_NOW;
if (flag) iter->second |= FLAG_UNDER_NOW;
}
}
static void addInTree(AstNode* nodep, bool linkable) {
#ifndef VL_LEAK_CHECKS
// cppcheck-suppress knownConditionTrueFalse
if (!linkable) return; // save some time, else the map will get huge!
#endif
const auto iter = s_nodes.find(nodep);
if (VL_UNCOVERABLE(iter == s_nodes.end())) {
#ifdef VL_LEAK_CHECKS
nodep->v3fatalSrc("AstNode is in tree, but not allocated");
#endif
} else {
#ifdef VL_LEAK_CHECKS
UASSERT_OBJ(iter->second & FLAG_ALLOCATED, nodep,
"AstNode is in tree, but not allocated");
#endif
UASSERT_OBJ(!(iter->second & FLAG_IN_TREE), nodep,
"AstNode is already in tree at another location");
}
int or_flags = FLAG_IN_TREE | (linkable ? FLAG_LINKABLE : 0);
if (iter == s_nodes.end()) {
s_nodes.emplace(nodep, or_flags);
} else {
iter->second |= or_flags;
}
}
static bool isAllocated(const AstNode* nodep) {
// Some generic node has a pointer to this node. Is it allocated?
// Use this when might not be in tree; otherwise use okIfLinkedTo().
#ifdef VL_LEAK_CHECKS
const auto iter = s_nodes.find(nodep);
if (iter == s_nodes.end()) return false;
if (!(iter->second & FLAG_ALLOCATED)) return false;
#endif
return true;
}
static bool okIfLinkedTo(const AstNode* nodep) {
// Some node in tree has a pointer to this node. Is it kosher?
const auto iter = s_nodes.find(nodep);
if (iter == s_nodes.end()) return false;
#ifdef VL_LEAK_CHECKS
if (!(iter->second & FLAG_ALLOCATED)) return false;
#endif
if (!(iter->second & FLAG_IN_TREE)) return false;
if (!(iter->second & FLAG_LINKABLE)) return false;
return true;
}
static bool okIfAbove(const AstNode* nodep) {
// Must be linked to and below current node
if (!okIfLinkedTo(nodep)) return false;
const auto iter = s_nodes.find(nodep);
if (iter == s_nodes.end()) return false;
if ((iter->second & FLAG_UNDER_NOW)) return false;
return true;
}
static bool okIfBelow(const AstNode* nodep) {
// Must be linked to and below current node
if (!okIfLinkedTo(nodep)) return false;
const auto iter = s_nodes.find(nodep);
if (iter == s_nodes.end()) return false;
if (!(iter->second & FLAG_UNDER_NOW)) return false;
return true;
}
static void prepForTree() {
#ifndef VL_LEAK_CHECKS
s_nodes.clear();
#else
for (NodeMap::iterator it = s_nodes.begin(); it != s_nodes.end(); ++it) {
it->second &= ~FLAG_IN_TREE;
it->second &= ~FLAG_LINKABLE;
}
#endif
}
static void doneWithTree() {
for (int backs = 0; backs < 2;
backs++) { // Those with backp() are probably under one leaking without
for (NodeMap::iterator it = s_nodes.begin(); it != s_nodes.end(); ++it) {
// LCOV_EXCL_START
if (VL_UNCOVERABLE((it->second & FLAG_ALLOCATED) && !(it->second & FLAG_IN_TREE)
&& !(it->second & FLAG_LEAKED)
&& (it->first->backp() ? backs == 1 : backs == 0))) {
// Use only AstNode::dump instead of the virtual one, as there
// may be varp() and other cross links that are bad.
if (v3Global.opt.debugCheck()) {
// When get this message, find what forgot to delete the
// node by running GDB, where for node "<e###>" use:
// watch AstNode::s_editCntGbl==####
// run
// bt
std::cerr << "%Error: LeakedNode"
<< (it->first->backp() ? "Back: " : ": ");
AstNode* rawp
= const_cast<AstNode*>(static_cast<const AstNode*>(it->first));
rawp->AstNode::dump(std::cerr);
std::cerr << endl;
V3Error::incErrors();
}
it->second |= FLAG_LEAKED;
}
// LCOV_EXCL_STOP
}
}
}
// CONSTRUCTORS
BrokenTable() = default;
virtual ~BrokenTable() override = default;
};
BrokenTable::NodeMap BrokenTable::s_nodes;
bool AstNode::brokeExists() const {
// Called by node->broken() routines to do table lookup
return BrokenTable::okIfLinkedTo(this);
}
bool AstNode::brokeExistsAbove() const {
// Called by node->broken() routines to do table lookup
return BrokenTable::okIfBelow(this);
}
bool AstNode::brokeExistsBelow() const {
// Called by node->broken() routines to do table lookup
return BrokenTable::okIfAbove(this);
}
//######################################################################
class BrokenMarkVisitor final : public AstNVisitor {
// Mark every node in the tree
private:
// NODE STATE
// Nothing! // This may be called deep inside other routines
// // so userp and friends may not be used
// METHODS
void processAndIterate(AstNode* nodep) {
BrokenTable::addInTree(nodep, nodep->maybePointedTo());
iterateChildrenConst(nodep);
}
// VISITORS
virtual void visit(AstNode* nodep) override {
// Process not just iterate
processAndIterate(nodep);
}
public:
// CONSTRUCTORS
explicit BrokenMarkVisitor(AstNetlist* nodep) { iterate(nodep); }
virtual ~BrokenMarkVisitor() override = default;
};
//######################################################################
// Broken state, as a visitor of each AstNode
class BrokenCheckVisitor final : public AstNVisitor {
bool m_inScope = false; // Under AstScope
private:
static void checkWidthMin(const AstNode* nodep) {
UASSERT_OBJ(nodep->width() == nodep->widthMin()
|| v3Global.widthMinUsage() != VWidthMinUsage::MATCHES_WIDTH,
nodep, "Width != WidthMin");
}
void processAndIterate(AstNode* nodep) {
BrokenTable::setUnder(nodep, true);
const char* whyp = nodep->broken();
UASSERT_OBJ(!whyp, nodep,
"Broken link in node (or something without maybePointedTo): " << whyp);
if (nodep->dtypep()) {
UASSERT_OBJ(nodep->dtypep()->brokeExists(), nodep,
"Broken link in node->dtypep() to " << cvtToHex(nodep->dtypep()));
UASSERT_OBJ(VN_IS(nodep->dtypep(), NodeDType), nodep,
"Non-dtype link in node->dtypep() to " << cvtToHex(nodep->dtypep()));
}
if (v3Global.assertDTypesResolved()) {
if (nodep->hasDType()) {
UASSERT_OBJ(nodep->dtypep(), nodep,
"No dtype on node with hasDType(): " << nodep->prettyTypeName());
} else {
UASSERT_OBJ(!nodep->dtypep(), nodep,
"DType on node without hasDType(): " << nodep->prettyTypeName());
}
UASSERT_OBJ(!nodep->getChildDTypep(), nodep,
"childDTypep() non-null on node after should have removed");
if (const AstNodeDType* dnodep = VN_CAST(nodep, NodeDType)) checkWidthMin(dnodep);
}
checkWidthMin(nodep);
iterateChildrenConst(nodep);
BrokenTable::setUnder(nodep, false);
}
virtual void visit(AstNodeAssign* nodep) override {
processAndIterate(nodep);
UASSERT_OBJ(!(v3Global.assertDTypesResolved() && nodep->brokeLhsMustBeLvalue()
&& VN_IS(nodep->lhsp(), NodeVarRef)
&& !VN_CAST(nodep->lhsp(), NodeVarRef)->access().isWriteOrRW()),
nodep, "Assignment LHS is not an lvalue");
}
virtual void visit(AstScope* nodep) override {
VL_RESTORER(m_inScope);
{
m_inScope = true;
processAndIterate(nodep);
}
}
virtual void visit(AstNodeVarRef* nodep) override {
processAndIterate(nodep);
// m_inScope because some Vars have initial variable references without scopes
// This might false fire with some debug flags, as not certain we don't have temporary
// clear varScopep's during some an infrequent dump just before we re-LinkDot.
UASSERT_OBJ(
!(v3Global.assertScoped() && m_inScope && nodep->varp() && !nodep->varScopep()), nodep,
"VarRef missing VarScope pointer");
}
virtual void visit(AstNode* nodep) override {
// Process not just iterate
processAndIterate(nodep);
}
public:
// CONSTRUCTORS
explicit BrokenCheckVisitor(AstNetlist* nodep) { iterate(nodep); }
virtual ~BrokenCheckVisitor() override = default;
};
//######################################################################
// Broken class functions
void V3Broken::brokenAll(AstNetlist* nodep) {
// UINFO(9, __FUNCTION__ << ": " << endl);
static bool inBroken = false;
if (VL_UNCOVERABLE(inBroken)) {
// A error called by broken can recurse back into broken; avoid this
UINFO(1, "Broken called under broken, skipping recursion.\n"); // LCOV_EXCL_LINE
} else {
inBroken = true;
BrokenTable::prepForTree();
BrokenMarkVisitor mvisitor(nodep);
BrokenCheckVisitor cvisitor(nodep);
BrokenTable::doneWithTree();
inBroken = false;
}
}
void V3Broken::addNewed(AstNode* nodep) { BrokenTable::addNewed(nodep); }
void V3Broken::deleted(AstNode* nodep) { BrokenTable::deleted(nodep); }
bool V3Broken::isAllocated(AstNode* nodep) { return BrokenTable::isAllocated(nodep); }
void V3Broken::selfTest() {
// Warmup addNewed and deleted for coverage, as otherwise only with VL_LEAK_CHECKS
FileLine* fl = new FileLine(FileLine::commandLineFilename());
auto* newp = new AstBegin(fl, "[EditWrapper]", nullptr);
#ifndef VL_LEAK_CHECKS
addNewed(newp);
deleted(newp);
#endif
VL_DO_DANGLING(delete newp, newp);
}