Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JSON AST dumps #46

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ PY_PROGRAMS = \
src/cppcheck_filtered \
src/flexfix \
src/vlcovgen \
src/.gdbinit.py \
test_regress/t/*.pf \
nodist/clang_check_attributes \
nodist/code_coverage \
Expand Down
1 change: 1 addition & 0 deletions docs/CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ Srinivasan Venkataramanan
Stefan Wallentowitz
Stephen Henry
Steven Hugg
Szymon Gizler
Sören Tempel
Teng Huang
Tim Hutt
Expand Down
55 changes: 55 additions & 0 deletions docs/internals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1696,6 +1696,47 @@ Similarly, the ``NETLIST`` has a list of modules referred to by its
``op1p()`` pointer.


.tree.json Output
-----------------

``.tree.json``` is an alternative dump format to ``.tree`` that is meant for programmatic processing (e.g. with `astsee <https://github.com/antmicro/astsee>_`]). To enable this dump format, use ``--dump-tree-json``)

Structure:
::

{
/* here go atributes that are common to all types of nodes */
"type": "VAR",
"name": "out_wide",
"addr": "0x91a780",
"file": "t_do_while.v:18:4", /* <filename>:<linenum>:<colnum> */
"editNum": 634,
/* here go fields that are specific to VAR nodes: */
"isSc": false,
"ioDirection": "NONE",
"isConst": false,
"isPullup": false,
"isPulldown": false,
"isUsedClock": false,
"isSigPublic": false,
"isLatched": false,
"isUsedLoopIdx": false,
"noReset": false,
"attrIsolateAssign": false,
"attrFileDescr": false,
"isDpiOpenArray": false,
"isFuncReturn": false,
"isFuncLocal": false,
"attrClocker": "UNKNOWN",
"lifetime": "NONE",
"varType": "VAR",
/* here go lists of child nodes (which use similar structure as their parrent): */
op1: [ /* ... */ ],
op2: [ /* ... */ ],
op3: [ /* ... */ ],
op4: [ /* ... */ ]
}

.tree.dot Output
----------------

Expand Down Expand Up @@ -1768,6 +1809,20 @@ To print a node:
pnt nodep
# or: call dumpTreeGdb(nodep) # aliased to "pnt" in src/.gdbinit

``src/.gdbinit`` and ``src/.gdbinit.py`` define handy utilities for working with
JSON AST dumps. For example

* ``jstash nodep`` - Perform a JSON AST dump and save it into GDB value history (e.g. ``$1``)
* ``jtree nodep`` - Perform a JSON AST dump and pretty print it using ``astsee_verilator``.
* ``jtree $1`` - Pretty print a dump that was previously saved by ``jstash``.
* ``jtree nodep -d '.file, .timeunit'`` - Perform a JSON AST dump, filter out some fields and pretty print it.
* ``jtree 0x55555613dca0`` - Pretty print using address literal (rather than actual pointer).
* ``jtree $1 nodep`` - Diff ``nodep`` against an older dump.

A detailed description of ``jstash`` and ``jtree`` can be displayed using ``gdb``'s ``help`` command.

These commands require `astsee <https://github.com/antmicro/astsee>_` to be installed.

When GDB halts, it is useful to understand that the backtrace will commonly
show the iterator functions between each invocation of ``visit`` in the
backtrace. You will typically see a frame sequence something like:
Expand Down
20 changes: 20 additions & 0 deletions src/.gdbinit
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@ document pnt
Verilator: Print AstNode NODEP's tree
end

# source python-based gdb config with jshow/jdiff definitions
# (we store it in separate file, so it can be highlighted/linted/formatted as python)
sgizler marked this conversation as resolved.
Show resolved Hide resolved
python
import os
if "VERILATOR_ROOT" in os.environ:
gdbinit_py = os.environ["VERILATOR_ROOT"] + "/src/.gdbinit.py"
gdb.execute("source" + gdbinit_py)
end

define jstash
call (char*) &(AstNode::dumpJsonTreeGdb($arg0)[0])
end
document jstash
Verilator: Perform a JSON dump of the given AST node and save it in value history (e.g. $1) for later
inspection using jtree. The node can be a pointer identifier or an address literal.
end

alias -a js=jstash
alias -a jt=jtree

define dtf
call AstNode::dumpTreeFileGdb($arg0, 0)
end
Expand Down
68 changes: 68 additions & 0 deletions src/.gdbinit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# pylint: disable=line-too-long,invalid-name,multiple-statements,missing-function-docstring,missing-class-docstring,missing-module-docstring,no-else-return,too-few-public-methods,unused-argument
import os
import sys
import tempfile

import gdb # pylint: disable=import-error

# add dir with verilator_jsontree to import path
sgizler marked this conversation as resolved.
Show resolved Hide resolved
sys.path.append(os.environ["VERILATOR_ROOT"] + "/bin")


def _get_dump(node):
return gdb.execute(f'printf "%s", &(AstNode::dumpJsonTreeGdb({node})[0])',
to_string=True)


