Support for condition operator on class objects (#4214)

This commit is contained in:
Ryszard Rozak 2023-05-30 14:59:00 +02:00 committed by GitHub
parent e9135598b3
commit ba82d43ca1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 236 additions and 5 deletions

View File

@ -1817,6 +1817,7 @@ public:
// ACCESSORS for specific types
// Alas these can't be virtual or they break when passed a nullptr
inline bool isClassHandleValue() const;
inline bool isZero() const;
inline bool isOne() const;
inline bool isNeqZero() const;

View File

@ -41,6 +41,10 @@ bool AstNode::isString() const VL_MT_STABLE {
}
bool AstNode::isSigned() const VL_MT_STABLE { return dtypep() && dtypep()->isSigned(); }
bool AstNode::isClassHandleValue() const {
return (VN_IS(this, Const) && VN_AS(this, Const)->num().isNull())
|| VN_IS(dtypep(), ClassRefDType);
}
bool AstNode::isZero() const {
return (VN_IS(this, Const) && VN_AS(this, Const)->num().isEqZero());
}

View File

@ -122,6 +122,31 @@ private:
if (nodep->sizeMattersLhs()) ensureCast(nodep->lhsp());
if (nodep->sizeMattersRhs()) ensureCast(nodep->rhsp());
}
void visit(AstNodeCond* nodep) override {
// All class types are castable to each other. If they are of different types,
// a compilation error will be thrown, so an explicit cast is required. Types were
// already checked by V3Width and dtypep of a condition operator is a type of their
// common base class, so both classes can be safetly casted.
const AstClassRefDType* const thenClassDtypep
= VN_CAST(nodep->thenp()->dtypep(), ClassRefDType);
const AstClassRefDType* const elseClassDtypep
= VN_CAST(nodep->elsep()->dtypep(), ClassRefDType);
const bool castRequired = thenClassDtypep && elseClassDtypep
&& (thenClassDtypep->classp() != elseClassDtypep->classp());
if (castRequired) {
const AstClass* const commonBaseClassp
= VN_AS(nodep->dtypep(), ClassRefDType)->classp();
if (thenClassDtypep->classp() != commonBaseClassp) {
AstNodeExpr* thenp = nodep->thenp()->unlinkFrBack();
nodep->thenp(new AstCCast{thenp->fileline(), thenp, nodep});
}
if (elseClassDtypep->classp() != commonBaseClassp) {
AstNodeExpr* elsep = nodep->elsep()->unlinkFrBack();
nodep->elsep(new AstCCast{elsep->fileline(), elsep, nodep});
}
}
visit(static_cast<AstNodeTriop*>(nodep));
}
void visit(AstNodeTriop* nodep) override {
iterateChildren(nodep);
nodep->user1(nodep->lhsp()->user1() | nodep->rhsp()->user1() | nodep->thsp()->user1());

View File

@ -3405,9 +3405,9 @@ private:
TREEOPS("AstCond {$lhsp.isNeqZero}", "replaceWIteratedRhs(nodep)");
TREEOP ("AstCond{$condp.castNot, $thenp, $elsep}", "AstCond{$condp->castNot()->lhsp(), $elsep, $thenp}");
TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp.isAllOnes, $elsep}", "AstLogOr {$condp, $elsep}"); // a?1:b == a||b
TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp, $elsep.isZero}", "AstLogAnd{$condp, $thenp}"); // a?b:0 == a&&b
TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp, $elsep.isZero, !$elsep.isClassHandleValue}", "AstLogAnd{$condp, $thenp}"); // a?b:0 == a&&b
TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp, $elsep.isAllOnes}", "AstLogOr {AstNot{$condp}, $thenp}"); // a?b:1 == ~a||b
TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp.isZero, $elsep}", "AstLogAnd{AstNot{$condp}, $elsep}"); // a?0:b == ~a&&b
TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp.isZero, !$thenp.isClassHandleValue, $elsep}", "AstLogAnd{AstNot{$condp}, $elsep}"); // a?0:b == ~a&&b
TREEOP ("AstNodeCond{!$condp.width1, operandBoolShift(nodep->condp())}", "replaceBoolShift(nodep->condp())");
// Prefer constants on left, since that often needs a shift, it lets
// constant red remove the shift

