Skip to content

Commit cf36eee

Browse files
committed
Switch DSLX interpreter to be emit a InterpreterEventProto.
1 parent e234b02 commit cf36eee

35 files changed

+1634
-445
lines changed

xls/dev_tools/BUILD

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,3 +923,33 @@ py_library(
923923
name = "check_cpp_includes",
924924
srcs = ["check_cpp_includes.py"],
925925
)
926+
927+
cc_binary(
928+
name = "dump_call_trace",
929+
srcs = ["dump_call_trace.cc"],
930+
deps = [
931+
"//xls/common:exit_status",
932+
"//xls/common:init_xls",
933+
"//xls/common/file:filesystem",
934+
"//xls/ir:evaluator_result_cc_proto",
935+
"//xls/ir:value",
936+
"@com_google_absl//absl/flags:flag",
937+
"@com_google_absl//absl/log:check",
938+
"@com_google_absl//absl/status",
939+
"@com_google_absl//absl/status:statusor",
940+
"@com_google_protobuf//:protobuf",
941+
],
942+
)
943+
944+
py_test(
945+
name = "dump_call_trace_test",
946+
srcs = ["dump_call_trace_test.py"],
947+
data = [
948+
":dump_call_trace",
949+
"//xls/dslx:interpreter_main",
950+
],
951+
deps = [
952+
"//xls/common:runfiles",
953+
"@abseil-py//absl/testing:absltest",
954+
],
955+
)

