-
Notifications
You must be signed in to change notification settings - Fork 119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[slang][lang-compliance] Introduce nondegeneracy check #998
[slang][lang-compliance] Introduce nondegeneracy check #998
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #998 +/- ##
==========================================
- Coverage 94.24% 94.14% -0.10%
==========================================
Files 191 191
Lines 47344 47568 +224
==========================================
+ Hits 44618 44785 +167
- Misses 2726 2783 +57
... and 2 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
Thank you for tackling this; the sections of the LRM that address this are daunting which is why I had not implemented it yet. I'll have to review this quite closely. One thing that can help inform additional tests is looking at the coverage report, which shows some big chunks that haven't been tested at all yet. |
@@ -91,12 +96,22 @@ class SLANG_EXPORT AssertionExpr { | |||
/// and false otherwise. | |||
bool admitsEmpty() const; | |||
|
|||
bool isAdmitsOnlyEmpty() const { return admitsOnlyEmpty; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you rework these three functions into a single one that returns the admits empty / no-match properties via a single flags enum? You can move the AdmitsEmpty
enum up here and expand it to include a bit for the no-match part as well. Then you won't need the clunky extra admitsOnlyEmpty
member below that is only validly set when you call admitsEmpty first, which seems like a footgun.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for suggestion, fixed)!
|
||
bool operator==(const SequenceRange& right) const; | ||
|
||
bool operator>(const SequenceRange& right) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can instead implement all of the comparison operators by implementing the "spaceship" operator: operator<=>
to simplify this a bunch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
currently it's failed on MacOS build - I'm working with it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I chose to implement only the operator<
and changed the comparison logic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spaceship (<=>
) operator requires realization for any field but for std::optional
that operator marked as deleted. It requires for changing processing of SequenceRange
s with unbonded maximum I don't have any good idea for resolving with another way.
@@ -417,6 +490,71 @@ void SequenceRange::serializeTo(ASTSerializer& serializer) const { | |||
serializer.write("max", "$"sv); | |||
} | |||
|
|||
bool SequenceRange::operator!=(const SequenceRange& right) const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the logic here could just be defaulted? You're just comparing field by field right? So operator==(const SourceRange&) = default;
should have the correct behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it was removed in terms of fixing a previous comment
return (*this == right) || (*this < right); | ||
} | ||
|
||
/// `SequenceRange`s intersects if max delay of one range |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation comments should go in the header, otherwise I don't think they'll be picked up by doxygen into the doc build.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved
context.addDiag(diag::SeqPropAdmitEmpty, syntax.expr->sourceRange()); | ||
// Any sequence that is used as a property shall be nondegenerate and shall not admit | ||
// any empty match. | ||
if (bool isSeqAdmitsEmpty = expr.admitsEmpty(); isSeqAdmitsEmpty || expr.admitsNoMatch()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than checking this again here can you rely on the bind() call above to do it by passing the right flags?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
currently I don't have any idea how to correctly fix that because there is call to bind
overload which accepts SequenceExprSyntax
instead of PropertyExprSyntax
there are no way to fix that without changes in bind
call behavior or StrongWeakPropertyExprSyntax struct
/// Returns maximum possible sequence length beetween all case labels sequence expressions | ||
/// include `default` label | ||
std::optional<SequenceRange> CaseAssertionExpr::computeSequenceLengthImpl() const { | ||
std::set<SequenceRange> seqRangeItems; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're just using the set for sorting here right? It's generally a good idea to avoid allocating containers unless strictly necessary, and in this case I think you can just store the current maximum and update it on each iteration if the new value is larger? You don't actually need to store all the items.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@@ -1178,6 +1539,14 @@ void CaseAssertionExpr::serializeTo(ASTSerializer& serializer) const { | |||
serializer.write("defaultCase", *defaultCase); | |||
} | |||
|
|||
/// Returns `nullopt` if `disable iff` condition is always `true` (`1`) | |||
std::optional<SequenceRange> DisableIffAssertionExpr::computeSequenceLengthImpl() const { | |||
if (condition.constant && condition.constant->integer().countOnes()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ConstantValue::isTrue()
function is probably a nicer way to express this than calling countOnes()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
static const AssertionExpr& bind(const syntax::SequenceExprSyntax& syntax, | ||
const ASTContext& context, bool allowDisable = false); | ||
|
||
static const AssertionExpr& bind(const syntax::PropertyExprSyntax& syntax, | ||
const ASTContext& context, bool allowDisable = false, | ||
bool allowSeqAdmitEmpty = false); | ||
bool isFollowedByProp = false, bool isOverlapImpl = false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is becoming too many boolean arguments in a row, which makes it easy to mess up the ordering of them when calling the function. I think this should be made a separate flags enum.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
3050cdf
to
6895b51
Compare
At the moment I only added a few tests to increase the possible coverage but at all it still in progress |
And also add support for checking nondegenerancy for lhs of "followed-by property" properties (e.g. |
(16.12.22 SystemVerilog LRM section)
0829546
to
85c8dae
Compare
@@ -7,6 +7,10 @@ | |||
//------------------------------------------------------------------------------ | |||
#pragma once | |||
|
|||
#include <optional> | |||
#include <set> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer need to include set here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed all unnecessary headers which I was added early
bool allowSeqAdmitEmpty = false); | ||
static const AssertionExpr& bind( | ||
const syntax::PropertyExprSyntax& syntax, const ASTContext& context, | ||
bool allowDisable = false, const NondegeneracyFlags nondegFlags = NondegeneracyFlags::None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The const
here does nothing -- since this is passed by value a copy will be made, which is fine, it's a small type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@@ -239,11 +295,19 @@ class SLANG_EXPORT SimpleAssertionExpr : public AssertionExpr { | |||
/// An optional repetition of the sequence. | |||
std::optional<SequenceRepetition> repetition; | |||
|
|||
SimpleAssertionExpr(const Expression& expr, std::optional<SequenceRepetition> repetition) : | |||
AssertionExpr(AssertionExprKind::Simple), expr(expr), repetition(repetition) {} | |||
/// Store `true` if sequence exression is can be evaluated as constant `false` (`0`). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"expression", -is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@@ -7,6 +7,9 @@ | |||
//------------------------------------------------------------------------------ | |||
#include "slang/ast/expressions/AssertionExpr.h" | |||
|
|||
#include <iostream> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iostream include should be unnecessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -417,6 +471,47 @@ void SequenceRange::serializeTo(ASTSerializer& serializer) const { | |||
serializer.write("max", "$"sv); | |||
} | |||
|
|||
bool SequenceRange::operator<(const SequenceRange& right) const { | |||
// if both sequences are unbounded than check min's |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/than/then
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
// If condition of `disable iff` can be evaluated only as `true` (`1`) than sequence property | ||
// is no match | ||
if (const auto evaluatedValue = context.tryEval(cond); | ||
evaluatedValue.isInteger() && evaluatedValue.integer().countOnes()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isTrue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed for all locations when I used countOnes
std::optional<SequenceRange> max; | ||
for (const auto& item : items) { | ||
if (const auto itemLen = item.body->computeSequenceLength(); itemLen.has_value()) { | ||
if (const auto itemLenVal = itemLen.value(); max.has_value() || max < itemLenVal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this meant to be !max.has_value()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes it's typo - fixed
: res; | ||
break; | ||
} | ||
case BinaryAssertionOperator::And: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You no longer set the admitsEmpty flag for the And case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
: res; | ||
break; | ||
} | ||
case BinaryAssertionOperator::Intersect: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
case BinaryAssertionOperator::Throughout: { | ||
res = (rightAdmitsEmpty) ? res | NondegeneracyStatus::AdmitsEmpty : res; | ||
|
||
// In case of `throught` full sequence is no match if right part is no match |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/throught/throughout
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
Ok, I think this is good enough to land -- we can tweak or improve it later if needed. |
* master: (37 commits) Assertion expressions: only sequences can be nondegenerate, not properties Fix MSVC warning Be better about preserving the types of packed structs and unions used in binary operators Fix parameter default value visitor Cleanup of recent assertion expr changes; no change in behavior [slang][lang-compliance] Introduce nondegeneracy check (MikePopoloski#998) Update CHANGELOG.md Add Warith-op-mismatch, Wbitwise-op-mismatch, Wcomparison-mismatch, and Wsign-compare warnings Fix array type of cover symbol with-expression iterators Improve diagnostic location for constant conversion warnings Refactor Expression::getEffectiveSign to return a 3-state result Fix checking of sequence concat delay with single bracketed delay expression Add Wunused-import and Wunused-wildcard-import Improve parser error when encountering extra end delimiter in member list Don't add invalid generic class specializations to internal specialization map Add optional compiler directives from annex E Add optional system tasks and functions from annex D Make error for invalid state-dependent path conditions suppressible Update CHANGELOG.md v1800-2023 clarification: only specific bidi switches allow UDNT connections ...
Based on 16.12.22 SystemVerilog LRM section and partially (F.4.3/F.5.2/F.5.5).
Also Ben Cohen's paper about nondegeneracy understanding from here.
Complex sequences are not often used in industrial/open source designs, but I think these checks will help to verify restrictions from 16.12.22 in most cases.
I written about ~300 lines of
sv
tests but i need help with more complex test cases but if anyone could also come up with more complex tests to test heuristics, that would be nice.