Skip to content

Commit 54a6363

Browse files
committed
Compilation: Add CheckUninstantiated flag
stack-info: PR: MikePopoloski#1901, branch: AndrewNolte/stack/18
1 parent 73090d4 commit 54a6363

8 files changed

Lines changed: 153 additions & 25 deletions

File tree

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 {

include/slang/ast/Symbol.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ class SLANG_EXPORT Symbol {
173173
/// Determines whether this symbol represents a value.
174174
bool isValue() const;
175175

176+
/// Determines whether this symbol is instantiated.
177+
bool isInstantiated() const;
178+
176179
/// If the symbol has a declared type, returns a pointer to it. Otherwise returns nullptr.
177180
const DeclaredType* getDeclaredType() const;
178181

include/slang/diagnostics/Diagnostics.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ class SLANG_EXPORT DiagCode {
9999
/// Explicit boolean conversion operator that defers to @a valid
100100
constexpr explicit operator bool() const { return valid(); }
101101

102+
/// Wrapper type for use in switch statements.
103+
struct Key {
104+
uint32_t value;
105+
constexpr operator uint32_t() const { return value; }
106+
};
107+
108+
/// Returns a switchable key for use in switch/case statements.
109+
constexpr Key key() const {
110+
return {(uint32_t(subsystem) << 16) | code};
111+
}
112+
102113
/// Three way comparison.
103114
constexpr friend auto operator<=>(DiagCode left, DiagCode right) = default;
104115

source/ast/Compilation.cpp

Lines changed: 34 additions & 16 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,47 @@ void Compilation::addDiagnostics(const Diagnostics& diagnostics) {
16881689
addDiag(diag);
16891690
}
16901691

1692+
bool shouldReportUninstantiatedDiag(const DiagCode& code) {
1693+
switch (code.getSubsystem()) {
1694+
case DiagSubsystem::Declarations:
1695+
return true;
1696+
case DiagSubsystem::Lookup:
1697+
switch (code.key()) {
1698+
case diag::ScopeIndexOutOfRange.key():
1699+
case diag::InvalidScopeIndexExpression.key():
1700+
case diag::CouldNotResolveHierarchicalPath.key():
1701+
case diag::DotIntoInstArray.key():
1702+
return false;
1703+
default:
1704+
break;
1705+
}
1706+
return true;
1707+
default:
1708+
break;
1709+
}
1710+
1711+
return false;
1712+
}
1713+
16911714
Diagnostic& Compilation::addDiag(Diagnostic diag) {
16921715
SLANG_ASSERT(!isFrozen());
16931716

1694-
if (diagsDisabled) {
1717+
auto suppressDiag = [&]() -> Diagnostic& {
16951718
tempDiag = std::move(diag);
16961719
return tempDiag;
1697-
}
1698-
1699-
auto isSuppressed = [](const Symbol* symbol) {
1700-
while (symbol) {
1701-
if (symbol->kind == SymbolKind::GenerateBlock)
1702-
return symbol->as<GenerateBlockSymbol>().isUninstantiated;
1703-
1704-
auto scope = symbol->getParentScope();
1705-
symbol = scope ? &scope->asSymbol() : nullptr;
1706-
}
1707-
return false;
17081720
};
17091721

1722+
if (diagsDisabled)
1723+
return suppressDiag();
1724+
17101725
// Filter out diagnostics that came from inside an uninstantiated generate block.
17111726
SLANG_ASSERT(diag.symbol);
17121727
SLANG_ASSERT(diag.location);
1713-
if (isSuppressed(diag.symbol)) {
1714-
tempDiag = std::move(diag);
1715-
return tempDiag;
1728+
1729+
if (!diag.symbol->isInstantiated()) {
1730+
if (!hasFlag(CompilationFlags::CheckUninstantiated) ||
1731+
!shouldReportUninstantiatedDiag(diag.code))
1732+
return suppressDiag();
17161733
}
17171734

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

24502467
Diagnostic* Compilation::errorMissingDef(std::string_view name, const Scope& scope,
24512468
SourceRange sourceRange, DiagCode code) const {
2452-
if (hasFlag(CompilationFlags::IgnoreUnknownModules) || scope.isUninstantiated() || name.empty())
2469+
if (hasFlag(CompilationFlags::IgnoreUnknownModules) || name.empty() ||
2470+
(scope.isUninstantiated() && !hasFlag(CompilationFlags::CheckUninstantiated)))
24532471
return nullptr;
24542472

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

source/ast/Symbol.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@ const DeclaredType* Symbol::getDeclaredType() const {
9090
}
9191
}
9292

93+
bool Symbol::isInstantiated() const {
94+
auto symbol = this;
95+
while (symbol) {
96+
if (symbol->kind == SymbolKind::GenerateBlock)
97+
return !(symbol->as<GenerateBlockSymbol>().isUninstantiated);
98+
99+
auto scope = symbol->getParentScope();
100+
symbol = scope ? &scope->asSymbol() : nullptr;
101+
}
102+
return true;
103+
}
104+
93105
static void getHierarchicalPathImpl(const Symbol& symbol, FormatBuffer& buffer,
94106
SmallSet<const Symbol*, 4>& visited) {
95107
auto scope = symbol.getParentScope();

source/ast/symbols/BlockSymbols.cpp

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

914914
result->entries = entries.copy(comp);
915915
if (entries.empty()) {
916+
// Keep result->entries empty so indexed lookup (g[0], etc.) still reports
917+
// the same out-of-range errors for zero-iteration arrays. The synthetic
918+
// block is only exposed through generic member traversal; callers that
919+
// don't want speculative semantic-checking contents can ignore members
920+
// where isInstantiated() is false.
916921
createBlock(SVInt(32, 0, true), true);
917922
}
918-
else {
919-
for (auto entry : entries)
920-
result->addMember(*entry);
921-
}
923+
for (auto entry : entries)
924+
result->addMember(*entry);
922925

923926
return *result;
924927
}

source/ast/symbols/InstanceSymbols.cpp

Lines changed: 9 additions & 4 deletions
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
}
@@ -967,11 +968,15 @@ InstanceBodySymbol& InstanceBodySymbol::fromDefinition(
967968

968969
ParameterBuilder paramBuilder(*definition.getParentScope(), definition.name,
969970
definition.parameters);
970-
paramBuilder.setForceInvalidValues(flags.has(InstanceFlags::Uninstantiated));
971-
if (compilation.hasFlag(CompilationFlags::AllowInvalidTop) &&
972-
instanceLoc == definition.location) {
971+
972+
if (flags.has(InstanceFlags::Uninstantiated)) {
973+
paramBuilder.setForceInvalidValues(true);
974+
}
975+
else if (compilation.hasFlag(CompilationFlags::AllowInvalidTop) &&
976+
instanceLoc == definition.location) {
973977
paramBuilder.setUseInvalidForMissing(true);
974978
}
979+
975980
if (hierarchyOverrideNode)
976981
paramBuilder.setOverrides(hierarchyOverrideNode);
977982

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)