xls/dev_tools/dump_call_trace.cc

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
#include <algorithm>
2+
#include <limits>
3+
#include <memory>
4+
#include <string>
5+
#include <string_view>
6+
#include <tuple>
7+
#include <vector>
8+
9+
#include "absl/flags/flag.h"
10+
#include "absl/log/check.h"
11+
#include "absl/status/status.h"
12+
#include "absl/status/statusor.h"
13+
#include "google/protobuf/text_format.h"
14+
#include "xls/common/exit_status.h"
15+
#include "xls/common/file/filesystem.h"
16+
#include "xls/common/init_xls.h"
17+
#include "xls/ir/evaluator_result.pb.h"
18+
#include "xls/ir/value.h"
19+
20+
static constexpr std::string_view kUsage = R"(
21+
Reads a EvaluatorResultsProto and prints the trace call messages
22+
in a nicely formatted form. Usage:
23+
24+
interpreter_main --trace_calls -- output_results_proto=<PROTOFILE>
25+
dump_call_trace --input=<PROTOFILE>
26+
)";
27+
28+
ABSL_FLAG(std::string, input, "", "Path to EvaluatorResultsProto file.");
29+
ABSL_FLAG(std::string, function, "",
30+
"If set, only emit calls nested within this function; output is "
31+
"unindented relative to this function");
32+
ABSL_FLAG(bool, sort_calls, false,
33+
"If set, sort sibling calls by source location (file, line, column) "
34+
"within each function scope");
35+
36+
namespace xls {
37+
namespace {
38+
39+
constexpr int kIndentSpaces = 2;
40+
static void PrintIndent(int spaces) { printf("%*s", spaces, ""); }
41+
42+
static void PrettyPrintValue(const Value& v, int indent) {
43+
if (v.IsTuple()) {
44+
PrintIndent(indent);
45+
printf("(\n");
46+
for (const Value& e : v.elements()) {
47+
PrettyPrintValue(e, indent + kIndentSpaces);
48+
}
49+
PrintIndent(indent);
50+
printf(")\n");
51+
return;
52+
}
53+
if (v.IsArray()) {
54+
PrintIndent(indent);
55+
printf("[\n");
56+
for (const Value& e : v.elements()) {
57+
PrettyPrintValue(e, indent + kIndentSpaces);
58+
}
59+
PrintIndent(indent);
60+
printf("]\n");
61+
return;
62+
}
63+
PrintIndent(indent);
64+
printf("%s\n", v.ToString().c_str());
65+
}
66+
67+
static std::string LocationPrefix(const TraceMessageProto& tm) {
68+
if (!tm.has_location()) {
69+
return std::string("<unknown>: ");
70+
}
71+
const SourceLocationProto& loc = tm.location();
72+
std::string f =
73+
loc.has_filename() ? loc.filename() : std::string("<unknown>");
74+
std::string l =
75+
loc.has_line() ? std::to_string(loc.line()) : std::string("?");
76+
std::string c =
77+
loc.has_column() ? std::to_string(loc.column()) : std::string("?");
78+
return absl::StrFormat("%s:%s:%s: ", f, l, c);
79+
}
80+
81+
struct CallNode {
82+
const TraceMessageProto* trace_msg;
83+
const TraceMessageProto* return_msg = nullptr;
84+
int64_t depth;
85+
std::vector<std::unique_ptr<CallNode>> children;
86+
};
87+
88+
static void SortChildrenByLocation(CallNode* node) {
89+
auto location_key = [](const TraceMessageProto& tm) {
90+
int64_t line_no = std::numeric_limits<int64_t>::max();
91+
int64_t col_no = std::numeric_limits<int64_t>::max();
92+
if (tm.has_location()) {
93+
const SourceLocationProto& loc = tm.location();
94+
if (loc.has_line()) {
95+
line_no = loc.line();
96+
}
97+
if (loc.has_column()) {
98+
col_no = loc.column();
99+
}
100+
}
101+
return std::make_tuple(line_no, col_no);
102+
};
103+
104+
std::stable_sort(node->children.begin(), node->children.end(),
105+
[&](const std::unique_ptr<CallNode>& a,
106+
const std::unique_ptr<CallNode>& b) {
107+
return location_key(*a->trace_msg) <
108+
location_key(*b->trace_msg);
109+
});
110+
for (const auto& child : node->children) {
111+
SortChildrenByLocation(child.get());
112+
}
113+
}
114+
115+
static void PrintCallPrettyAtDepth(const CallNode& node, int64_t depth) {
116+
const TraceMessageProto& tm = *node.trace_msg;
117+
const TraceCallProto& call = tm.call();
118+
std::string indent(depth * kIndentSpaces, ' ');
119+
std::string loc_prefix = LocationPrefix(tm);
120+
const std::string& fn = call.function_name();
121+
printf("%s%s%s(\n", indent.c_str(), loc_prefix.c_str(), fn.c_str());
122+
std::string arg_indent = indent + std::string(kIndentSpaces, ' ');
123+
for (const ValueProto& vp : call.args()) {
124+
absl::StatusOr<Value> v = Value::FromProto(vp);
125+
if (!v.ok()) {
126+
printf("%s<invalid value: %s>\n", arg_indent.c_str(),
127+
v.status().ToString().c_str());
128+
continue;
129+
}
130+
PrettyPrintValue(*v, /*indent=*/static_cast<int>(arg_indent.size()));
131+
}
132+
printf("%s)\n", indent.c_str());
133+
}
134+
135+
static void PrintReturnPrettyAtDepth(const CallNode& node, int64_t depth) {
136+
if (node.return_msg == nullptr || !node.return_msg->has_call_return()) {
137+
return;
138+
}
139+
const TraceMessageProto& tm = *node.trace_msg;
140+
const TraceCallProto& call = tm.call();
141+
std::string indent(depth * kIndentSpaces, ' ');
142+
const std::string& fn = call.function_name();
143+
const TraceCallReturnProto& cr = node.return_msg->call_return();
144+
absl::StatusOr<Value> ret_v = Value::FromProto(cr.return_value());
145+
if (!ret_v.ok()) {
146+
printf("%s%s(...) => <invalid return: %s>\n", indent.c_str(), fn.c_str(),
147+
ret_v.status().ToString().c_str());
148+
return;
149+
}
150+
std::string ret_str = ret_v->ToString();
151+
printf("%s%s(...) => %s\n", indent.c_str(), fn.c_str(), ret_str.c_str());
152+
}
153+
154+
static void PrintSubtree(const CallNode* node, int depth_adjustment) {
155+
PrintCallPrettyAtDepth(*node, node->depth + depth_adjustment);
156+
int child_adjustment = depth_adjustment;
157+
for (const auto& child : node->children) {
158+
PrintSubtree(child.get(), child_adjustment);
159+
}
160+
PrintReturnPrettyAtDepth(*node, node->depth + depth_adjustment);
161+
}
162+
163+
// Only one subtree printer is needed; we control indentation via
164+
// depth_adjustment.
165+
166+
static absl::Status RealMain(std::string_view input_path,
167+
const std::string& filter_function,
168+
bool sort_calls) {
169+
XLS_ASSIGN_OR_RETURN(std::string contents, GetFileContents(input_path));
170+
EvaluatorResultsProto results;
171+
QCHECK(google::protobuf::TextFormat::ParseFromString(contents, &results))
172+
<< "Failed to parse EvaluatorResultsProto textproto from: " << input_path;
173+
174+
for (const EvaluatorResultProto& result : results.results()) {
175+
const EvaluatorEventsProto& events = result.events();
176+
177+
// Build a forest of call trees using call_depth nesting.
178+
std::vector<std::unique_ptr<CallNode>> roots;
179+
std::vector<CallNode*> stack; // Stack of active calls by depth.
180+
for (const TraceMessageProto& tm : events.trace_msgs()) {
181+
if (tm.type_case() == TraceMessageProto::kCall) {
182+
const TraceCallProto& call = tm.call();
183+
int64_t depth = call.call_depth();
184+
while (!stack.empty() && static_cast<int64_t>(stack.size()) > depth) {
185+
stack.pop_back();
186+
}
187+
auto node = std::make_unique<CallNode>();
188+
node->trace_msg = &tm;
189+
node->depth = depth;
190+
CallNode* node_raw = node.get();
191+
if (stack.empty()) {
192+
roots.push_back(std::move(node));
193+
} else {
194+
stack.back()->children.push_back(std::move(node));
195+
}
196+
stack.push_back(node_raw);
197+
} else if (tm.type_case() == TraceMessageProto::kCallReturn) {
198+
const TraceCallReturnProto& cr = tm.call_return();
199+
int64_t depth = cr.call_depth();
200+
// When call_depth is N, there should be N+1 nodes on the stack, with
201+
// the top corresponding to this call.
202+
while (!stack.empty() &&
203+
static_cast<int64_t>(stack.size()) > depth + 1) {
204+
stack.pop_back();
205+
}
206+
if (!stack.empty() && static_cast<int64_t>(stack.size()) == depth + 1) {
207+
stack.back()->return_msg = &tm;
208+
}
209+
}
210+
}
211+
212+
// Optionally sort siblings by source location within each function scope.
213+
if (sort_calls) {
214+
for (const auto& root : roots) {
215+
SortChildrenByLocation(root.get());
216+
}
217+
}
218+
219+
if (filter_function.empty()) {
220+
for (const auto& root : roots) {
221+
PrintSubtree(root.get(), /*depth_adjustment=*/0);
222+
}
223+
} else {
224+
// Print each occurrence of the filter function with normalize
225+
// indentation.
226+
std::vector<const CallNode*> stack_nodes;
227+
stack_nodes.reserve(roots.size());
228+
// DFS through the forest.
229+
std::vector<const CallNode*> dfs;
230+
dfs.reserve(roots.size());
231+
for (const auto& root : roots) {
232+
dfs.push_back(root.get());
233+
}
234+
while (!dfs.empty()) {
235+
const CallNode* node = dfs.back();
236+
dfs.pop_back();
237+
if (node->trace_msg->call().function_name() == filter_function) {
238+
PrintSubtree(node,
239+
/*depth_adjustment=*/-static_cast<int>(node->depth));
240+
}
241+
for (auto it = node->children.rbegin(); it != node->children.rend();
242+
++it) {
243+
dfs.push_back(it->get());
244+
}
245+
}
246+
}
247+
}
248+
249+
return absl::OkStatus();
250+
}
251+
252+
} // namespace
253+
} // namespace xls
254+
255+
int main(int argc, char** argv) {
256+
xls::InitXls(kUsage, argc, argv);
257+
std::string input = absl::GetFlag(FLAGS_input);
258+
QCHECK(!input.empty()) << "--input must be specified";
259+
std::string function = absl::GetFlag(FLAGS_function);
260+
bool sort_calls = absl::GetFlag(FLAGS_sort_calls);
261+
return xls::ExitStatus(xls::RealMain(input, function, sort_calls));
262+
}

0 commit comments

Comments
 (0)