View File

@ -1044,7 +1044,9 @@ public:
}
void visit(AstCCast* nodep) override {
// Extending a value of the same word width is just a NOP.
if (nodep->size() <= VL_IDATASIZE) {
if (const AstClassRefDType* const classDtypep = VN_CAST(nodep->dtypep(), ClassRefDType)) {
puts("(" + classDtypep->cType("", false, false) + ")(");
} else if (nodep->size() <= VL_IDATASIZE) {
puts("(IData)(");
} else {
puts("(QData)(");

View File

@ -503,9 +503,26 @@ private:
// the size of this subexpression only.
// Second call (final()) m_vup->width() is probably the expression size, so
// the expression includes the size of the output too.
if (nodep->thenp()->dtypep()->skipRefp() == nodep->elsep()->dtypep()->skipRefp()) {
const AstNodeDType* const thenDTypep = nodep->thenp()->dtypep();
const AstNodeDType* const elseDTypep = nodep->elsep()->dtypep();
if (thenDTypep->skipRefp() == elseDTypep->skipRefp()) {
// TODO might need a broader equation, use the Castable function?
nodep->dtypeFrom(nodep->thenp()->dtypep());
nodep->dtypeFrom(thenDTypep);
} else if (nodep->thenp()->isClassHandleValue()
|| nodep->elsep()->isClassHandleValue()) {
AstNodeDType* commonClassTypep = nullptr;
if (nodep->thenp()->isClassHandleValue() && nodep->elsep()->isClassHandleValue()) {
// Get the most-deriving class type that both arguments can be casted to.
commonClassTypep = getCommonClassTypep(nodep->thenp(), nodep->elsep());
}
if (commonClassTypep) {
nodep->dtypep(commonClassTypep);
} else {
nodep->v3error("Incompatible types of operands of condition operator: "
<< thenDTypep->prettyTypeName() << " and "
<< elseDTypep->prettyTypeName());
nodep->dtypeFrom(thenDTypep);
}
} else if (nodep->thenp()->isDouble() || nodep->elsep()->isDouble()) {
nodep->dtypeSetDouble();
} else if (nodep->thenp()->isString() || nodep->elsep()->isString()) {
@ -7309,6 +7326,30 @@ private:
if (nodep->subDTypep()) return hasOpenArrayIterateDType(nodep->subDTypep()->skipRefp());
return false;
}
AstNodeDType* getCommonClassTypep(AstNode* nodep1, AstNode* nodep2) {
// Return the class type that both nodep1 and nodep2 are castable to.
// If both are null, return the type of null constant.
// If one is a class and one is null, return AstClassRefDType that points to that class.
// If no common class type exists, return nullptr.
// First handle cases with null values and when one class is a super class of the other.
if (VN_IS(nodep1, Const)) std::swap(nodep1, nodep2);
const Castable castable = computeCastable(nodep1->dtypep(), nodep2->dtypep(), nodep2);
if (castable == SAMEISH || castable == COMPATIBLE) {
return nodep1->dtypep()->cloneTree(false);
} else if (castable == DYNAMIC_CLASS) {
return nodep2->dtypep()->cloneTree(false);
}
AstClassRefDType* classDtypep1 = VN_CAST(nodep1->dtypep(), ClassRefDType);
while (classDtypep1) {
const Castable castable = computeCastable(classDtypep1, nodep2->dtypep(), nodep2);
if (castable == COMPATIBLE) return classDtypep1->cloneTree(false);
AstClassExtends* const extendsp = classDtypep1->classp()->extendsp();
classDtypep1 = extendsp ? VN_AS(extendsp->dtypep(), ClassRefDType) : nullptr;
}
return nullptr;
}
//----------------------------------------------------------------------
// METHODS - casting

View File

@ -0,0 +1,21 @@
#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2022 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
scenarios(simulator => 1);
compile(
);
execute(
check_finished => 1,
);
ok(1);
1;

View File

@ -0,0 +1,70 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed into the Public Domain, for any use,
// without warranty, 2023 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
class Cls;
int f;
function new(int x);
f = x;
endfunction
endclass
class ExtendCls extends Cls;
function new(int x);
super.new(x);
endfunction
endclass
class AnotherExtendCls extends Cls;
function new(int x);
super.new(x);
endfunction
endclass
class ExtendExtendCls extends ExtendCls;
function new(int x);
super.new(x);
endfunction
endclass
module t (/*AUTOARG*/);
initial begin
Cls cls1 = null, cls2 = null;
ExtendCls ext_cls = null;
AnotherExtendCls an_ext_cls = null;
ExtendExtendCls ext_ext_cls = null;
cls1 = (cls1 == null) ? cls2 : cls1;
if (cls1 != null) $stop;
cls1 = new(1);
cls1 = (cls1 == null) ? cls2 : cls1;
if (cls1.f != 1) $stop;
cls1 = (cls1 != null) ? cls2 : cls1;
if (cls1 != null) $stop;
cls1 = new(1);
cls2 = new(2);
cls1 = (cls1 != null) ? cls2 : cls1;
if (cls1.f != 2) $stop;
cls1 = null;
cls1 = (ext_cls != null) ? ext_cls : cls2;
if (cls1.f != 2) $stop;
ext_cls = new(3);
cls1 = (ext_cls != null) ? ext_cls : cls2;
if (cls1.f != 3) $stop;
ext_ext_cls = new(4);
an_ext_cls = new(5);
cls1 = (ext_ext_cls.f != 4) ? ext_ext_cls : an_ext_cls;
if (cls1.f != 5) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,21 @@
%Error: t/t_class_assign_cond_bad.v:22:25: Incompatible types of operands of condition operator: CLASSREFDTYPE 'Cls1' and CLASSREFDTYPE 'Cls2'
: ... In instance t
22 | c1 = (c1 != null) ? c1 : c2;
| ^
%Error: t/t_class_assign_cond_bad.v:23:10: Assign RHS expects a CLASSREFDTYPE 'Cls1', got CLASSREFDTYPE 'Cls2'
: ... In instance t
23 | c1 = (c1 != null) ? c2 : c2;
| ^
%Error: t/t_class_assign_cond_bad.v:24:25: Incompatible types of operands of condition operator: BASICDTYPE 'logic' and CLASSREFDTYPE 'Cls2'
: ... In instance t
24 | c2 = (c1 == null) ? 1'b1 : c2;
| ^
%Error: t/t_class_assign_cond_bad.v:24:10: Assign RHS expects a CLASSREFDTYPE 'Cls2', got BASICDTYPE 'logic'
: ... In instance t
24 | c2 = (c1 == null) ? 1'b1 : c2;
| ^
%Error: t/t_class_assign_cond_bad.v:25:29: Incompatible types of operands of condition operator: CLASSREFDTYPE 'ExtCls1' and CLASSREFDTYPE 'Cls1'
: ... In instance t
25 | ext_c1 = (c1 == null) ? ext_c1 : c1;
| ^
%Error: Exiting due to

View File

@ -0,0 +1,19 @@
#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2022 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
scenarios(simulator => 1);
compile(
fails => 1,
expect_filename => $Self->{golden_filename},
);
ok(1);
1;

View File

@ -0,0 +1,27 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2023 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
class Cls1;
endclass
class Cls2;
endclass
class ExtCls1;
endclass
module t (/*AUTOARG*/);
Cls1 c1;
Cls2 c2;
ExtCls1 ext_c1;
initial begin
c1 = (c1 != null) ? c1 : c2;
c1 = (c1 != null) ? c2 : c2;
c2 = (c1 == null) ? 1'b1 : c2;
ext_c1 = (c1 == null) ? ext_c1 : c1;
end
endmodule