diff --git a/src/V3Ast.h b/src/V3Ast.h index 8dc154695..960a3c69a 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -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; diff --git a/src/V3AstInlines.h b/src/V3AstInlines.h index 9ca54f137..d2325c14b 100644 --- a/src/V3AstInlines.h +++ b/src/V3AstInlines.h @@ -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()); } diff --git a/src/V3Cast.cpp b/src/V3Cast.cpp index 7c3d47c53..b976b5324 100644 --- a/src/V3Cast.cpp +++ b/src/V3Cast.cpp @@ -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(nodep)); + } void visit(AstNodeTriop* nodep) override { iterateChildren(nodep); nodep->user1(nodep->lhsp()->user1() | nodep->rhsp()->user1() | nodep->thsp()->user1()); diff --git a/src/V3Const.cpp b/src/V3Const.cpp index a5413c1ec..f0656eaa5 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -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 diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 657f1d955..165413aab 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -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)("); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 241e886da..95f3c3915 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -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 diff --git a/test_regress/t/t_class_assign_cond.pl b/test_regress/t/t_class_assign_cond.pl new file mode 100755 index 000000000..1aa73f80a --- /dev/null +++ b/test_regress/t/t_class_assign_cond.pl @@ -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; diff --git a/test_regress/t/t_class_assign_cond.v b/test_regress/t/t_class_assign_cond.v new file mode 100644 index 000000000..4d78e3a68 --- /dev/null +++ b/test_regress/t/t_class_assign_cond.v @@ -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 diff --git a/test_regress/t/t_class_assign_cond_bad.out b/test_regress/t/t_class_assign_cond_bad.out new file mode 100644 index 000000000..d0d347f17 --- /dev/null +++ b/test_regress/t/t_class_assign_cond_bad.out @@ -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 diff --git a/test_regress/t/t_class_assign_cond_bad.pl b/test_regress/t/t_class_assign_cond_bad.pl new file mode 100755 index 000000000..430290c0a --- /dev/null +++ b/test_regress/t/t_class_assign_cond_bad.pl @@ -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; diff --git a/test_regress/t/t_class_assign_cond_bad.v b/test_regress/t/t_class_assign_cond_bad.v new file mode 100644 index 000000000..a016c832d --- /dev/null +++ b/test_regress/t/t_class_assign_cond_bad.v @@ -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