Skip to content

Commit 0421c30

Browse files
committed
Compilation: Add CheckUninstantiated flag
This compilation flag enables more checks to be done in untaken generate branches, or gen loops with invalid loop params. stack-info: PR: #1901, branch: AndrewNolte/stack/18
1 parent 1e5b8b8 commit 0421c30

6 files changed

Lines changed: 121 additions & 11 deletions

File tree

bindings/python/CompBindings.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ void registerCompilation(py::module_& m, py::module_& ast, py::module_& driver)
7171
.value("AllowArrayConcatAssignPattern", CompilationFlags::AllowArrayConcatAssignPattern)
7272
.value("AllowCrossAutoBinMax", CompilationFlags::AllowCrossAutoBinMax)
7373
.value("AllowInvalidTop", CompilationFlags::AllowInvalidTop)
74+
.value("CheckUninstantiated", CompilationFlags::CheckUninstantiated)
7475
.finalize();
7576

7677
py::classh<CompilationOptions>(ast, "CompilationOptions")

include/slang/ast/Compilation.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,13 @@ enum class SLANG_EXPORT CompilationFlags {
145145

146146
/// Allow top-level modules to be selected even when their parameters have no defaults.
147147
AllowInvalidTop = 1 << 20,
148+
149+
/// Elaborate code that would normally be skipped because it is uninstantiated
150+
/// (untaken generate branches and uninstantiated module instances) so that
151+
/// additional lints, like port and parameter name checks, can run on it.
152+
CheckUninstantiated = 1 << 21,
148153
};
149-
SLANG_BITMASK(CompilationFlags, AllowInvalidTop)
154+
SLANG_BITMASK(CompilationFlags, CheckUninstantiated)
150155