def _tmpfile():
return tempfile.NamedTemporaryFile(mode="wt") # write, text mode


def _fwrite(file, s):
"""Write to file and flush buffer before passing the file to astsee"""
file.write(s)
file.flush()


class JsonTreeCmd(gdb.Command):
"""Verilator: Pretty print or diff nodes using `astsee`. A node can be:
* an pointer identifier,
* an address literal,
* a GDB value (like `$1`) that stores a dump previously done by the `jstash` command.
Apart from not taking input from a file, it works exactly like `astsee`:
* passing one node gives you a pretty print,
* passing two nodes gives you a diff,
* for more options see `astsee` readme/help.
"""

def __init__(self):
super().__init__("jtree", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION)

def _null_check(self, old, new):
err = ""
if old == "<nullptr>\n": err += "old == <nullptr>\n"
if new == "<nullptr>\n": err += "new == <nullptr>"
if err: raise gdb.GdbError(err.strip("\n"))

def invoke(self, arg_str, from_tty):
import verilator_jsontree as jsontree # pylint: disable=wrong-import-position
# jsontree import may fail so we do it here rather than in outer scope

# We hack `astsee_verilator`'s arg parser to find arguments with nodes
# After finding them, we replace them with proper files
jsontree_args = jsontree.parser.parse_args(gdb.string_to_argv(arg_str))
self._null_check(jsontree_args.file, jsontree_args.newfile)
with _tmpfile() as oldfile, _tmpfile() as newfile:
if jsontree_args.file:
_fwrite(oldfile, _get_dump(jsontree_args.file))
jsontree_args.file = oldfile.name
if jsontree_args.newfile:
_fwrite(newfile, _get_dump(jsontree_args.newfile))
jsontree_args.newfile = newfile.name
try:
jsontree.main(jsontree_args)
except SystemExit: # jsontree prints nice errmsgs on exit(), so we just catch it to suppress cryptic python trace
sgizler marked this conversation as resolved.
Show resolved Hide resolved
return


JsonTreeCmd()
26 changes: 26 additions & 0 deletions src/V3Ast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <iomanip>
#include <memory>
#include <sstream>

VL_DEFINE_DEBUG_FUNCTIONS;

Expand Down Expand Up @@ -1170,6 +1171,22 @@ void AstNode::checkTreeIter(const AstNode* prevBackp) const VL_MT_STABLE {
}
}

