diff --git a/include/slang/ast/LValue.h b/include/slang/ast/LValue.h index 12d6cb4c3..6c47c52bf 100644 --- a/include/slang/ast/LValue.h +++ b/include/slang/ast/LValue.h @@ -18,16 +18,23 @@ namespace slang::ast { class SLANG_EXPORT LValue { public: /// A concatenation of lvalues is also an lvalue and can be assigned to. - using Concat = std::vector; + struct Concat { + std::vector elems; + enum Kind { Packed, Unpacked } kind; + + Concat(std::vector&& elems, Kind kind) : elems(std::move(elems)), kind(kind) {} + }; LValue() = default; LValue(nullptr_t) {} LValue(const LValue&) = delete; LValue(LValue&& other) = default; - explicit LValue(Concat&& concat) : value(std::move(concat)) {} explicit LValue(ConstantValue& base) : value(Path{base}) {} + LValue(std::vector&& elems, Concat::Kind kind) : + value(Concat(std::move(elems), kind)) {} + bool bad() const { return std::holds_alternative(value); } explicit operator bool() const { return !bad(); } diff --git a/include/slang/ast/expressions/AssignmentExpressions.h b/include/slang/ast/expressions/AssignmentExpressions.h index 319bbccad..a048521fe 100644 --- a/include/slang/ast/expressions/AssignmentExpressions.h +++ b/include/slang/ast/expressions/AssignmentExpressions.h @@ -247,10 +247,16 @@ class SLANG_EXPORT AssignmentPatternExpressionBase : public Expression { /// Represents an assignment pattern expression. class SLANG_EXPORT SimpleAssignmentPatternExpression : public AssignmentPatternExpressionBase { public: - SimpleAssignmentPatternExpression(const Type& type, std::span elements, + bool isLValue; + + SimpleAssignmentPatternExpression(const Type& type, bool isLValue, + std::span elements, SourceRange sourceRange) : AssignmentPatternExpressionBase(ExpressionKind::SimpleAssignmentPattern, type, elements, - sourceRange) {} + sourceRange), + isLValue(isLValue) {} + + LValue evalLValueImpl(EvalContext& context) const; static Expression& forStruct(Compilation& compilation, const syntax::SimpleAssignmentPatternSyntax& syntax, diff --git a/scripts/diagnostics.txt b/scripts/diagnostics.txt index 01bb676b6..629f5348f 100644 --- a/scripts/diagnostics.txt +++ b/scripts/diagnostics.txt @@ -625,7 +625,7 @@ error CHandleInAssertion "cannot reference variable of type 'chandle' in concurr error ClassMemberInAssertion "cannot reference non-static class members in concurrent assertion expressions" error InvalidArgumentExpr "sequence and property expressions are not valid in this context" error UnexpectedClockingExpr "unexpected expression after clocking event" -error ClockVarAssignConcat "clocking signal '{}' cannot be part of a concatenation lvalue" +error ClockVarAssignConcat "clocking signal '{}' cannot be part of a concatenation or assignment pattern lvalue" error CycleDelayNonClock "intra-assignment cycle delays can only be used with clocking signals" error ClockVarBadTiming "clocking signals cannot be driven with a timing control other than a cycle delay" error ClockVarOutputRead "cannot read from output clocking signal '{}'" @@ -672,6 +672,7 @@ error SpecifyPathConditionExpr "expression is not valid in a state-dependent pat error SpecifyPathBadReference "cannot refer to '{}' in state-dependent path condition expression; only locally defined nets and variables are allowed" error PathPulseInExpr "PATHPULSE$ specparams cannot be used in expressions" error InterfacePortInvalidExpression "invalid expression for interface port connection" +error AssignmentPatternLValueDynamic "lvalue assignment patterns cannot be assigned from dynamic arrays" warning ignored-slice IgnoredSlice "slice size ignored for left-to-right streaming operator" warning unsized-concat UnsizedInConcat "unsized integer in concat; using a width of {}" warning width-expand WidthExpand "implicit conversion expands from {} to {} bits" diff --git a/source/ast/Expression.cpp b/source/ast/Expression.cpp index f761ca9cb..137c164a4 100644 --- a/source/ast/Expression.cpp +++ b/source/ast/Expression.cpp @@ -198,10 +198,9 @@ const Expression& Expression::bindLValue(const ExpressionSyntax& lhs, const Type astFlags |= ASTFlags::LAndRValue; lhsExpr = &create(comp, lhs, context, astFlags, rhsExpr->type); + selfDetermined(context, lhsExpr); } - selfDetermined(context, lhsExpr); - bitmask assignFlags; if (instance) { if (isInout) @@ -466,6 +465,8 @@ bool Expression::requireLValue(const ASTContext& context, SourceLocation locatio } return true; } + case ExpressionKind::SimpleAssignmentPattern: + return as().isLValue; case ExpressionKind::Streaming: { SLANG_ASSERT(!longestStaticPrefix); auto& stream = as(); diff --git a/source/ast/LValue.cpp b/source/ast/LValue.cpp index b1827b27e..8473b33d8 100644 --- a/source/ast/LValue.cpp +++ b/source/ast/LValue.cpp @@ -34,10 +34,20 @@ ConstantValue LValue::load() const { auto concat = std::get_if(&value); if (concat) { - SmallVector vals; - for (auto& elem : *concat) - vals.push_back(elem.load().integer()); - return SVInt::concat(vals); + if (concat->kind == Concat::Packed) { + SmallVector vals; + vals.reserve(concat->elems.size()); + for (auto& elem : concat->elems) + vals.push_back(elem.load().integer()); + return SVInt::concat(vals); + } + else { + std::vector vals; + vals.reserve(concat->elems.size()); + for (auto& elem : concat->elems) + vals.push_back(elem.load()); + return vals; + } } // Otherwise, we have an lvalue path. Walk the path and apply each element. @@ -114,13 +124,22 @@ void LValue::store(const ConstantValue& newValue) { auto concat = std::get_if(&value); if (concat) { - // Divide up the value among all of the concatenated lvalues. - auto& sv = newValue.integer(); - int32_t msb = (int32_t)sv.getBitWidth() - 1; - for (auto& elem : *concat) { - int32_t width = (int32_t)elem.load().integer().getBitWidth(); - elem.store(sv.slice(msb, msb - width + 1)); - msb -= width; + if (concat->kind == Concat::Packed) { + // Divide up the value among all of the concatenated lvalues. + auto& sv = newValue.integer(); + int32_t msb = (int32_t)sv.getBitWidth() - 1; + for (auto& elem : concat->elems) { + int32_t width = (int32_t)elem.load().integer().getBitWidth(); + elem.store(sv.slice(msb, msb - width + 1)); + msb -= width; + } + } + else { + auto newElems = newValue.elements(); + auto& lvalElems = concat->elems; + SLANG_ASSERT(newElems.size() == lvalElems.size()); + for (size_t i = 0; i < lvalElems.size(); i++) + lvalElems[i].store(newElems[i]); } return; } diff --git a/source/ast/expressions/AssignmentExpressions.cpp b/source/ast/expressions/AssignmentExpressions.cpp index 155af7650..2d2d43952 100644 --- a/source/ast/expressions/AssignmentExpressions.cpp +++ b/source/ast/expressions/AssignmentExpressions.cpp @@ -1232,6 +1232,11 @@ Expression& Expression::bindAssignmentPattern(Compilation& comp, } const AssignmentPatternSyntax& p = *syntax.pattern; + if (context.flags.has(ASTFlags::LValue) && p.kind != SyntaxKind::SimpleAssignmentPattern) { + context.addDiag(diag::ExpressionNotAssignable, range); + return badExpr(comp, nullptr); + } + if (structScope) { switch (p.kind) { case SyntaxKind::SimpleAssignmentPattern: @@ -1403,17 +1408,19 @@ Expression& SimpleAssignmentPatternExpression::forStruct( return badExpr(comp, nullptr); } + const bool isLValue = context.flags.has(ASTFlags::LValue); + auto direction = isLValue ? ArgumentDirection::Out : ArgumentDirection::In; + bool bad = false; uint32_t index = 0; SmallVector elems; for (auto item : syntax.items) { - auto& expr = Expression::bindRValue(*types[index++], *item, - item->getFirstToken().location(), context); + auto& expr = Expression::bindArgument(*types[index++], direction, *item, context); elems.push_back(&expr); bad |= expr.bad(); } - auto result = comp.emplace(type, elems.copy(comp), + auto result = comp.emplace(type, isLValue, elems.copy(comp), sourceRange); if (bad) return badExpr(comp, result); @@ -1426,10 +1433,12 @@ static std::span bindExpressionList( const SeparatedSyntaxList& items, const ASTContext& context, SourceRange sourceRange, bool& bad) { + const bool isLValue = context.flags.has(ASTFlags::LValue); + auto direction = isLValue ? ArgumentDirection::Out : ArgumentDirection::In; + SmallVector elems; for (auto item : items) { - auto& expr = Expression::bindRValue(elementType, *item, item->getFirstToken().location(), - context); + auto& expr = Expression::bindArgument(elementType, direction, *item, context); elems.push_back(&expr); bad |= expr.bad(); } @@ -1451,7 +1460,9 @@ Expression& SimpleAssignmentPatternExpression::forFixedArray( auto elems = bindExpressionList(type, elementType, 1, numElements, syntax.items, context, sourceRange, bad); - auto result = comp.emplace(type, elems, sourceRange); + const bool isLValue = context.flags.has(ASTFlags::LValue); + auto result = comp.emplace(type, isLValue, elems, + sourceRange); if (bad) return badExpr(comp, result); @@ -1462,17 +1473,39 @@ Expression& SimpleAssignmentPatternExpression::forDynamicArray( Compilation& comp, const SimpleAssignmentPatternSyntax& syntax, const ASTContext& context, const Type& type, const Type& elementType, SourceRange sourceRange) { + const bool isLValue = context.flags.has(ASTFlags::LValue); + if (isLValue) { + context.addDiag(diag::AssignmentPatternLValueDynamic, sourceRange); + return badExpr(comp, nullptr); + } + bool bad = false; auto elems = bindExpressionList(type, elementType, 1, 0, syntax.items, context, sourceRange, bad); - auto result = comp.emplace(type, elems, sourceRange); + auto result = comp.emplace(type, isLValue, elems, + sourceRange); if (bad) return badExpr(comp, result); return *result; } +LValue SimpleAssignmentPatternExpression::evalLValueImpl(EvalContext& context) const { + std::vector lvals; + lvals.reserve(elements().size()); + for (auto elem : elements()) { + LValue lval = elem->as().left().evalLValue(context); + if (!lval) + return nullptr; + + lvals.emplace_back(std::move(lval)); + } + + auto lvalKind = type->isIntegral() ? LValue::Concat::Packed : LValue::Concat::Unpacked; + return LValue(std::move(lvals), lvalKind); +} + static const Expression* matchElementValue( const ASTContext& context, const Type& elementType, const FieldSymbol* targetField, SourceRange sourceRange, @@ -1544,8 +1577,8 @@ static const Expression* matchElementValue( } auto& comp = context.getCompilation(); - return comp.emplace(elementType, elements.copy(comp), - sourceRange); + return comp.emplace(elementType, /* isLValue */ false, + elements.copy(comp), sourceRange); } if (elementType.isArray() && elementType.hasFixedRange()) { @@ -1563,8 +1596,8 @@ static const Expression* matchElementValue( elements.push_back(elemExpr); auto& comp = context.getCompilation(); - return comp.emplace(elementType, elements.copy(comp), - sourceRange); + return comp.emplace(elementType, /* isLValue */ false, + elements.copy(comp), sourceRange); } // Finally, if we have a default then it must now be assignment compatible. diff --git a/source/ast/expressions/OperatorExpressions.cpp b/source/ast/expressions/OperatorExpressions.cpp index b48544e2e..252e7494e 100644 --- a/source/ast/expressions/OperatorExpressions.cpp +++ b/source/ast/expressions/OperatorExpressions.cpp @@ -1553,7 +1553,7 @@ LValue ConcatenationExpression::evalLValueImpl(EvalContext& context) const { lvals.emplace_back(std::move(lval)); } - return LValue(std::move(lvals)); + return LValue(std::move(lvals), LValue::Concat::Packed); } void ConcatenationExpression::serializeTo(ASTSerializer& serializer) const { diff --git a/tests/unittests/ast/ExpressionTests.cpp b/tests/unittests/ast/ExpressionTests.cpp index 64006ce49..2b149dfd3 100644 --- a/tests/unittests/ast/ExpressionTests.cpp +++ b/tests/unittests/ast/ExpressionTests.cpp @@ -3065,3 +3065,81 @@ endmodule compilation.addSyntaxTree(tree); NO_COMPILATION_ERRORS; } + +TEST_CASE("Assignment patterns as lvalues") { + auto tree = SyntaxTree::fromText(R"( +module m; + typedef byte U[3]; + function automatic U f1; + var U A = '{1, 2, 3}; + var byte a, b, c; + '{a, b, c} = A; + U'{c, a, b} = '{a+1, b+1, c+1}; + return {a, b, c}; + endfunction + + typedef struct packed { int i; logic j; } V; + function automatic V f2; + V v = '{32, 1}; + int i; + logic j; + '{i, j} = v; + return '{i, j}; + endfunction + + typedef struct { real r; string s; } W; + function automatic W f3; + W w = '{3.14, "Hello World"}; + real r; + string s; + '{r, s} = w; + return '{r, s}; + endfunction + + localparam U u = f1(); + localparam V v = f2(); + localparam W w = f3(); +endmodule +)"); + + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + + auto& root = compilation.getRoot(); + auto& u = root.lookupName("m.u"); + CHECK(u.getValue().toString() == "[8'sd3,8'sd4,8'sd2]"); + + auto& v = root.lookupName("m.v"); + CHECK(v.getValue().integer() == 65); + + auto& w = root.lookupName("m.w"); + CHECK(w.getValue().toString() == "[3.14,\"Hello World\"]"); +} + +TEST_CASE("Invalid assigment pattern lvalues") { + auto tree = SyntaxTree::fromText(R"( +module m; + typedef struct { real r; string s; } U; + function automatic void f1; + real r; + string s; + U'{r:r, s:s} = '{3.14, "Hello World"}; + endfunction + + function automatic void f2; + int i[]; + int j, k; + '{j, k} = i; + endfunction +endmodule +)"); + + Compilation compilation; + compilation.addSyntaxTree(tree); + + auto& diags = compilation.getAllDiagnostics(); + REQUIRE(diags.size() == 2); + CHECK(diags[0].code == diag::ExpressionNotAssignable); + CHECK(diags[1].code == diag::AssignmentPatternLValueDynamic); +}