151156
/// Contains various options that can control compilation behavior.
152157
struct SLANG_EXPORT CompilationOptions {

source/ast/Compilation.cpp

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "slang/ast/SystemSubroutine.h"
1717
#include "slang/ast/types/TypePrinter.h"
1818
#include "slang/diagnostics/DiagnosticEngine.h"
19+
#include "slang/diagnostics/Diagnostics.h"
1920
#include "slang/diagnostics/LookupDiags.h"
2021
#include "slang/parsing/Parser.h"
2122
#include "slang/parsing/Preprocessor.h"
@@ -1688,31 +1689,56 @@ void Compilation::addDiagnostics(const Diagnostics& diagnostics) {
16881689
addDiag(diag);
16891690
}
16901691

1692+
bool shouldReportUninstantiatedDiag(const DiagCode& code) {
1693+
static const flat_hash_set<DiagCode> BadLookupDiags = {
1694+
diag::ScopeIndexOutOfRange,
1695+
diag::InvalidScopeIndexExpression,
1696+
diag::CouldNotResolveHierarchicalPath,
1697+
diag::DotIntoInstArray,
1698+
};
1699+
1700+
switch (code.getSubsystem()) {
1701+
case DiagSubsystem::Declarations:
1702+
return true;
1703+
case DiagSubsystem::Lookup:
1704+
return !BadLookupDiags.contains(code);
1705+
default:
1706+
break;
1707+
}
1708+
1709+
return false;
1710+
}
1711+
16911712
Diagnostic& Compilation::addDiag(Diagnostic diag) {
16921713
SLANG_ASSERT(!isFrozen());
16931714

1694-
if (diagsDisabled) {
1715+
auto suppressDiag = [&]() -> Diagnostic& {
16951716
tempDiag = std::move(diag);
16961717
return tempDiag;
1697-
}
1718+
};
16981719

1699-
auto isSuppressed = [](const Symbol* symbol) {
1720+
if (diagsDisabled)
1721+
return suppressDiag();
1722+
1723+
auto isInstantiated = [](const Symbol* symbol) {
17001724
while (symbol) {
17011725
if (symbol->kind == SymbolKind::GenerateBlock)
1702-
return symbol->as<GenerateBlockSymbol>().isUninstantiated;
1726+
return !symbol->as<GenerateBlockSymbol>().isUninstantiated;
17031727

17041728
auto scope = symbol->getParentScope();
17051729
symbol = scope ? &scope->asSymbol() : nullptr;
17061730
}
1707-
return false;
1731+
return true;
17081732
};
17091733

17101734
// Filter out diagnostics that came from inside an uninstantiated generate block.
17111735
SLANG_ASSERT(diag.symbol);
17121736
SLANG_ASSERT(diag.location);
1713-
if (isSuppressed(diag.symbol)) {
1714-
tempDiag = std::move(diag);
1715-
return tempDiag;
1737+
1738+
if (!isInstantiated(diag.symbol)) {
1739+
if (!hasFlag(CompilationFlags::CheckUninstantiated) ||
1740+
!shouldReportUninstantiatedDiag(diag.code))
1741+
return suppressDiag();
17161742
}
17171743

17181744
const bool isError = diag.isError();
@@ -2449,7 +2475,8 @@ std::pair<Compilation::DefinitionLookupResult, bool> Compilation::resolveConfigR
24492475

24502476
Diagnostic* Compilation::errorMissingDef(std::string_view name, const Scope& scope,
24512477
SourceRange sourceRange, DiagCode code) const {
2452-
if (hasFlag(CompilationFlags::IgnoreUnknownModules) || scope.isUninstantiated() || name.empty())
2478+
if (hasFlag(CompilationFlags::IgnoreUnknownModules) || name.empty() ||
2479+
(scope.isUninstantiated() && !hasFlag(CompilationFlags::CheckUninstantiated)))
24532480
return nullptr;
24542481

24552482
if (auto def = getExternDefinition(name, scope)) {

source/ast/symbols/BlockSymbols.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,12 @@ GenerateBlockArraySymbol& GenerateBlockArraySymbol::fromSyntax(Compilation& comp
913913

914914
result->entries = entries.copy(comp);
915915
if (entries.empty()) {
916+
// Create block and add it so visitors can access the uninstantiated block; by
917+
// default diags from uninstantiated blocks are suppressed, so this is safe. This is used
918+
// for things like unused checking and with CompilationFlags::CheckUninstantiated.
919+
// It is still inacessible from gen_arr[0] due to how lookups work.
916920
createBlock(SVInt(32, 0, true), true);
921+
result->addMember(*entries[0]);
917922
}
918923
else {
919924
for (auto entry : entries)

source/ast/symbols/InstanceSymbols.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,8 @@ void InstanceSymbol::fromSyntax(Compilation& comp, const HierarchyInstantiationS
505505

506506
// If this instance is not instantiated then we'll just fill in a placeholder
507507
// and move on. This is likely inside an untaken generate branch.
508-
if (flags.has(InstanceFlags::Uninstantiated)) {
508+
if (flags.has(InstanceFlags::Uninstantiated) &&
509+
!comp.hasFlag(CompilationFlags::CheckUninstantiated)) {
509510
UninstantiatedDefSymbol::fromSyntax(comp, syntax, context, results, implicitNets);
510511
return;
511512
}

tests/unittests/ast/HierarchyTests.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1520,6 +1520,77 @@ endmodule
15201520
NO_COMPILATION_ERRORS;
15211521
}
15221522

1523+
TEST_CASE("Check uninstantiated reports bindable errors") {
1524+
auto text = R"(
1525+
module child(input logic a);
1526+
endmodule
1527+
1528+
module top;
1529+
if (0) begin
1530+
child child(.missing(1'b1));
1531+
missing missing();
1532+
string s;
1533+
initial s.foobar();
1534+
end
1535+
endmodule
1536+
)";
1537+
1538+
auto hasCode = [](auto& diags, DiagCode code) {
1539+
return std::ranges::any_of(diags, [&](auto& diag) { return diag.code == code; });
1540+
};
1541+
1542+
// By default the contents of an untaken generate branch are not elaborated, so
1543+
// none of these problems are reported.
1544+
{
1545+
Compilation compilation;
1546+
compilation.addSyntaxTree(SyntaxTree::fromText(text));
1547+
NO_COMPILATION_ERRORS;
1548+
}
1549+
1550+
// With the flag the branch is elaborated and structural / lookup errors surface.
1551+
{
1552+
CompilationOptions options;
1553+
options.flags |= CompilationFlags::CheckUninstantiated;
1554+
1555+
Compilation compilation(options);
1556+
compilation.addSyntaxTree(SyntaxTree::fromText(text));
1557+
1558+
auto& diags = compilation.getAllDiagnostics();
1559+
CHECK(hasCode(diags, diag::PortDoesNotExist));
1560+
CHECK(hasCode(diags, diag::UnknownModule));
1561+
CHECK(hasCode(diags, diag::UnknownSystemMethod));
1562+
}
1563+
}
1564+
1565+
TEST_CASE("Check uninstantiated still suppresses dead-code-only diagnostics") {
1566+
// Diagnostics that would be false positives in never-taken code (e.g. an out
1567+
// of bounds index that is only reachable in the untaken branch) stay
1568+
// suppressed even when CheckUninstantiated is enabled.
1569+
auto text = R"(
1570+
module top;
1571+
logic [3:0] arr;
1572+
if (0) begin
1573+
wire w = arr[7];
1574+
end
1575+
endmodule
1576+
)";
1577+
1578+
auto hasCode = [](auto& diags, DiagCode code) {
1579+
return std::ranges::any_of(diags, [&](auto& diag) { return diag.code == code; });
1580+
};
1581+
1582+
{
1583+
CompilationOptions options;
1584+
options.flags |= CompilationFlags::CheckUninstantiated;
1585+
1586+
Compilation compilation(options);
1587+
compilation.addSyntaxTree(SyntaxTree::fromText(text));
1588+
1589+
auto& diags = compilation.getAllDiagnostics();
1590+
CHECK_FALSE(hasCode(diags, diag::IndexOOB));
1591+
}
1592+
}
1593+
15231594
TEST_CASE("Bind directives") {
15241595
auto tree = SyntaxTree::fromText(R"(
15251596
module baz(input q);

0 commit comments

Comments
 (0)