diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index 564f2995f..2ce8f2ffb 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -304,7 +304,7 @@ class AstClass : public AstNodeModule { // MEMBERS MemberNameMap m_members; // Members or method children AstClassPackage* m_packagep = nullptr; // Class package this is under - bool m_virtual; // Virtual class + bool m_virtual = false; // Virtual class void insertCache(AstNode* nodep); public: @@ -329,7 +329,7 @@ public: addStmtp(nodep); } AstClassExtends* extendsp() const { return VN_CAST(op4p(), ClassExtends); } - void extendsp(AstNode* nodep) { addNOp2p(nodep); } + void extendsp(AstNode* nodep) { addNOp4p(nodep); } void clearCache() { m_members.clear(); } void repairCache(); AstNode* findMember(const string& name) const { @@ -346,13 +346,15 @@ class AstClassExtends : public AstNode { public: AstClassExtends(FileLine* fl, AstNode* classOrPkgsp) : ASTGEN_SUPER(fl) { - setNOp1p(classOrPkgsp); // Only for parser + setNOp2p(classOrPkgsp); // Only for parser } ASTNODE_NODE_FUNCS(ClassExtends) - virtual string verilogKwd() const override { return "extends"; } virtual bool hasDType() const override { return true; } - AstNodeDType* classOrPkgsp() const { return VN_CAST(op1p(), NodeDType); } - void classOrPkgsp(AstNodeDType* nodep) { setOp1p(nodep); } + virtual string verilogKwd() const override { return "extends"; } + AstNodeDType* childDTypep() const { return VN_CAST(op1p(), NodeDType); } + void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } + AstNode* classOrPkgsp() const { return op2p(); } + void classOrPkgsp(AstNode* nodep) { setOp2p(nodep); } AstClass* classp() const; // Class being extended (after link) }; @@ -8512,7 +8514,8 @@ private: string m_name; string m_cname; // C name, for dpiExports string m_rtnType; // void, bool, or other return type - string m_argTypes; + string m_argTypes; // Argument types + string m_ctorInits; // Constructor sub-class inits string m_ifdef; // #ifdef symbol around this function VBoolOrUnknown m_isConst; // Function is declared const (*this not changed) VBoolOrUnknown m_isStatic; // Function is declared static (no this) @@ -8574,7 +8577,7 @@ public: virtual bool same(const AstNode* samep) const override { const AstCFunc* asamep = static_cast(samep); return ((funcType() == asamep->funcType()) && (rtnTypeVoid() == asamep->rtnTypeVoid()) - && (argTypes() == asamep->argTypes()) + && (argTypes() == asamep->argTypes()) && (ctorInits() == asamep->ctorInits()) && (!(dpiImport() || dpiExport()) || name() == asamep->name())); } // @@ -8606,6 +8609,8 @@ public: void funcPublic(bool flag) { m_funcPublic = flag; } void argTypes(const string& str) { m_argTypes = str; } string argTypes() const { return m_argTypes; } + void ctorInits(const string& str) { m_ctorInits = str; } + string ctorInits() const { return m_ctorInits; } void ifdef(const string& str) { m_ifdef = str; } string ifdef() const { return m_ifdef; } void funcType(AstCFuncType flag) { m_funcType = flag; } diff --git a/src/V3EmitC.cpp b/src/V3EmitC.cpp index 54b91ba43..e3d03d24b 100644 --- a/src/V3EmitC.cpp +++ b/src/V3EmitC.cpp @@ -264,6 +264,15 @@ public: puts("static void __Vmtask__final(bool even_cycle, void* symtab);\n"); } } + void ccallIterateArgs(AstNodeCCall* nodep) { + puts(nodep->argTypes()); + bool comma = (nodep->argTypes() != ""); + for (AstNode* subnodep = nodep->argsp(); subnodep; subnodep = subnodep->nextp()) { + if (comma) puts(", "); + iterate(subnodep); + comma = true; + } + } // VISITORS virtual void visit(AstNodeAssign* nodep) override { @@ -373,13 +382,7 @@ public: } puts(nodep->funcp()->nameProtect()); puts("("); - puts(nodep->argTypes()); - bool comma = (nodep->argTypes() != ""); - for (AstNode* subnodep = nodep->argsp(); subnodep; subnodep = subnodep->nextp()) { - if (comma) puts(", "); - iterate(subnodep); - comma = true; - } + ccallIterateArgs(nodep); if (VN_IS(nodep->backp(), NodeMath) || VN_IS(nodep->backp(), CReturn)) { // We should have a separate CCall for math and statement usage, but... puts(")"); @@ -1485,6 +1488,13 @@ class EmitCImp : EmitCStmts { puts(funcNameProtect(nodep, m_modp)); puts("(" + cFuncArgs(nodep) + ")"); if (nodep->isConst().trueKnown()) puts(" const"); + + // TODO perhaps better to have a new AstCCtorInit so we can pass arguments + // rather than requiring a string here + if (!nodep->ctorInits().empty()) { + puts(": "); + puts(nodep->ctorInits()); + } puts(" {\n"); // "+" in the debug indicates a print from the model @@ -2982,7 +2992,8 @@ void EmitCImp::emitInt(AstNodeModule* modp) { if (AstClass* classp = VN_CAST(modp, Class)) { puts("class " + prefixNameProtect(modp)); - if (classp->extendsp()) puts(" : public " + classp->extendsp()->classp()->nameProtect()); + if (classp->extendsp()) + puts(" : public " + prefixNameProtect(classp->extendsp()->classp())); puts(" {\n"); } else if (optSystemC() && modp->isTop()) { puts("SC_MODULE(" + prefixNameProtect(modp) + ") {\n"); diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 00096213a..985eb8e26 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -76,6 +76,10 @@ //###################################################################### // Matcher classes (for suggestion matching) +class LinkNodeMatcherClass : public VNodeMatcher { +public: + virtual bool nodeMatch(const AstNode* nodep) const override { return VN_IS(nodep, Class); } +}; class LinkNodeMatcherFTask : public VNodeMatcher { public: virtual bool nodeMatch(const AstNode* nodep) const override { return VN_IS(nodep, NodeFTask); } @@ -2679,13 +2683,6 @@ private: virtual void visit(AstClass* nodep) override { UINFO(5, " " << nodep << endl); checkNoDot(nodep); - for (AstNode* itemp = nodep->membersp(); itemp; itemp = itemp->nextp()) { - if (AstClassExtends* eitemp = VN_CAST(itemp, ClassExtends)) { - // Replace abstract reference with hard pointer - // Will need later resolution when deal with parameters - eitemp->v3warn(E_UNSUPPORTED, "Unsupported: class extends"); - } - } VSymEnt* oldCurSymp = m_curSymp; VSymEnt* oldModSymp = m_modSymp; { @@ -2695,6 +2692,39 @@ private: m_modp = nodep; iterateChildren(nodep); } + for (AstNode* itemp = nodep->extendsp(); itemp; itemp = itemp->nextp()) { + if (AstClassExtends* cextp = VN_CAST(itemp, ClassExtends)) { + // Replace abstract reference with hard pointer + // Will need later resolution when deal with parameters + if (cextp->childDTypep() || cextp->dtypep()) continue; // Already converted + AstClassOrPackageRef* cpackagerefp + = VN_CAST(cextp->classOrPkgsp(), ClassOrPackageRef); + if (!cpackagerefp) { + cextp->v3error("Attempting to extend using a non-class "); + } else { + VSymEnt* foundp = m_curSymp->findIdFallback(cpackagerefp->name()); + bool ok = false; + if (foundp) { + if (AstClass* classp = VN_CAST(foundp->nodep(), Class)) { + AstClassRefDType* newp + = new AstClassRefDType{nodep->fileline(), classp}; + cextp->childDTypep(newp); + VL_DO_DANGLING(cpackagerefp->unlinkFrBack()->deleteTree(), + cpackagerefp); + ok = true; + } + } + if (!ok) { + string suggest = m_statep->suggestSymFallback( + m_curSymp, cpackagerefp->name(), LinkNodeMatcherClass{}); + cpackagerefp->v3error( + "Class to extend not found: " + << cpackagerefp->prettyNameQ() << endl + << (suggest.empty() ? "" : cpackagerefp->warnMore() + suggest)); + } + } + } + } // V3Width when determines types needs to find enum values and such // so add members pointing to appropriate enum values { diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 0e0b9d393..550ddfbeb 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -104,11 +104,14 @@ private: // TYPES typedef std::map, AstVarScope*> VarToScopeMap; + typedef std::map FuncToClassMap; typedef std::vector Initials; // MEMBERS VarToScopeMap m_varToScopeMap; // Map for Var -> VarScope mappings + FuncToClassMap m_funcToClassMap; // Map for ctor func -> class AstAssignW* m_assignwp = nullptr; // Current assignment AstNodeFTask* m_ctorp = nullptr; // Class constructor + AstClass* m_classp = nullptr; // Current class V3Graph m_callGraph; // Task call graph TaskBaseVertex* m_curVxp; // Current vertex we're adding to Initials m_initialps; // Initial blocks to move @@ -125,6 +128,14 @@ public: UASSERT_OBJ(iter != m_varToScopeMap.end(), nodep, "No scope for var"); return iter->second; } + AstClass* getClassp(AstNodeFTask* nodep) { + AstClass* classp = m_funcToClassMap[nodep]; + UASSERT_OBJ(classp, nodep, "No class for ctor func"); + return classp; + } + void remapFuncClassp(AstNodeFTask* nodep, AstNodeFTask* newp) { + m_funcToClassMap[newp] = getClassp(nodep); + } bool ftaskNoInline(AstNodeFTask* nodep) { return getFTaskVertex(nodep)->noInline(); } AstCFunc* ftaskCFuncp(AstNodeFTask* nodep) { return getFTaskVertex(nodep)->cFuncp(); } void ftaskCFuncp(AstNodeFTask* nodep, AstCFunc* cfuncp) { @@ -202,6 +213,8 @@ private: if (nodep->isConstructor()) { m_curVxp->noInline(true); m_ctorp = nodep; + UASSERT_OBJ(m_classp, nodep, "Ctor not under class"); + m_funcToClassMap[nodep] = m_classp; } iterateChildren(nodep); m_curVxp = lastVxp; @@ -229,6 +242,7 @@ private: // Move initial statements into the constructor m_initialps.clear(); m_ctorp = nullptr; + m_classp = nodep; { // Find m_initialps, m_ctor iterateChildren(nodep); } @@ -246,6 +260,7 @@ private: } m_initialps.clear(); m_ctorp = nullptr; + m_classp = nullptr; } virtual void visit(AstInitial* nodep) override { m_initialps.push_back(nodep); @@ -312,7 +327,7 @@ class TaskVisitor : public AstNVisitor { private: // NODE STATE // Each module: - // AstNodeFTask::user // True if its been expanded + // AstNodeFTask::user1 // True if its been expanded // Each funccall // to TaskRelinkVisitor: // AstVar::user2p // AstVarScope* to replace varref with @@ -1035,7 +1050,14 @@ private: cfuncp->dpiImportWrapper(nodep->dpiImport()); cfuncp->isStatic(!(nodep->dpiImport() || nodep->taskPublic() || nodep->classMethod())); cfuncp->pure(nodep->pure()); - cfuncp->isConstructor(nodep->name() == "new"); + if (nodep->name() == "new") { + cfuncp->isConstructor(true); + AstClass* classp = m_statep->getClassp(nodep); + if (classp->extendsp()) { + cfuncp->ctorInits(EmitCBaseVisitor::prefixNameProtect(classp->extendsp()->classp()) + + "(vlSymsp)"); + } + } // cfuncp->dpiImport // Not set in the wrapper - the called function has it set if (cfuncp->dpiExport()) cfuncp->cname(nodep->cname()); @@ -1278,6 +1300,8 @@ private: m_statep->checkPurity(nodep); } AstNodeFTask* clonedFuncp = nodep->cloneTree(false); + if (nodep->isConstructor()) m_statep->remapFuncClassp(nodep, clonedFuncp); + AstCFunc* cfuncp = makeUserFunc(clonedFuncp, m_statep->ftaskNoInline(nodep)); if (cfuncp) { nodep->addNextHere(cfuncp); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 8d8c0b6fa..0e61e810c 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2039,10 +2039,9 @@ private: } virtual void visit(AstClassExtends* nodep) override { if (nodep->didWidthAndSet()) return; - nodep->v3warn(E_UNSUPPORTED, "Unsupported: class extends"); // Member/meth access breaks - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - // nodep->dtypep(iterateEditMoveDTypep(nodep)); // data_type '{ pattern } - // userIterateChildren(nodep, nullptr); + if (VN_IS(nodep->childDTypep(), ClassRefDType)) { + nodep->dtypep(iterateEditMoveDTypep(nodep, nodep->childDTypep())); + } } virtual void visit(AstMemberDType* nodep) override { if (nodep->didWidthAndSet()) return; // This node is a dtype & not both PRELIMed+FINALed diff --git a/test_regress/t/t_class_extends.out b/test_regress/t/t_class_extends.out index 8cd0d95fd..fc51e85d6 100644 --- a/test_regress/t/t_class_extends.out +++ b/test_regress/t/t_class_extends.out @@ -1,16 +1,4 @@ -%Error-UNSUPPORTED: t/t_class_extends.v:13:21: Unsupported: class extends - 13 | class Base1 extends Base0; - | ^~~~~ -%Error-UNSUPPORTED: t/t_class_extends.v:18:21: Unsupported: class extends - 18 | class Base2 extends Base1; - | ^~~~~ -%Error-UNSUPPORTED: t/t_class_extends.v:22:19: Unsupported: class extends - 22 | class Cls extends Base2; - | ^~~~~ %Error: t/t_class_extends.v:25:4: Can't find typedef: 'T' 25 | T imemberc; | ^ -%Error-UNSUPPORTED: t/t_class_extends.v:33:43: Unsupported: class extends - 33 | class uvm__registry #(type T=int) extends uvm_object_wrapper; - | ^~~~~~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_class_extends1.pl b/test_regress/t/t_class_extends1.pl new file mode 100755 index 000000000..aabcde63e --- /dev/null +++ b/test_regress/t/t_class_extends1.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 2020 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_extends1.v b/test_regress/t/t_class_extends1.v new file mode 100644 index 000000000..c0f8ceeec --- /dev/null +++ b/test_regress/t/t_class_extends1.v @@ -0,0 +1,45 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2020 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +class Base0; + int baseonly; + int baseover; + + function void b_set_bo(int v); baseover = v; endfunction + function int b_get_bo(); return baseover; endfunction + function int get_bo(); return baseover; endfunction +endclass + +class Ext extends Base0; + int baseover; + int extonly; + + function void e_set_bo(int v); baseover = v; endfunction + function int e_get_bo(); return baseover; endfunction + function int get_bo(); return baseover; endfunction +endclass + +module t (/*AUTOARG*/); + initial begin + Ext c; + c = new; + c.baseonly = 10; + c.baseover = 20; + c.extonly = 30; + if (c.baseonly != 10) $stop; + if (c.baseover != 20) $stop; + if (c.extonly != 30) $stop; + + c.b_set_bo(100); + c.e_set_bo(200); + if (c.b_get_bo() != 100) $stop; + if (c.e_get_bo() != 200) $stop; + if (c.get_bo() != 200) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_class_extends_nf_bad.out b/test_regress/t/t_class_extends_nf_bad.out new file mode 100644 index 000000000..62fc4e306 --- /dev/null +++ b/test_regress/t/t_class_extends_nf_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_class_extends_nf_bad.v:10:19: Class to extend not found: 'IsNotFound' + : ... Suggested alternative: 'IsFound' + 10 | class Cls extends IsNotFound; + | ^~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_class_extends_nf_bad.pl b/test_regress/t/t_class_extends_nf_bad.pl new file mode 100755 index 000000000..7be596e0f --- /dev/null +++ b/test_regress/t/t_class_extends_nf_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 2020 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(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_class_extends_nf_bad.v b/test_regress/t/t_class_extends_nf_bad.v new file mode 100644 index 000000000..2285ba8e6 --- /dev/null +++ b/test_regress/t/t_class_extends_nf_bad.v @@ -0,0 +1,14 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2020 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +class IsFound; +endclass + +class Cls extends IsNotFound; +endclass + +module t (/*AUTOARG*/); +endmodule diff --git a/test_regress/t/t_class_extends_this.out b/test_regress/t/t_class_extends_this.out index 6f0456393..364b929f1 100644 --- a/test_regress/t/t_class_extends_this.out +++ b/test_regress/t/t_class_extends_this.out @@ -4,9 +4,6 @@ %Error: t/t_class_extends_this.v:13:11: Can't find definition of scope/variable: 'this' 13 | if (this.value != 1) $stop; | ^~~~ -%Error-UNSUPPORTED: t/t_class_extends_this.v:17:19: Unsupported: class extends - 17 | class Cls extends Base; - | ^~~~ %Error-UNSUPPORTED: t/t_class_extends_this.v:21:11: Unsupported: this 21 | if (this.value != 2) $stop; | ^~~~ diff --git a/test_regress/t/t_class_member_bad.out b/test_regress/t/t_class_member_bad.out index 1251ff9af..ef2eaedc8 100644 --- a/test_regress/t/t_class_member_bad.out +++ b/test_regress/t/t_class_member_bad.out @@ -1,4 +1,11 @@ -%Error-UNSUPPORTED: t/t_class_member_bad.v:11:20: Unsupported: class extends - 11 | class Cls2 extends Base1; - | ^~~~~ +%Error: t/t_class_member_bad.v:18:9: Member 'memb3' not found in class 'Cls2' + : ... In instance t + : ... Suggested alternative: 'memb2' + 18 | c.memb3 = 3; + | ^~~~~ +%Warning-WIDTH: t/t_class_member_bad.v:18:15: Operator ASSIGN expects 1 bits on the Assign RHS, but Assign RHS's CONST '?32?sh3' generates 32 or 2 bits. + : ... In instance t + 18 | c.memb3 = 3; + | ^ + ... Use "/* verilator lint_off WIDTH */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_class_method_bad.out b/test_regress/t/t_class_method_bad.out index bb2dbc0f6..5a2cbc931 100644 --- a/test_regress/t/t_class_method_bad.out +++ b/test_regress/t/t_class_method_bad.out @@ -1,4 +1,6 @@ -%Error-UNSUPPORTED: t/t_class_method_bad.v:11:20: Unsupported: class extends - 11 | class Cls2 extends Base1; - | ^~~~~ +%Error: t/t_class_method_bad.v:18:9: Class method 'meth3' not found in class 'Cls2' + : ... In instance t + : ... Suggested alternative: 'meth2' + 18 | c.meth3(); + | ^~~~~ %Error: Exiting due to