// cppcheck-suppress unusedFunction // Debug only
std::string AstNode::dumpJsonTreeGdb(const AstNode* nodep) {
if (!nodep) return "<nullptr>\n";
std::stringstream nodepStream;
nodep->dumpTreeJson(nodepStream);
return nodepStream.rdbuf()->str();
}
// cppcheck-suppress unusedFunction // Debug only
// identity func to allow for passing already done dumps to jtree
const char* AstNode::dumpJsonTreeGdb(const char* str) { return str; }
// cppcheck-suppress unusedFunction // Debug only
// allow for passing pointer literals like 0x42.. without manual cast
std::string AstNode::dumpJsonTreeGdb(intptr_t nodep) {
if (!nodep) return "<nullptr>\n";
return dumpJsonTreeGdb((const AstNode*)nodep);
}
// cppcheck-suppress unusedFunction // Debug only
void AstNode::dumpGdb(const AstNode* nodep) { // For GDB only // LCOV_EXCL_LINE
if (!nodep) {
Expand Down Expand Up @@ -1328,6 +1345,15 @@ void AstNode::dumpTreeDot(std::ostream& os) const {
drawChildren(os, this, m_op4p, "op4");
}

void AstNode::dumpTreeJsonFile(const string& filename, bool append, bool doDump) {
if (!doDump) return;
UINFO(2, "Dumping " << filename << endl);
const std::unique_ptr<std::ofstream> treejsonp{V3File::new_ofstream(filename, append)};
if (treejsonp->fail()) v3fatal("Can't write " << filename);
dumpTreeJson(*treejsonp);
*treejsonp << "\n";
}

void AstNode::dumpTreeDotFile(const string& filename, bool append, bool doDump) {
if (doDump) {
UINFO(2, "Dumping " << filename << endl);
Expand Down
7 changes: 7 additions & 0 deletions src/V3Ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "V3Ast__gen_forward_class_decls.h" // From ./astgen

#include <cmath>
#include <cstdint>
#include <functional>
#include <map>
#include <set>
Expand Down Expand Up @@ -2161,6 +2162,9 @@ class AstNode VL_NOT_FINAL {
string warnOther() const VL_REQUIRES(V3Error::s().m_mutex) { return fileline()->warnOther(); }

virtual void dump(std::ostream& str = std::cout) const;
static std::string dumpJsonTreeGdb(const AstNode* nodep); // For GDB only
static const char* dumpJsonTreeGdb(const char* str); // For GDB only
static std::string dumpJsonTreeGdb(intptr_t nodep); // For GDB only
static void dumpGdb(const AstNode* nodep); // For GDB only
void dumpGdbHeader() const;

Expand Down Expand Up @@ -2216,6 +2220,9 @@ class AstNode VL_NOT_FINAL {
static void dumpTreeFileGdb(const AstNode* nodep, const char* filenamep = nullptr);
void dumpTreeDot(std::ostream& os = std::cout) const;
void dumpTreeDotFile(const string& filename, bool append = false, bool doDump = true);
virtual void dumpExtraJson(std::ostream& os) const {}; // node specific fields
void dumpTreeJson(std::ostream& os) const;
void dumpTreeJsonFile(const string& filename, bool append = false, bool doDump = true);

// METHODS - static advancement
static AstNode* afterCommentp(AstNode* nodep) {
Expand Down
9 changes: 9 additions & 0 deletions src/V3AstNodeDType.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class AstNodeDType VL_NOT_FINAL : public AstNode {
ASTGEN_MEMBERS_AstNodeDType;
// ACCESSORS
void dump(std::ostream& str) const override;
void dumpExtraJson(std::ostream& str) const override;
virtual void dumpSmall(std::ostream& str) const;
bool hasDType() const override { return true; }
/// Require VlUnpacked, instead of [] for POD elements.
Expand Down Expand Up @@ -148,6 +149,7 @@ class AstNodeArrayDType VL_NOT_FINAL : public AstNodeDType {
public:
ASTGEN_MEMBERS_AstNodeArrayDType;
void dump(std::ostream& str) const override;
void dumpExtraJson(std::ostream& str) const override;
void dumpSmall(std::ostream& str) const override;
const char* broken() const override {
BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists())
Expand Down Expand Up @@ -217,6 +219,7 @@ class AstNodeUOrStructDType VL_NOT_FINAL : public AstNodeDType {
int uniqueNum() const { return m_uniqueNum; }
const char* broken() const override;
void dump(std::ostream& str) const override;
void dumpExtraJson(std::ostream& str) const override;
bool isCompound() const override { return !packed(); }
// For basicp() we reuse the size to indicate a "fake" basic type of same size
AstBasicDType* basicp() const override {
Expand Down Expand Up @@ -387,6 +390,7 @@ class AstBasicDType final : public AstNodeDType {
public:
ASTGEN_MEMBERS_AstBasicDType;
void dump(std::ostream& str) const override;
void dumpExtraJson(std::ostream& str) const override;
// width/widthMin/numeric compared elsewhere
bool same(const AstNode* samep) const override;
bool similarDType(const AstNodeDType* samep) const override {
Expand Down Expand Up @@ -548,6 +552,7 @@ class AstClassRefDType final : public AstNodeDType {
return this == samep || (type() == samep->type() && same(samep));
}
void dump(std::ostream& str = std::cout) const override;
void dumpExtraJson(std::ostream& str = std::cout) const override;
void dumpSmall(std::ostream& str) const override;
string name() const override VL_MT_STABLE;
AstBasicDType* basicp() const override VL_MT_STABLE { return nullptr; }
Expand Down Expand Up @@ -813,6 +818,7 @@ class AstEnumDType final : public AstNodeDType {
string name() const override VL_MT_STABLE { return m_name; }
void name(const string& flag) override { m_name = flag; }
void dump(std::ostream& str = std::cout) const override;
void dumpExtraJson(std::ostream& str = std::cout) const override;
void dumpSmall(std::ostream& str) const override;
// METHODS
AstBasicDType* basicp() const override VL_MT_STABLE { return subDTypep()->basicp(); }
Expand Down Expand Up @@ -867,6 +873,7 @@ class AstIfaceRefDType final : public AstNodeDType {
// METHODS
const char* broken() const override;
void dump(std::ostream& str = std::cout) const override;
void dumpExtraJson(std::ostream& str = std::cout) const override;
void dumpSmall(std::ostream& str) const override;
void cloneRelink() override;
AstBasicDType* basicp() const override VL_MT_STABLE { return nullptr; }
Expand Down Expand Up @@ -982,6 +989,7 @@ class AstParamTypeDType final : public AstNodeDType {
}
ASTGEN_MEMBERS_AstParamTypeDType;
void dump(std::ostream& str = std::cout) const override;
void dumpExtraJson(std::ostream& str = std::cout) const override;
AstNodeDType* getChildDTypep() const override { return childDTypep(); }
AstNodeDType* subDTypep() const override VL_MT_STABLE {
return dtypep() ? dtypep() : childDTypep();
Expand Down Expand Up @@ -1135,6 +1143,7 @@ class AstRefDType final : public AstNodeDType {
return skipRefp()->similarDType(samep->skipRefp());
}
void dump(std::ostream& str = std::cout) const override;
void dumpExtraJson(std::ostream& str = std::cout) const override;
void dumpSmall(std::ostream& str) const override;
string name() const override VL_MT_STABLE { return m_name; }
string prettyDTypeName() const override {
Expand Down
Loading
Loading