Skip to content

Commit

Permalink
Add support for lvalue assignment patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
MikePopoloski committed Nov 15, 2023
1 parent 75ed9b4 commit 515b5e5
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 30 deletions.
11 changes: 9 additions & 2 deletions include/slang/ast/LValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<LValue>;
struct Concat {
std::vector<LValue> elems;
enum Kind { Packed, Unpacked } kind;

Concat(std::vector<LValue>&& 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<LValue>&& elems, Concat::Kind kind) :
value(Concat(std::move(elems), kind)) {}

bool bad() const { return std::holds_alternative<std::monostate>(value); }
explicit operator bool() const { return !bad(); }

Expand Down
10 changes: 8 additions & 2 deletions include/slang/ast/expressions/AssignmentExpressions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<const Expression* const> elements,
bool isLValue;

SimpleAssignmentPatternExpression(const Type& type, bool isLValue,
std::span<const Expression* const> 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,
Expand Down
3 changes: 2 additions & 1 deletion scripts/diagnostics.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 '{}'"
Expand Down Expand Up @@ -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"
Expand Down
5 changes: 3 additions & 2 deletions source/ast/Expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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> assignFlags;
if (instance) {
if (isInout)
Expand Down Expand Up @@ -466,6 +465,8 @@ bool Expression::requireLValue(const ASTContext& context, SourceLocation locatio
}
return true;
}
case ExpressionKind::SimpleAssignmentPattern:
return as<SimpleAssignmentPatternExpression>().isLValue;
case ExpressionKind::Streaming: {
SLANG_ASSERT(!longestStaticPrefix);
auto& stream = as<StreamingConcatenationExpression>();
Expand Down
41 changes: 30 additions & 11 deletions source/ast/LValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,20 @@ ConstantValue LValue::load() const {

auto concat = std::get_if<Concat>(&value);
if (concat) {
SmallVector<SVInt> vals;
for (auto& elem : *concat)
vals.push_back(elem.load().integer());
return SVInt::concat(vals);
if (concat->kind == Concat::Packed) {
SmallVector<SVInt> vals;
vals.reserve(concat->elems.size());
for (auto& elem : concat->elems)
vals.push_back(elem.load().integer());
return SVInt::concat(vals);
}
else {
std::vector<ConstantValue> 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.
Expand Down Expand Up @@ -114,13 +124,22 @@ void LValue::store(const ConstantValue& newValue) {

auto concat = std::get_if<Concat>(&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;
}
Expand Down
55 changes: 44 additions & 11 deletions source/ast/expressions/AssignmentExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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<const Expression*> 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<SimpleAssignmentPatternExpression>(type, elems.copy(comp),
auto result = comp.emplace<SimpleAssignmentPatternExpression>(type, isLValue, elems.copy(comp),
sourceRange);
if (bad)
return badExpr(comp, result);
Expand All @@ -1426,10 +1433,12 @@ static std::span<const Expression* const> bindExpressionList(
const SeparatedSyntaxList<ExpressionSyntax>& items, const ASTContext& context,
SourceRange sourceRange, bool& bad) {

const bool isLValue = context.flags.has(ASTFlags::LValue);
auto direction = isLValue ? ArgumentDirection::Out : ArgumentDirection::In;

SmallVector<const Expression*> 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();
}
Expand All @@ -1451,7 +1460,9 @@ Expression& SimpleAssignmentPatternExpression::forFixedArray(
auto elems = bindExpressionList(type, elementType, 1, numElements, syntax.items, context,
sourceRange, bad);

auto result = comp.emplace<SimpleAssignmentPatternExpression>(type, elems, sourceRange);
const bool isLValue = context.flags.has(ASTFlags::LValue);
auto result = comp.emplace<SimpleAssignmentPatternExpression>(type, isLValue, elems,
sourceRange);
if (bad)
return badExpr(comp, result);

Expand All @@ -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<SimpleAssignmentPatternExpression>(type, elems, sourceRange);
auto result = comp.emplace<SimpleAssignmentPatternExpression>(type, isLValue, elems,
sourceRange);
if (bad)
return badExpr(comp, result);

return *result;
}

LValue SimpleAssignmentPatternExpression::evalLValueImpl(EvalContext& context) const {
std::vector<LValue> lvals;
lvals.reserve(elements().size());
for (auto elem : elements()) {
LValue lval = elem->as<AssignmentExpression>().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,
Expand Down Expand Up @@ -1544,8 +1577,8 @@ static const Expression* matchElementValue(
}

auto& comp = context.getCompilation();
return comp.emplace<SimpleAssignmentPatternExpression>(elementType, elements.copy(comp),
sourceRange);
return comp.emplace<SimpleAssignmentPatternExpression>(elementType, /* isLValue */ false,
elements.copy(comp), sourceRange);
}

if (elementType.isArray() && elementType.hasFixedRange()) {
Expand All @@ -1563,8 +1596,8 @@ static const Expression* matchElementValue(
elements.push_back(elemExpr);

auto& comp = context.getCompilation();
return comp.emplace<SimpleAssignmentPatternExpression>(elementType, elements.copy(comp),
sourceRange);
return comp.emplace<SimpleAssignmentPatternExpression>(elementType, /* isLValue */ false,
elements.copy(comp), sourceRange);
}

// Finally, if we have a default then it must now be assignment compatible.
Expand Down
2 changes: 1 addition & 1 deletion source/ast/expressions/OperatorExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
78 changes: 78 additions & 0 deletions tests/unittests/ast/ExpressionTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ParameterSymbol>("m.u");
CHECK(u.getValue().toString() == "[8'sd3,8'sd4,8'sd2]");

auto& v = root.lookupName<ParameterSymbol>("m.v");
CHECK(v.getValue().integer() == 65);

auto& w = root.lookupName<ParameterSymbol>("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);
}

0 comments on commit 515b5e5

Please sign in to comment.