diff --git a/common/ostream.h b/common/ostream.h index be984707af2d7..6ba004898875f 100644 --- a/common/ostream.h +++ b/common/ostream.h @@ -5,13 +5,14 @@ #ifndef CARBON_COMMON_OSTREAM_H_ #define CARBON_COMMON_OSTREAM_H_ +// Libraries should include this header instead of raw_ostream. + #include #include #include -#include "llvm/Support/raw_os_ostream.h" -// Libraries should include this header instead of raw_ostream. #include "llvm/Support/Compiler.h" +#include "llvm/Support/raw_os_ostream.h" #include "llvm/Support/raw_ostream.h" // IWYU pragma: export namespace Carbon { diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index d6a7b15da04de..d3b61857f8788 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -92,6 +92,25 @@ cc_library( ], ) +cc_library( + name = "dump", + srcs = ["dump.cpp"], + # Contains Dump methods without a forward declaration. + copts = ["-Wno-missing-prototypes"], + deps = [ + ":context", + "//common:check", + "//common:ostream", + "//toolchain/lex:dump", + "//toolchain/lex:tokenized_buffer", + "//toolchain/parse:dump", + "//toolchain/parse:tree", + "//toolchain/sem_ir:file", + ], + # Always link dump methods. + alwayslink = 1, +) + cc_library( name = "check", srcs = [ @@ -109,6 +128,7 @@ cc_library( hdrs = ["check.h"], deps = [ ":context", + ":dump", ":impl", ":interface", ":pointer_dereference", diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 42c76b1f15af7..67f8e5945e1a0 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -483,10 +483,15 @@ class Context { } auto sem_ir() -> SemIR::File& { return *sem_ir_; } + auto sem_ir() const -> const SemIR::File& { return *sem_ir_; } - auto parse_tree() -> const Parse::Tree& { return sem_ir_->parse_tree(); } + auto parse_tree() const -> const Parse::Tree& { + return sem_ir_->parse_tree(); + } - auto tokens() -> const Lex::TokenizedBuffer& { return parse_tree().tokens(); } + auto tokens() const -> const Lex::TokenizedBuffer& { + return parse_tree().tokens(); + } auto node_stack() -> NodeStack& { return node_stack_; } diff --git a/toolchain/check/dump.cpp b/toolchain/check/dump.cpp new file mode 100644 index 0000000000000..d92306c78d518 --- /dev/null +++ b/toolchain/check/dump.cpp @@ -0,0 +1,78 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// This library contains functions to assist dumping objects to stderr during +// interactive debugging. Functions named `Dump` are intended for direct use by +// developers, and should use overload resolution to determine which will be +// invoked. The debugger should do namespace resolution automatically. For +// example: +// +// - lldb: `expr Dump(context, id)` +// - gdb: `call Dump(context, id)` +// +// The `DumpNoNewline` functions are helpers that exclude a trailing newline. +// They're intended to be composed by `Dump` function implementations. + +#ifndef NDEBUG + +#include "toolchain/lex/dump.h" + +#include "common/check.h" +#include "common/ostream.h" +#include "toolchain/check/context.h" +#include "toolchain/lex/tokenized_buffer.h" +#include "toolchain/parse/dump.h" +#include "toolchain/parse/tree.h" +#include "toolchain/sem_ir/file.h" + +namespace Carbon::Check { + +static auto DumpNoNewline(const Context& context, SemIR::LocId loc_id) -> void { + if (!loc_id.is_valid()) { + llvm::errs() << "LocId(invalid)"; + return; + } + + if (loc_id.is_node_id()) { + auto token = context.parse_tree().node_token(loc_id.node_id()); + auto line = context.tokens().GetLineNumber(token); + auto col = context.tokens().GetColumnNumber(token); + const char* implicit = loc_id.is_implicit() ? " implicit" : ""; + llvm::errs() << "LocId("; + llvm::errs().write_escaped(context.sem_ir().filename()); + llvm::errs() << ":" << line << ":" << col << implicit << ")"; + } else { + CARBON_CHECK(loc_id.is_import_ir_inst_id()); + + auto import_ir_id = context.sem_ir() + .import_ir_insts() + .Get(loc_id.import_ir_inst_id()) + .ir_id; + const auto* import_file = + context.sem_ir().import_irs().Get(import_ir_id).sem_ir; + llvm::errs() << "LocId(import from \""; + llvm::errs().write_escaped(import_file->filename()); + llvm::errs() << "\")"; + } +} + +LLVM_DUMP_METHOD auto Dump(const Context& context, Lex::TokenIndex token) + -> void { + Parse::Dump(context.parse_tree(), token); +} + +LLVM_DUMP_METHOD auto Dump(const Context& context, Parse::NodeId node_id) + -> void { + Parse::Dump(context.parse_tree(), node_id); +} + +LLVM_DUMP_METHOD auto Dump(const Context& context, SemIR::LocId loc_id) + -> void { + DumpNoNewline(context, loc_id); + llvm::errs() << '\n'; +} + +} // namespace Carbon::Check + +#endif // NDEBUG diff --git a/toolchain/docs/adding_features.md b/toolchain/docs/adding_features.md index 2eb1800895434..cf23795105dfe 100644 --- a/toolchain/docs/adding_features.md +++ b/toolchain/docs/adding_features.md @@ -23,6 +23,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Reviewing test deltas](#reviewing-test-deltas) - [Verbose output](#verbose-output) - [Stack traces](#stack-traces) + - [Dumping objects in interactive debuggers](#dumping-objects-in-interactive-debuggers) @@ -496,3 +497,13 @@ While the iterative processing pattern means function stack traces will have minimal context for how the current function is reached, we use LLVM's `PrettyStackTrace` to include details about the state stack. The state stack will be above the function stack in crash output. + +### Dumping objects in interactive debuggers + +We provide namespace-scoped `Dump` functions in several components, such as +[check/dump.cpp](/toolchain/check/dump.cpp). These `Dump` functions will print +contextual information about an object to stderr. The files contain details +regarding support. + +Objects which inherit from `Printable` also have `Dump` member functions, but +these will lack contextual information. diff --git a/toolchain/lex/BUILD b/toolchain/lex/BUILD index 08e2f0bb0d5f7..c68378ef81ccd 100644 --- a/toolchain/lex/BUILD +++ b/toolchain/lex/BUILD @@ -184,6 +184,7 @@ cc_library( hdrs = ["lex.h"], deps = [ ":character_set", + ":dump", ":helpers", ":numeric_literal", ":string_literal", @@ -198,6 +199,18 @@ cc_library( ], ) +cc_library( + name = "dump", + srcs = ["dump.cpp"], + hdrs = ["dump.h"], + deps = [ + ":tokenized_buffer", + "//common:ostream", + ], + # Always link dump methods. + alwayslink = 1, +) + cc_library( name = "token_index", hdrs = ["token_index.h"], diff --git a/toolchain/lex/dump.cpp b/toolchain/lex/dump.cpp new file mode 100644 index 0000000000000..d6e3532a6c9e6 --- /dev/null +++ b/toolchain/lex/dump.cpp @@ -0,0 +1,36 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef NDEBUG + +#include "toolchain/lex/dump.h" + +#include "common/ostream.h" + +namespace Carbon::Lex { + +auto DumpNoNewline(const TokenizedBuffer& tokens, TokenIndex token) -> void { + if (!token.is_valid()) { + llvm::errs() << "TokenIndex(invalid)"; + return; + } + + auto kind = tokens.GetKind(token); + auto line = tokens.GetLineNumber(token); + auto col = tokens.GetColumnNumber(token); + + llvm::errs() << "TokenIndex(kind: " << kind << ", loc: "; + llvm::errs().write_escaped(tokens.source().filename()); + llvm::errs() << ":" << line << ":" << col << ")"; +} + +LLVM_DUMP_METHOD auto Dump(const TokenizedBuffer& tokens, TokenIndex token) + -> void { + DumpNoNewline(tokens, token); + llvm::errs() << '\n'; +} + +} // namespace Carbon::Lex + +#endif // NDEBUG diff --git a/toolchain/lex/dump.h b/toolchain/lex/dump.h new file mode 100644 index 0000000000000..f38f5598b2713 --- /dev/null +++ b/toolchain/lex/dump.h @@ -0,0 +1,34 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// This library contains functions to assist dumping objects to stderr during +// interactive debugging. Functions named `Dump` are intended for direct use by +// developers, and should use overload resolution to determine which will be +// invoked. The debugger should do namespace resolution automatically. For +// example: +// +// - lldb: `expr Dump(tokens, id)` +// - gdb: `call Dump(tokens, id)` +// +// The `DumpNoNewline` functions are helpers that exclude a trailing newline. +// They're intended to be composed by `Dump` function implementations. + +#ifndef CARBON_TOOLCHAIN_LEX_DUMP_H_ +#define CARBON_TOOLCHAIN_LEX_DUMP_H_ + +#ifndef NDEBUG + +#include "toolchain/lex/tokenized_buffer.h" + +namespace Carbon::Lex { + +auto DumpNoNewline(const TokenizedBuffer& tokens, TokenIndex token) -> void; + +auto Dump(const TokenizedBuffer& tokens, TokenIndex token) -> void; + +} // namespace Carbon::Lex + +#endif // NDEBUG + +#endif // CARBON_TOOLCHAIN_LEX_DUMP_H_ diff --git a/toolchain/lex/tokenized_buffer.cpp b/toolchain/lex/tokenized_buffer.cpp index 6aec49a05cec7..acd7e9e987770 100644 --- a/toolchain/lex/tokenized_buffer.cpp +++ b/toolchain/lex/tokenized_buffer.cpp @@ -25,7 +25,7 @@ auto TokenizedBuffer::GetLine(TokenIndex token) const -> LineIndex { } auto TokenizedBuffer::GetLineNumber(TokenIndex token) const -> int { - return GetLineNumber(GetLine(token)); + return GetLine(token).index + 1; } auto TokenizedBuffer::GetColumnNumber(TokenIndex token) const -> int { @@ -162,10 +162,6 @@ auto TokenizedBuffer::IsRecoveryToken(TokenIndex token) const -> bool { return recovery_tokens_[token.index]; } -auto TokenizedBuffer::GetLineNumber(LineIndex line) const -> int { - return line.index + 1; -} - auto TokenizedBuffer::GetNextLine(LineIndex line) const -> LineIndex { LineIndex next(line.index + 1); CARBON_DCHECK(static_cast(next.index) < line_infos_.size()); @@ -262,7 +258,7 @@ auto TokenizedBuffer::PrintToken(llvm::raw_ostream& output_stream, llvm::right_justify( llvm::formatv("'{0}'", token_info.kind().name()).str(), widths.kind + 2), - llvm::format_decimal(GetLineNumber(GetLine(token)), widths.line), + llvm::format_decimal(GetLineNumber(token), widths.line), llvm::format_decimal(GetColumnNumber(token), widths.column), llvm::format_decimal(GetIndentColumnNumber(line_index), widths.indent), token_text); diff --git a/toolchain/lex/tokenized_buffer.h b/toolchain/lex/tokenized_buffer.h index 1822a7c25823c..748e65f51e810 100644 --- a/toolchain/lex/tokenized_buffer.h +++ b/toolchain/lex/tokenized_buffer.h @@ -156,9 +156,6 @@ class TokenizedBuffer : public Printable { // For example, a closing paren inserted to match an unmatched paren. auto IsRecoveryToken(TokenIndex token) const -> bool; - // Returns the 1-based line number. - auto GetLineNumber(LineIndex line) const -> int; - // Returns the 1-based indentation column number. auto GetIndentColumnNumber(LineIndex line) const -> int; diff --git a/toolchain/parse/BUILD b/toolchain/parse/BUILD index 310ff296eeca1..24ee38f008373 100644 --- a/toolchain/parse/BUILD +++ b/toolchain/parse/BUILD @@ -88,6 +88,7 @@ cc_library( ], deps = [ ":context", + ":dump", ":node_kind", ":state", ":tree", @@ -102,6 +103,19 @@ cc_library( ], ) +cc_library( + name = "dump", + srcs = ["dump.cpp"], + hdrs = ["dump.h"], + deps = [ + ":tree", + "//common:ostream", + "//toolchain/lex:dump", + ], + # Always link dump methods. + alwayslink = 1, +) + cc_library( name = "state", srcs = ["state.cpp"], diff --git a/toolchain/parse/context.cpp b/toolchain/parse/context.cpp index dbcd5c6b14338..fd0b6384cb2a3 100644 --- a/toolchain/parse/context.cpp +++ b/toolchain/parse/context.cpp @@ -481,7 +481,7 @@ auto Context::PrintForStackDump(llvm::raw_ostream& output) const -> void { auto Context::PrintTokenForStackDump(llvm::raw_ostream& output, Lex::TokenIndex token) const -> void { - output << " @ " << tokens_->GetLineNumber(tokens_->GetLine(token)) << ":" + output << " @ " << tokens_->GetLineNumber(token) << ":" << tokens_->GetColumnNumber(token) << ": token " << token << " : " << tokens_->GetKind(token) << "\n"; } diff --git a/toolchain/parse/dump.cpp b/toolchain/parse/dump.cpp new file mode 100644 index 0000000000000..127990596e728 --- /dev/null +++ b/toolchain/parse/dump.cpp @@ -0,0 +1,39 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef NDEBUG + +#include "toolchain/parse/dump.h" + +#include "common/ostream.h" +#include "toolchain/lex/dump.h" + +namespace Carbon::Parse { + +auto DumpNoNewline(const Tree& tree, NodeId node_id) -> void { + if (!node_id.is_valid()) { + llvm::errs() << "NodeId(invalid)"; + return; + } + + auto kind = tree.node_kind(node_id); + auto token = tree.node_token(node_id); + + llvm::errs() << "NodeId(kind: " << kind << ", token: "; + Lex::DumpNoNewline(tree.tokens(), token); + llvm::errs() << ")"; +} + +LLVM_DUMP_METHOD auto Dump(const Tree& tree, Lex::TokenIndex token) -> void { + Lex::Dump(tree.tokens(), token); +} + +LLVM_DUMP_METHOD auto Dump(const Tree& tree, NodeId node_id) -> void { + DumpNoNewline(tree, node_id); + llvm::errs() << '\n'; +} + +} // namespace Carbon::Parse + +#endif // NDEBUG diff --git a/toolchain/parse/dump.h b/toolchain/parse/dump.h new file mode 100644 index 0000000000000..4c3d90377c3b4 --- /dev/null +++ b/toolchain/parse/dump.h @@ -0,0 +1,35 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// This library contains functions to assist dumping objects to stderr during +// interactive debugging. Functions named `Dump` are intended for direct use by +// developers, and should use overload resolution to determine which will be +// invoked. The debugger should do namespace resolution automatically. For +// example: +// +// - lldb: `expr Dump(tree, id)` +// - gdb: `call Dump(tree, id)` +// +// The `DumpNoNewline` functions are helpers that exclude a trailing newline. +// They're intended to be composed by `Dump` function implementations. + +#ifndef CARBON_TOOLCHAIN_PARSE_DUMP_H_ +#define CARBON_TOOLCHAIN_PARSE_DUMP_H_ + +#ifndef NDEBUG + +#include "toolchain/parse/tree.h" + +namespace Carbon::Parse { + +auto DumpNoNewline(const Tree& tree, NodeId node_id) -> void; + +auto Dump(const Tree& tree, Lex::TokenIndex token) -> void; +auto Dump(const Tree& tree, NodeId node_id) -> void; + +} // namespace Carbon::Parse + +#endif // NDEBUG + +#endif // CARBON_TOOLCHAIN_PARSE_DUMP_H_