// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: File stream wrapper that understands indentation // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2019 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. // // Verilator is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // //************************************************************************* #include "config_build.h" #include "verilatedos.h" #include "V3Global.h" #include "V3File.h" #include "V3Os.h" #include "V3PreShell.h" #include "V3Ast.h" #include #include #include #include #include #include #include #include #if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) # define INFILTER_PIPE // Allow pipe filtering. Needs fork() #endif #ifdef HAVE_STAT_NSEC // i.e. Linux 2.6, from configure # define VL_STAT_CTIME_NSEC(stat) ((stat).st_ctim.tv_nsec) // Nanoseconds # define VL_STAT_MTIME_NSEC(stat) ((stat).st_mtim.tv_nsec) // Nanoseconds #else # define VL_STAT_CTIME_NSEC(stat) (0) # define VL_STAT_MTIME_NSEC(stat) (0) #endif #ifdef INFILTER_PIPE # include #endif // If change this code, run a test with the below size set very small //#define INFILTER_IPC_BUFSIZ 16 #define INFILTER_IPC_BUFSIZ (64*1024) // For debug, try this as a small number #define INFILTER_CACHE_MAX (64*1024) // Maximum bytes to cache if same file read twice //###################################################################### // V3File Internal state class V3FileDependImp { // TYPES class DependFile { // A single file bool m_target; // True if write, else read bool m_exists; string m_filename; // Filename struct stat m_stat; // Stat information public: DependFile(const string& filename, bool target) : m_target(target), m_exists(true), m_filename(filename) { m_stat.st_ctime = 0; m_stat.st_mtime = 0; } ~DependFile() {} const string& filename() const { return m_filename; } bool target() const { return m_target; } bool exists() const { return m_exists; } off_t size() const { return m_stat.st_size; } ino_t ino() const { return m_stat.st_ino; } time_t cstime() const { return m_stat.st_ctime; } // Seconds time_t cnstime() const { return VL_STAT_CTIME_NSEC(m_stat); } // Nanoseconds time_t mstime() const { return m_stat.st_mtime; } // Seconds time_t mnstime() const { return VL_STAT_MTIME_NSEC(m_stat); } // Nanoseconds void loadStats() { if (!m_stat.st_mtime) { string fn = filename(); int err = stat(fn.c_str(), &m_stat); if (err!=0) { memset(&m_stat, 0, sizeof(m_stat)); m_stat.st_mtime = 1; m_exists = false; // Not an error... This can occur due to `line directives in the .vpp files UINFO(1,"-Info: File not statable: "< m_filenameSet; // Files generated (elim duplicates) std::set m_filenameList; // Files sourced/generated static string stripQuotes(const string& in) { string pretty = in; string::size_type pos; while ((pos = pretty.find('\"')) != string::npos) pretty.replace(pos, 1, "_"); while ((pos = pretty.find('\n')) != string::npos) pretty.replace(pos, 1, "_"); return pretty; } public: // ACCESSOR METHODS void addSrcDepend(const string& filename) { if (m_filenameSet.find(filename) == m_filenameSet.end()) { m_filenameSet.insert(filename); DependFile df (filename, false); df.loadStats(); // Get size now, in case changes during the run m_filenameList.insert(df); } } void addTgtDepend(const string& filename) { if (m_filenameSet.find(filename) == m_filenameSet.end()) { m_filenameSet.insert(filename); m_filenameList.insert(DependFile(filename, true)); } } void writeDepend(const string& filename); std::vector getAllDeps() const; void writeTimes(const string& filename, const string& cmdlineIn); bool checkTimes(const string& filename, const string& cmdlineIn); }; V3FileDependImp dependImp; // Depend implementation class //###################################################################### // V3FileDependImp inline void V3FileDependImp::writeDepend(const string& filename) { const vl_unique_ptr ofp (V3File::new_ofstream(filename)); if (ofp->fail()) v3fatal("Can't write "<::iterator iter=m_filenameList.begin(); iter!=m_filenameList.end(); ++iter) { if (iter->target()) { *ofp<filename()<<" "; } } *ofp<<" : "; *ofp<::iterator iter=m_filenameList.begin(); iter!=m_filenameList.end(); ++iter) { if (!iter->target()) { *ofp<filename()<<" "; } } *ofp<::iterator iter=m_filenameList.begin(); iter!=m_filenameList.end(); ++iter) { if (!iter->target()) { *ofp<filename()<<":"< V3FileDependImp::getAllDeps() const { std::vector r; for (std::set::const_iterator iter=m_filenameList.begin(); iter!=m_filenameList.end(); ++iter) { if (!iter->target() && iter->exists()) { r.push_back(iter->filename()); } } return r; } inline void V3FileDependImp::writeTimes(const string& filename, const string& cmdlineIn) { const vl_unique_ptr ofp (V3File::new_ofstream(filename)); if (ofp->fail()) v3fatal("Can't write "<::iterator iter=m_filenameList.begin(); iter!=m_filenameList.end(); ++iter) { // Read stats of files we create after we're done making them // (except for this file, of course) DependFile* dfp = const_cast(&(*iter)); V3Options::fileNfsFlush(dfp->filename()); dfp->loadStats(); off_t showSize = iter->size(); ino_t showIno = iter->ino(); if (dfp->filename() == filename) { showSize = 0; showIno = 0; // We're writing it, so need to ignore it } *ofp<<(iter->target()?"T":"S")<<" "; *ofp<<" "<cstime(); *ofp<<" "<cnstime(); *ofp<<" "<mstime(); *ofp<<" "<mnstime(); *ofp<<" \""<filename()<<"\""; *ofp< ifp (V3File::new_ifstream_nodepend(filename)); if (ifp->fail()) { UINFO(2," --check-times failed: no input "<>chkDir; char quote; *ifp>>quote; string chkCmdline = V3Os::getline(*ifp, '"'); string cmdline = stripQuotes(cmdlineIn); if (cmdline != chkCmdline) { UINFO(2," --check-times failed: different command line\n"); return false; } } while (!ifp->eof()) { char chkDir; *ifp>>chkDir; off_t chkSize; *ifp>>chkSize; ino_t chkIno; *ifp>>chkIno; if (ifp->eof()) break; // Needed to read final whitespace before found eof time_t chkCstime; *ifp>>chkCstime; time_t chkCnstime; *ifp>>chkCnstime; time_t chkMstime; *ifp>>chkMstime; time_t chkMnstime; *ifp>>chkMnstime; char quote; *ifp>>quote; string chkFilename = V3Os::getline(*ifp, '"'); V3Options::fileNfsFlush(chkFilename); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) struct stat chkStat; int err = stat(chkFilename.c_str(), &chkStat); if (err!=0) { UINFO(2," --check-times failed: missing "< V3File::getAllDeps() { return dependImp.getAllDeps(); } void V3File::writeTimes(const string& filename, const string& cmdlineIn) { dependImp.writeTimes(filename, cmdlineIn); } bool V3File::checkTimes(const string& filename, const string& cmdlineIn) { return dependImp.checkTimes(filename, cmdlineIn); } void V3File::createMakeDirFor(const string& filename) { if (filename != VL_DEV_NULL // If doesn't start with makeDir then some output file user requested && filename.substr(0, v3Global.opt.makeDir().length()+1) == v3Global.opt.makeDir()+"/") { createMakeDir(); } } void V3File::createMakeDir() { static bool created = false; if (!created) { created = true; V3Os::createDir(v3Global.opt.makeDir()); } } //###################################################################### // VInFilterImp class VInFilterImp { typedef std::map FileContentsMap; typedef VInFilter::StrList StrList; FileContentsMap m_contentsMap; // Cache of file contents bool m_readEof; // Received EOF on read #ifdef INFILTER_PIPE pid_t m_pid; // fork() process id #else int m_pid; // fork() process id - always zero as disabled #endif bool m_pidExited; int m_pidStatus; int m_writeFd; // File descriptor TO filter int m_readFd; // File descriptor FROM filter private: // METHODS VL_DEBUG_FUNC; // Declare debug() bool readContents(const string& filename, StrList& outl) { if (m_pid) return readContentsFilter(filename, outl); else return readContentsFile(filename, outl); } bool readContentsFile(const string& filename, StrList& outl) { int fd = open(filename.c_str(), O_RDONLY); if (fd<0) return false; m_readEof = false; readBlocks(fd, -1, outl); close(fd); return true; } bool readContentsFilter(const string& filename, StrList& outl) { if (filename!="" || outl.empty()) {} // Prevent unused #ifdef INFILTER_PIPE writeFilter("read \""+filename+"\"\n"); string line = readFilterLine(); if (line.find("Content-Length") != string::npos) { int len = 0; sscanf(line.c_str(), "Content-Length: %d\n", &len); readBlocks(m_readFd, len, outl); return true; } else { if (line!="") v3error("--pipe-filter protocol error, unexpected: "<sizegot)) { ssize_t todo = INFILTER_IPC_BUFSIZ; if (size>0 && size0) { outl.push_back(string(buf, got)); sizegot += got; } else if (errno == EINTR || errno == EAGAIN #ifdef EWOULDBLOCK || errno == EWOULDBLOCK #endif ) { // cppcheck-suppress obsoleteFunctionsusleep checkFilter(false); usleep(1000); continue; } else { m_readEof = true; break; } } return out; } // cppcheck-suppress unusedFunction unusedPrivateFunction string readFilterLine() { // Slow, but we don't need it much UINFO(9,"readFilterLine\n"); string line; while (!m_readEof) { StrList outl; readBlocks(m_readFd, 1, outl); string onechar = listString(outl); line += onechar; if (onechar == "\n") { if (line == "\n") { line = ""; continue; } else break; } } UINFO(6,"filter-line-in: "<=6) { UINFO(6,"filter-out: "<offset) { errno = 0; int got = write(m_writeFd, (out.c_str())+offset, out.length()-offset); //UINFO(9,"WR GOT g "<< got<<" e "<0) offset += got; else if (errno == EINTR || errno == EAGAIN #ifdef EWOULDBLOCK || errno == EWOULDBLOCK #endif ) { // cppcheck-suppress obsoleteFunctionsusleep checkFilter(false); usleep(1000); continue; } else break; } } // Start the filter void start(const string& command) { if (command=="") { m_pid = 0; // Disabled } else { startFilter(command); } } void startFilter(const string& command) { if (command=="") {} // Prevent Unused #ifdef INFILTER_PIPE int fd_stdin[2], fd_stdout[2]; static const int P_RD = 0; static const int P_WR = 1; if (pipe(fd_stdin) != 0 || pipe(fd_stdout) != 0) { v3fatal("--pipe-filter: Can't pipe: "<(NULL)); // Don't use v3fatal, we don't share the common structures any more fprintf(stderr, "--pipe-filter: exec failed: %s\n", strerror(errno)); _exit(1); } else { // Parent UINFO(6,"In parent, child pid "<"<"<second); return true; } if (!readContents(filename, outl)) return false; if (listSize(outl) < INFILTER_CACHE_MAX) { // Cache small files (only to save space) // It's quite common to `include "timescale" thousands of times // This isn't so important if it's just an open(), but filtering can be slow m_contentsMap.insert(make_pair(filename, listString(outl))); } return true; } size_t listSize(StrList& sl) { size_t out = 0; for (StrList::iterator it=sl.begin(); it!=sl.end(); ++it) { out += it->length(); } return out; } string listString(StrList& sl) { string out; for (StrList::iterator it=sl.begin(); it!=sl.end(); ++it) { out += *it; } return out; } // CONSTRUCTORS explicit VInFilterImp(const string& command) { m_readEof = false; m_pid = 0; m_pidExited = false; m_pidStatus = 0; m_writeFd = 0; m_readFd = 0; start(command); } ~VInFilterImp() { stop(); } }; //###################################################################### // VInFilter // Just dispatch to the implementation VInFilter::VInFilter(const string& command) { m_impp = new VInFilterImp(command); } VInFilter::~VInFilter() { if (m_impp) delete m_impp; m_impp = NULL; } bool VInFilter::readWholefile(const string& filename, VInFilter::StrList& outl) { if (!m_impp) v3fatalSrc("readWholefile on invalid filter"); return m_impp->readWholefile(filename, outl); } //###################################################################### // V3OutFormatter: A class for printing to a file, with automatic indentation of C++ code. V3OutFormatter::V3OutFormatter(const string& filename, V3OutFormatter::Language lang) : m_filename(filename), m_lang(lang) , m_lineno(1), m_column(0) , m_nobreak(false), m_prependIndent(true), m_indentLevel(0), m_bracketLevel(0) { m_blockIndent = v3Global.opt.decoration() ? 4 : 1; m_commaWidth = v3Global.opt.decoration() ? 50 : 150; } //---------------------------------------------------------------------- const string V3OutFormatter::indentSpaces(int num) { // Indent the specified number of spaces. Use spaces. static char str[MAXSPACE+20]; char* cp = str; if (num>MAXSPACE) num = MAXSPACE; while (num>0) { *cp++ = ' '; num --; } *cp++ = '\0'; string st (str); return st; } bool V3OutFormatter::tokenStart(const char* cp, const char* cmp) { while (*cmp == *cp) { cp++; cmp++; } if (*cmp) return false; if (*cp && !isspace(*cp)) return false; return true; } bool V3OutFormatter::tokenEnd(const char* cp) { return (tokenStart(cp, "end") || tokenStart(cp, "endcase") || tokenStart(cp, "endmodule")); } int V3OutFormatter::endLevels(const char* strg) { int levels = m_indentLevel; { const char* cp = strg; while (isspace(*cp)) cp++; switch (*cp) { case '\n': // Newlines.. No need for whitespace before it return 0; case '#': // Preproc directive return 0; } { // label/public/private: Deindent by 2 spaces const char* mp = cp; for (; isalnum(*mp); mp++) ; if (mp[0]==':' && mp[1]!=':') return (levels-m_blockIndent/2); } } // We want "} else {" to be one level to the left of normal for (const char* cp=strg; *cp; cp++) { switch (*cp) { case '}': case ')': levels -= m_blockIndent; break; case '<': if (m_lang==LA_XML) { if (cp[1] == '/') levels -= m_blockIndent; } break; case 'e': if (m_lang==LA_VERILOG && tokenEnd(cp)) { levels -= m_blockIndent; } break; case '\t': case ' ': break; // Continue default: return levels; // Letter } } return levels; } void V3OutFormatter::puts(const char* strg) { if (m_prependIndent) { putsNoTracking(indentSpaces(endLevels(strg))); m_prependIndent = false; } bool wordstart = true; bool equalsForBracket = false; // Looking for "= {" for (const char* cp=strg; *cp; cp++) { putcNoTracking(*cp); switch (*cp) { case '\n': m_lineno++; wordstart = true; if (cp[1]=='\0') { m_prependIndent = true; // Add the indent later, may be a indentInc/indentDec called between now and then } else { m_prependIndent = false; putsNoTracking(indentSpaces(endLevels(cp+1))); } break; case ' ': wordstart = true; break; case '\t': wordstart = true; break; case '/': if (m_lang==LA_C || m_lang==LA_VERILOG) { if (cp>strg && cp[-1]=='/') { // Output ignoring contents to EOL cp++; while (*cp && cp[1] && cp[1] != '\n') putcNoTracking(*cp++); if (*cp) putcNoTracking(*cp); } } break; case '{': if (m_lang==LA_C && (equalsForBracket || m_bracketLevel)) { // Break up large code inside "= { ..." m_parenVec.push(m_indentLevel*m_blockIndent); // Line up continuation with block+1 ++m_bracketLevel; } indentInc(); break; case '}': if (m_bracketLevel>0) { m_parenVec.pop(); --m_bracketLevel; } indentDec(); break; case '(': indentInc(); if (v3Global.opt.decoration()) { m_parenVec.push(m_column); // Line up continuation with open paren, plus one indent } else { m_parenVec.push(m_indentLevel*m_blockIndent); // Line up continuation with block+1 } break; case ')': if (!m_parenVec.empty()) m_parenVec.pop(); indentDec(); break; case '<': if (m_lang==LA_XML) { if (cp[1] == '/') {} // Zero as the > will result in net decrease by one else if (cp[1] == '!' || cp[1] == '?') { indentInc(); } // net same indent else { indentInc(); indentInc(); } // net increase by one } break; case '>': if (m_lang==LA_XML) { indentDec(); if (cp>strg && cp[-1]=='/') indentDec(); // < ..... /> stays same level } break; case 'b': if (wordstart && m_lang==LA_VERILOG && tokenStart(cp, "begin")) { indentInc(); } wordstart = false; break; case 'c': if (wordstart && m_lang==LA_VERILOG && (tokenStart(cp, "case") || tokenStart(cp, "casex") || tokenStart(cp, "casez"))) { indentInc(); } wordstart = false; break; case 'e': if (wordstart && m_lang==LA_VERILOG && tokenEnd(cp)) { indentDec(); } wordstart = false; break; case 'm': if (wordstart && m_lang==LA_VERILOG && tokenStart(cp, "module")) { indentInc(); } wordstart = false; break; default: wordstart = false; break; } switch (*cp) { case '=': equalsForBracket = true; break; case ' ': break; default: equalsForBracket = false; break; } } } void V3OutFormatter::putBreakExpr() { if (!m_parenVec.empty()) putBreak(); } // Add a line break if too wide void V3OutFormatter::putBreak() { if (!m_nobreak) { //char s[1000]; sprintf(s, "{%d,%d}", m_column, m_parenVec.top()); putsNoTracking(s); if (exceededWidth()) { putcNoTracking('\n'); if (!m_parenVec.empty()) putsNoTracking(indentSpaces(m_parenVec.top())); } } } void V3OutFormatter::putsQuoted(const string& strg) { // Quote \ and " for use inside C programs // Don't use to quote a filename for #include - #include doesn't \ escape. putcNoTracking('"'); string quoted = quoteNameControls(strg); for (string::const_iterator cp=quoted.begin(); cp!=quoted.end(); ++cp) { putcNoTracking(*cp); } putcNoTracking('"'); } void V3OutFormatter::putsNoTracking(const string& strg) { // Don't track {}'s, probably because it's a $display format string for (string::const_iterator cp=strg.begin(); cp!=strg.end(); ++cp) { putcNoTracking(*cp); } } void V3OutFormatter::putcNoTracking(char chr) { switch (chr) { case '\n': m_lineno++; m_column = 0; m_nobreak = true; break; case '\t': m_column = ((m_column + 9)/8)*8; break; case ' ': case '(': case '|': case '&': m_column++; break; default: m_column++; m_nobreak = false; break; } putcOutput(chr); } string V3OutFormatter::quoteNameControls(const string& namein, V3OutFormatter::Language lang) { // Encode control chars into output-appropriate escapes // Reverse is V3Parse::deQuote string out; if (lang==LA_XML) { // Encode chars into XML string for (string::const_iterator pos=namein.begin(); pos!=namein.end(); ++pos) { if (pos[0]=='"') { out += string("""); } else if (pos[0]=='\'') { out += string("'"); } else if (pos[0]=='<') { out += string("<"); } else if (pos[0]=='>') { out += string(">"); } else if (pos[0]=='&') { out += string("&"); } else if (isprint(pos[0])) { out += pos[0]; } else { char decimal[10]; sprintf(decimal, "&#%u;", (unsigned char)pos[0]); out += decimal; } } } else { // Encode control chars into C style escapes for (string::const_iterator pos=namein.begin(); pos!=namein.end(); ++pos) { if (pos[0]=='\\' || pos[0]=='"') { out += string("\\")+pos[0]; } else if (pos[0]=='\n') { out += "\\n"; } else if (pos[0]=='\r') { out += "\\r"; } else if (pos[0]=='\t') { out += "\\t"; } else if (isprint(pos[0])) { out += pos[0]; } else { // This will also cover \a etc // Can't use %03o as messes up when signed char octal[10]; sprintf(octal, "\\%o%o%o", (pos[0]>>6)&3, (pos[0]>>3)&7, pos[0]&7); out += octal; } } } return out; } //---------------------------------------------------------------------- // Simple wrappers void V3OutFormatter::printf(const char* fmt...) { char sbuff[5000]; va_list ap; va_start(ap, fmt); vsprintf(sbuff, fmt, ap); va_end(ap); this->puts(sbuff); } //###################################################################### // V3OutFormatter: A class for printing to a file, with automatic indentation of C++ code. V3OutFile::V3OutFile(const string& filename, V3OutFormatter::Language lang) : V3OutFormatter(filename, lang) { if ((m_fp = V3File::new_fopen_w(filename)) == NULL) { v3fatal("Cannot write "< IdMap; IdMap m_nameMap; // Map of old name into new name typedef vl_unordered_set IdSet; IdSet m_newIdSet; // Which new names exist protected: // CONSTRUCTORS friend class VIdProtect; static VIdProtectImp& singleton() { static VIdProtectImp s; return s; } public: VIdProtectImp() { passthru("this"); passthru("TOPp"); passthru("vlTOPp"); passthru("vlSymsp"); } ~VIdProtectImp() {} // METHODS string passthru(const string& old) { if (!v3Global.opt.protectIds()) return old; IdMap::iterator it = m_nameMap.find(old); if (it != m_nameMap.end()) { // No way to go back and correct the older crypt name UASSERT(old == it->second, "Passthru request for '" +old+"' after already --protect-ids of it."); } else { m_nameMap.insert(make_pair(old, old)); m_newIdSet.insert(old); } return old; } string protectIf(const string& old, bool doIt) { if (!v3Global.opt.protectIds() || old.empty() || !doIt) return old; IdMap::iterator it = m_nameMap.find(old); if (it != m_nameMap.end()) return it->second; else { string out; if (v3Global.opt.debugProtect()) { // This lets us see the symbol being protected to debug cases // where e.g. the definition is protect() but reference is // missing a protect() out = "PS"+old; } else { VHashSha256 digest (v3Global.opt.protectKeyDefaulted()); digest.insert(old); // Add "PS" prefix (Protect Symbols) as cannot start symbol with number out = "PS"+digest.digestSymbol(); // See if we can shrink the digest symbol to something smaller for (size_t len = 6; len < out.size() - 3; len += 3) { string tryout = out.substr(0, len); if (m_newIdSet.find(tryout) == m_newIdSet.end()) { out = tryout; break; } } } m_nameMap.insert(make_pair(old, out)); m_newIdSet.insert(out); return out; } } string protectWordsIf(const string& old, bool doIt) { // Split at " " (for traces), "." (for scopes), or "->" (for scopes) if (!(doIt && v3Global.opt.protectIds())) return old; string out; string::size_type start = 0; // space, ., -> while (1) { // When C++11, use find_if and lambda string::size_type pos = string::npos; string separator = ""; trySep(old, start, " ", pos/*ref*/, separator/*ref*/); trySep(old, start, ".", pos/*ref*/, separator/*ref*/); trySep(old, start, "->", pos/*ref*/, separator/*ref*/); if (pos == string::npos) break; out += protectIf(old.substr(start, pos-start), true) + separator; start = pos + separator.length(); } out += protectIf(old.substr(start), true); return out; } void writeMapFile(const string& filename) const { V3OutXmlFile of (filename); of.putsHeader(); of.puts("\n"); of.puts("\n"); { for (IdMap::const_iterator it = m_nameMap.begin(); it != m_nameMap.end(); ++it) { of.puts("second+"\" to=\""+it->first+"\"/>\n"); } } of.puts("\n"); } private: void trySep(const string& old, string::size_type start, const string& trySep, string::size_type& posr, string& separatorr) { string::size_type trypos = old.find(trySep, start); if (trypos != string::npos) { if (posr == string::npos || (posr > trypos)) { posr = trypos; separatorr = trySep; } } } }; string VIdProtect::protectIf(const string& old, bool doIt) { return VIdProtectImp::singleton().protectIf(old, doIt); } string VIdProtect::protectWordsIf(const string& old, bool doIt) { return VIdProtectImp::singleton().protectWordsIf(old, doIt); } void VIdProtect::writeMapFile(const string& filename) { VIdProtectImp::singleton().writeMapFile(filename); }