diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index 5f87b025b001..2043007a7f86 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -272,8 +272,10 @@ cc_library( "//toolchain/diagnostics:diagnostic_emitter", "//toolchain/lex:token_index", "//toolchain/parse:tree", + "//toolchain/sem_ir:absolute_node_id", "//toolchain/sem_ir:file", "//toolchain/sem_ir:stringify_type", + "//toolchain/sem_ir:typed_insts", "@llvm-project//llvm:Support", ], ) diff --git a/toolchain/check/check.h b/toolchain/check/check.h index 67b4922b4e92..1440fe7bbad9 100644 --- a/toolchain/check/check.h +++ b/toolchain/check/check.h @@ -23,8 +23,7 @@ struct Unit { Timings* timings; // Returns a lazily constructed TreeAndSubtrees. - llvm::function_ref - get_parse_tree_and_subtrees; + Parse::GetTreeAndSubtreesFn get_parse_tree_and_subtrees; // The unit's SemIR, provided as empty and filled in by CheckParseTrees. SemIR::File* sem_ir; diff --git a/toolchain/check/check_unit.h b/toolchain/check/check_unit.h index 44ad4bbf185e..dcca71f1f00d 100644 --- a/toolchain/check/check_unit.h +++ b/toolchain/check/check_unit.h @@ -48,8 +48,7 @@ class UnitAndImportsDiagnosticConverter : public DiagnosticConverter { public: explicit UnitAndImportsDiagnosticConverter( - llvm::function_ref - get_parse_tree_and_subtrees) + Parse::GetTreeAndSubtreesFn get_parse_tree_and_subtrees) : get_parse_tree_and_subtrees_(get_parse_tree_and_subtrees) {} auto ConvertLoc(Parse::NodeId node_id, ContextFnT /*context_fn*/) const @@ -59,8 +58,7 @@ class UnitAndImportsDiagnosticConverter } private: - llvm::function_ref - get_parse_tree_and_subtrees_; + Parse::GetTreeAndSubtreesFn get_parse_tree_and_subtrees_; }; // Contains information accumulated while checking a `Unit` (primarily import diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index be25dbd9fac0..5eba636ee82d 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -39,8 +39,7 @@ namespace Carbon::Check { Context::Context(DiagnosticEmitter* emitter, - llvm::function_ref - get_parse_tree_and_subtrees, + Parse::GetTreeAndSubtreesFn get_parse_tree_and_subtrees, SemIR::File* sem_ir, int imported_ir_count, int total_ir_count, llvm::raw_ostream* vlog_stream) : emitter_(emitter), diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 2b197bb5f38f..db095387b1d9 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -73,8 +73,7 @@ class Context { // Stores references for work. explicit Context(DiagnosticEmitter* emitter, - llvm::function_ref - get_parse_tree_and_subtrees, + Parse::GetTreeAndSubtreesFn get_parse_tree_and_subtrees, SemIR::File* sem_ir, int imported_ir_count, int total_ir_count, llvm::raw_ostream* vlog_stream); @@ -748,8 +747,7 @@ class Context { DiagnosticEmitter* emitter_; // Returns a lazily constructed TreeAndSubtrees. - llvm::function_ref - get_parse_tree_and_subtrees_; + Parse::GetTreeAndSubtreesFn get_parse_tree_and_subtrees_; // The SemIR::File being added to. SemIR::File* sem_ir_; diff --git a/toolchain/check/sem_ir_diagnostic_converter.cpp b/toolchain/check/sem_ir_diagnostic_converter.cpp index 73cf0b86d90d..14a336bd6dd9 100644 --- a/toolchain/check/sem_ir_diagnostic_converter.cpp +++ b/toolchain/check/sem_ir_diagnostic_converter.cpp @@ -5,6 +5,7 @@ #include "toolchain/check/sem_ir_diagnostic_converter.h" #include "common/raw_string_ostream.h" +#include "toolchain/sem_ir/absolute_node_id.h" #include "toolchain/sem_ir/stringify_type.h" namespace Carbon::Check { @@ -35,109 +36,34 @@ auto SemIRDiagnosticConverter::ConvertLoc(SemIRLoc loc, auto SemIRDiagnosticConverter::ConvertLocImpl(SemIRLoc loc, ContextFnT context_fn) const -> ConvertedDiagnosticLoc { - // Cursors for the current IR and instruction in that IR. - const auto* cursor_ir = sem_ir_; - auto cursor_inst_id = SemIR::InstId::None; + llvm::SmallVector absolute_node_ids = + loc.is_inst_id_ ? SemIR::GetAbsoluteNodeId(sem_ir_, loc.inst_id_) + : SemIR::GetAbsoluteNodeId(sem_ir_, loc.loc_id_); - // Notes an import on the diagnostic and updates cursors to point at the - // imported IR. - auto follow_import_ref = [&](SemIR::ImportIRInstId import_ir_inst_id) { - auto import_ir_inst = cursor_ir->import_ir_insts().Get(import_ir_inst_id); - const auto& import_ir = cursor_ir->import_irs().Get(import_ir_inst.ir_id); - CARBON_CHECK(import_ir.decl_id.has_value(), - "If we get `None` locations here, we may need to more " - "thoroughly track ImportDecls."); - - ConvertedDiagnosticLoc in_import_loc; - auto import_loc_id = cursor_ir->insts().GetLocId(import_ir.decl_id); - if (import_loc_id.is_node_id()) { - // For imports in the current file, the location is simple. - in_import_loc = ConvertLocInFile(cursor_ir, import_loc_id.node_id(), - loc.token_only_, context_fn); - } else if (import_loc_id.is_import_ir_inst_id()) { - // For implicit imports, we need to unravel the location a little - // further. - auto implicit_import_ir_inst = - cursor_ir->import_ir_insts().Get(import_loc_id.import_ir_inst_id()); - const auto& implicit_ir = - cursor_ir->import_irs().Get(implicit_import_ir_inst.ir_id); - auto implicit_loc_id = - implicit_ir.sem_ir->insts().GetLocId(implicit_import_ir_inst.inst_id); - CARBON_CHECK(implicit_loc_id.is_node_id(), - "Should only be one layer of implicit imports"); - in_import_loc = - ConvertLocInFile(implicit_ir.sem_ir, implicit_loc_id.node_id(), - loc.token_only_, context_fn); + auto final_node_id = absolute_node_ids.pop_back_val(); + for (const auto& absolute_node_id : absolute_node_ids) { + if (!absolute_node_id.node_id.has_value()) { + // TODO: Add an "In implicit import of prelude." note for the case where + // we don't have a location. + continue; } - - // TODO: Add an "In implicit import of prelude." note for the case where we - // don't have a location. - if (import_loc_id.has_value()) { - // TODO: Include the name of the imported library in the diagnostic. - CARBON_DIAGNOSTIC(InImport, LocationInfo, "in import"); - context_fn(in_import_loc.loc, InImport); - } - - cursor_ir = import_ir.sem_ir; - cursor_inst_id = import_ir_inst.inst_id; - }; - - // If the location is is an import, follows it and returns nullopt. - // Otherwise, it's a parse node, so return the final location. - auto handle_loc = - [&](SemIR::LocId loc_id) -> std::optional { - if (loc_id.is_import_ir_inst_id()) { - follow_import_ref(loc_id.import_ir_inst_id()); - return std::nullopt; - } else { - // Parse nodes always refer to the current IR. - return ConvertLocInFile(cursor_ir, loc_id.node_id(), loc.token_only_, - context_fn); - } - }; - - // Handle the base location. - if (loc.is_inst_id_) { - cursor_inst_id = loc.inst_id_; - } else { - if (auto diag_loc = handle_loc(loc.loc_id_)) { - return *diag_loc; - } - CARBON_CHECK(cursor_inst_id.has_value(), "Should have been set"); + // TODO: Include the name of the imported library in the diagnostic. + auto diag_loc = + ConvertLocInFile(absolute_node_id, loc.token_only_, context_fn); + CARBON_DIAGNOSTIC(InImport, LocationInfo, "in import"); + context_fn(diag_loc.loc, InImport); } - while (true) { - if (cursor_inst_id.has_value()) { - auto cursor_inst = cursor_ir->insts().Get(cursor_inst_id); - if (auto bind_ref = cursor_inst.TryAs(); - bind_ref && bind_ref->value_id.has_value()) { - cursor_inst_id = bind_ref->value_id; - continue; - } + return ConvertLocInFile(final_node_id, loc.token_only_, context_fn); +} - // If the parse node has a value, use it for the location. - if (auto loc_id = cursor_ir->insts().GetLocId(cursor_inst_id); - loc_id.has_value()) { - if (auto diag_loc = handle_loc(loc_id)) { - return *diag_loc; - } - continue; - } - - // If a namespace has an instruction for an import, switch to looking at - // it. - if (auto ns = cursor_inst.TryAs()) { - if (ns->import_id.has_value()) { - cursor_inst_id = ns->import_id; - continue; - } - } - } - - // `None` parse node but not an import; just nothing to point at. - return ConvertLocInFile(cursor_ir, Parse::NodeId::None, loc.token_only_, - context_fn); - } +auto SemIRDiagnosticConverter::ConvertLocInFile( + SemIR::AbsoluteNodeId absolute_node_id, bool token_only, + ContextFnT /*context_fn*/) const -> ConvertedDiagnosticLoc { + const auto& tree_and_subtrees = + imported_trees_and_subtrees_[absolute_node_id.check_ir_id.index](); + return tree_and_subtrees.NodeToDiagnosticLoc(absolute_node_id.node_id, + token_only); } auto SemIRDiagnosticConverter::ConvertArg(llvm::Any arg) const -> llvm::Any { @@ -195,14 +121,4 @@ auto SemIRDiagnosticConverter::ConvertArg(llvm::Any arg) const -> llvm::Any { return DiagnosticConverter::ConvertArg(arg); } -auto SemIRDiagnosticConverter::ConvertLocInFile(const SemIR::File* sem_ir, - Parse::NodeId node_id, - bool token_only, - ContextFnT /*context_fn*/) const - -> ConvertedDiagnosticLoc { - const auto& tree_and_subtrees = - imported_trees_and_subtrees_[sem_ir->check_ir_id().index](); - return tree_and_subtrees.NodeToDiagnosticLoc(node_id, token_only); -} - } // namespace Carbon::Check diff --git a/toolchain/check/sem_ir_diagnostic_converter.h b/toolchain/check/sem_ir_diagnostic_converter.h index ba865f8cedc4..1f540167cfe8 100644 --- a/toolchain/check/sem_ir_diagnostic_converter.h +++ b/toolchain/check/sem_ir_diagnostic_converter.h @@ -10,17 +10,17 @@ #include "toolchain/diagnostics/diagnostic_emitter.h" #include "toolchain/lex/token_index.h" #include "toolchain/parse/tree_and_subtrees.h" +#include "toolchain/sem_ir/absolute_node_id.h" #include "toolchain/sem_ir/file.h" +#include "toolchain/sem_ir/ids.h" namespace Carbon::Check { // Handles the transformation of a SemIRLoc to a DiagnosticLoc. class SemIRDiagnosticConverter : public DiagnosticConverter { public: - using TreeFnT = llvm::function_ref; - explicit SemIRDiagnosticConverter( - llvm::ArrayRef imported_trees_and_subtrees, + llvm::ArrayRef imported_trees_and_subtrees, const SemIR::File* sem_ir) : imported_trees_and_subtrees_(imported_trees_and_subtrees), sem_ir_(sem_ir) {} @@ -50,12 +50,11 @@ class SemIRDiagnosticConverter : public DiagnosticConverter { // Converts a node_id corresponding to a specific sem_ir to a diagnostic // location. - auto ConvertLocInFile(const SemIR::File* sem_ir, Parse::NodeId node_id, - bool token_only, ContextFnT context_fn) const - -> ConvertedDiagnosticLoc; + auto ConvertLocInFile(SemIR::AbsoluteNodeId absolute_node_id, bool token_only, + ContextFnT context_fn) const -> ConvertedDiagnosticLoc; // Converters for each SemIR. - llvm::ArrayRef imported_trees_and_subtrees_; + llvm::ArrayRef imported_trees_and_subtrees_; // The current SemIR being processed. const SemIR::File* sem_ir_; diff --git a/toolchain/driver/compile_subcommand.cpp b/toolchain/driver/compile_subcommand.cpp index f228db0ad0fa..7775168183a6 100644 --- a/toolchain/driver/compile_subcommand.cpp +++ b/toolchain/driver/compile_subcommand.cpp @@ -335,19 +335,20 @@ class CompilationUnit { // Prepares per-IR lazy fetch functions which may come up in cross-IR // diagnostics. - auto PreCheck() -> llvm::function_ref; + auto PreCheck() -> Parse::GetTreeAndSubtreesFn; // Returns information needed to check this unit. auto GetCheckUnit( SemIR::CheckIRId check_ir_id, - llvm::ArrayRef> - all_trees_and_subtrees) -> Check::Unit; + llvm::ArrayRef all_trees_and_subtrees) + -> Check::Unit; // Runs post-check logic. Returns true if checking succeeded for the IR. auto PostCheck() -> void; // Lower SemIR to LLVM IR. - auto RunLower() -> void; + auto RunLower(std::optional> + all_trees_and_subtrees_for_debug_info) -> void; auto RunCodeGen() -> void; @@ -499,8 +500,7 @@ auto CompilationUnit::RunParse() -> void { } } -auto CompilationUnit::PreCheck() - -> llvm::function_ref { +auto CompilationUnit::PreCheck() -> Parse::GetTreeAndSubtreesFn { CARBON_CHECK(parse_tree_, "Must call RunParse first"); CARBON_CHECK(!get_parse_tree_and_subtrees_, "Called PreCheck twice"); @@ -512,8 +512,8 @@ auto CompilationUnit::PreCheck() auto CompilationUnit::GetCheckUnit( SemIR::CheckIRId check_ir_id, - llvm::ArrayRef> - all_trees_and_subtrees) -> Check::Unit { + llvm::ArrayRef all_trees_and_subtrees) + -> Check::Unit { CARBON_CHECK(get_parse_tree_and_subtrees_, "Must call PreCheck first"); CARBON_CHECK(!sem_ir_converter_, "Called GetCheckUnit twice"); @@ -587,15 +587,17 @@ auto CompilationUnit::PostCheck() -> void { } } -auto CompilationUnit::RunLower() -> void { +auto CompilationUnit::RunLower( + std::optional> + all_trees_and_subtrees_for_debug_info) -> void { LogCall("Lower::LowerToLLVM", "lower", [&] { llvm_context_ = std::make_unique(); // TODO: Consider disabling instruction naming by default if we're not // producing textual LLVM IR. SemIR::InstNamer inst_namer(&*sem_ir_); - module_ = Lower::LowerToLLVM(*llvm_context_, options_.include_debug_info, - *sem_ir_converter_, input_filename_, *sem_ir_, - &inst_namer, vlog_stream_); + module_ = Lower::LowerToLLVM( + *llvm_context_, all_trees_and_subtrees_for_debug_info, input_filename_, + *sem_ir_, &inst_namer, vlog_stream_); }); if (vlog_stream_) { CARBON_VLOG("*** llvm::Module ***\n"); @@ -842,8 +844,7 @@ auto CompileSubcommand::Run(DriverEnv& driver_env) -> DriverResult { } // Pre-check assigns IR IDs and constructs node converters. - llvm::SmallVector> - all_trees_and_subtrees; + llvm::SmallVector all_trees_and_subtrees; // This size may not match due to units that are missing source, but that's an // error case and not worth extra work. all_trees_and_subtrees.reserve(units.size()); @@ -887,8 +888,13 @@ auto CompileSubcommand::Run(DriverEnv& driver_env) -> DriverResult { } // Lower. + std::optional> + all_trees_and_subtrees_for_debug_info; + if (options_.include_debug_info) { + all_trees_and_subtrees_for_debug_info = all_trees_and_subtrees; + } for (const auto& unit : units) { - unit->RunLower(); + unit->RunLower(all_trees_and_subtrees_for_debug_info); } if (options_.phase == CompileOptions::Phase::Lower) { return make_result(); diff --git a/toolchain/lower/BUILD b/toolchain/lower/BUILD index c9445b2c3362..285513b13793 100644 --- a/toolchain/lower/BUILD +++ b/toolchain/lower/BUILD @@ -18,6 +18,7 @@ cc_library( deps = [ ":context", "//toolchain/check:sem_ir_diagnostic_converter", + "//toolchain/parse:tree", "//toolchain/sem_ir:file", "//toolchain/sem_ir:inst_namer", "@llvm-project//llvm:Core", @@ -50,6 +51,7 @@ cc_library( "//common:vlog", "//toolchain/base:kind_switch", "//toolchain/check:sem_ir_diagnostic_converter", + "//toolchain/sem_ir:absolute_node_id", "//toolchain/sem_ir:entry_point", "//toolchain/sem_ir:file", "//toolchain/sem_ir:inst", diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index d4187d2df0e1..22e79e19e83d 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -12,6 +12,7 @@ #include "toolchain/lower/constant.h" #include "toolchain/lower/function_context.h" #include "toolchain/lower/mangler.h" +#include "toolchain/sem_ir/absolute_node_id.h" #include "toolchain/sem_ir/entry_point.h" #include "toolchain/sem_ir/file.h" #include "toolchain/sem_ir/function.h" @@ -22,20 +23,21 @@ namespace Carbon::Lower { -FileContext::FileContext(llvm::LLVMContext& llvm_context, - bool include_debug_info, - const Check::SemIRDiagnosticConverter& converter, - llvm::StringRef module_name, const SemIR::File& sem_ir, - const SemIR::InstNamer* inst_namer, - llvm::raw_ostream* vlog_stream) +FileContext::FileContext( + llvm::LLVMContext& llvm_context, + std::optional> + all_trees_and_subtrees_for_debug_info, + llvm::StringRef module_name, const SemIR::File& sem_ir, + const SemIR::InstNamer* inst_namer, llvm::raw_ostream* vlog_stream) : llvm_context_(&llvm_context), llvm_module_(std::make_unique(module_name, llvm_context)), di_builder_(*llvm_module_), di_compile_unit_( - include_debug_info + all_trees_and_subtrees_for_debug_info ? BuildDICompileUnit(module_name, *llvm_module_, di_builder_) : nullptr), - converter_(converter), + all_trees_and_subtrees_for_debug_info_( + all_trees_and_subtrees_for_debug_info), sem_ir_(&sem_ir), inst_namer_(inst_namer), vlog_stream_(vlog_stream) { @@ -617,13 +619,21 @@ auto FileContext::BuildGlobalVariableDecl(SemIR::VarStorage var_storage) } auto FileContext::GetLocForDI(SemIR::InstId inst_id) -> LocForDI { - auto converted = converter_.ConvertLoc( - inst_id, [&](DiagnosticLoc /*context_loc*/, - const DiagnosticBase<>& /*context_diagnostic_base*/) {}); - const auto& loc = converted.loc; - return {.filename = loc.filename, - .line_number = loc.line_number == -1 ? 0 : loc.line_number, - .column_number = loc.column_number == -1 ? 0 : loc.column_number}; + SemIR::AbsoluteNodeId resolved = GetAbsoluteNodeId(sem_ir_, inst_id).back(); + const auto& tree_and_subtrees = + (*all_trees_and_subtrees_for_debug_info_)[resolved.check_ir_id.index](); + const auto& tokens = tree_and_subtrees.tree().tokens(); + + if (resolved.node_id.has_value()) { + auto token = tree_and_subtrees.GetSubtreeTokenRange(resolved.node_id).begin; + return {.filename = tokens.source().filename(), + .line_number = tokens.GetLineNumber(token), + .column_number = tokens.GetColumnNumber(token)}; + } else { + return {.filename = tokens.source().filename(), + .line_number = 0, + .column_number = 0}; + } } } // namespace Carbon::Lower diff --git a/toolchain/lower/file_context.h b/toolchain/lower/file_context.h index f2ee621358b1..79f9bae95011 100644 --- a/toolchain/lower/file_context.h +++ b/toolchain/lower/file_context.h @@ -27,11 +27,12 @@ class FileContext { int32_t column_number; }; - explicit FileContext(llvm::LLVMContext& llvm_context, bool include_debug_info, - const Check::SemIRDiagnosticConverter& converter, - llvm::StringRef module_name, const SemIR::File& sem_ir, - const SemIR::InstNamer* inst_namer, - llvm::raw_ostream* vlog_stream); + explicit FileContext( + llvm::LLVMContext& llvm_context, + std::optional> + all_trees_and_subtrees_for_debug_info, + llvm::StringRef module_name, const SemIR::File& sem_ir, + const SemIR::InstNamer* inst_namer, llvm::raw_ostream* vlog_stream); // Lowers the SemIR::File to LLVM IR. Should only be called once, and handles // the main execution loop. @@ -128,8 +129,9 @@ class FileContext { // The DICompileUnit, if any - null implies debug info is not being emitted. llvm::DICompileUnit* di_compile_unit_; - // The source location converter. - const Check::SemIRDiagnosticConverter& converter_; + // The trees are only provided when debug info should be emitted. + std::optional> + all_trees_and_subtrees_for_debug_info_; // The input SemIR. const SemIR::File* const sem_ir_; diff --git a/toolchain/lower/lower.cpp b/toolchain/lower/lower.cpp index 5f60b70e0d2d..d4d5accb6ee6 100644 --- a/toolchain/lower/lower.cpp +++ b/toolchain/lower/lower.cpp @@ -8,14 +8,15 @@ namespace Carbon::Lower { -auto LowerToLLVM(llvm::LLVMContext& llvm_context, bool include_debug_info, - const Check::SemIRDiagnosticConverter& converter, +auto LowerToLLVM(llvm::LLVMContext& llvm_context, + std::optional> + all_trees_and_subtrees_for_debug_info, llvm::StringRef module_name, const SemIR::File& sem_ir, const SemIR::InstNamer* inst_namer, llvm::raw_ostream* vlog_stream) -> std::unique_ptr { - FileContext context(llvm_context, include_debug_info, converter, module_name, - sem_ir, inst_namer, vlog_stream); + FileContext context(llvm_context, all_trees_and_subtrees_for_debug_info, + module_name, sem_ir, inst_namer, vlog_stream); return context.Run(); } diff --git a/toolchain/lower/lower.h b/toolchain/lower/lower.h index b01fd7a39b89..b651528e00fb 100644 --- a/toolchain/lower/lower.h +++ b/toolchain/lower/lower.h @@ -5,17 +5,19 @@ #ifndef CARBON_TOOLCHAIN_LOWER_LOWER_H_ #define CARBON_TOOLCHAIN_LOWER_LOWER_H_ +#include "llvm/ADT/ArrayRef.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" -#include "toolchain/check/sem_ir_diagnostic_converter.h" +#include "toolchain/parse/tree_and_subtrees.h" #include "toolchain/sem_ir/file.h" #include "toolchain/sem_ir/inst_namer.h" namespace Carbon::Lower { // Lowers SemIR to LLVM IR. -auto LowerToLLVM(llvm::LLVMContext& llvm_context, bool include_debug_info, - const Check::SemIRDiagnosticConverter& converter, +auto LowerToLLVM(llvm::LLVMContext& llvm_context, + std::optional> + all_trees_and_subtrees_for_debug_info, llvm::StringRef module_name, const SemIR::File& sem_ir, const SemIR::InstNamer* inst_namer, llvm::raw_ostream* vlog_stream) diff --git a/toolchain/parse/BUILD b/toolchain/parse/BUILD index f7c7b5f5a320..8a8cb9dcf3a6 100644 --- a/toolchain/parse/BUILD +++ b/toolchain/parse/BUILD @@ -143,6 +143,7 @@ cc_library( "//common:ostream", "//common:struct_reflection", "//toolchain/base:value_store", + "//toolchain/lex:token_index", "//toolchain/lex:tokenized_buffer", "@llvm-project//llvm:Support", ], diff --git a/toolchain/parse/tree_and_subtrees.cpp b/toolchain/parse/tree_and_subtrees.cpp index 728804036772..d4d55c0d3482 100644 --- a/toolchain/parse/tree_and_subtrees.cpp +++ b/toolchain/parse/tree_and_subtrees.cpp @@ -4,6 +4,8 @@ #include "toolchain/parse/tree_and_subtrees.h" +#include "toolchain/lex/token_index.h" + namespace Carbon::Parse { TreeAndSubtrees::TreeAndSubtrees(const Lex::TokenizedBuffer& tokens, @@ -110,10 +112,10 @@ auto TreeAndSubtrees::Verify() const -> ErrorOr { auto TreeAndSubtrees::postorder(NodeId n) const -> llvm::iterator_range { - // The postorder ends after this node, the root, and begins at the start of + // The postorder ends after this node, the root, and begins at the begin of // its subtree. - int start_index = n.index - subtree_sizes_[n.index] + 1; - return Tree::PostorderIterator::MakeRange(NodeId(start_index), n); + int begin_index = n.index - subtree_sizes_[n.index] + 1; + return Tree::PostorderIterator::MakeRange(NodeId(begin_index), n); } auto TreeAndSubtrees::children(NodeId n) const @@ -239,6 +241,24 @@ auto TreeAndSubtrees::CollectMemUsage(MemUsage& mem_usage, subtree_sizes_); } +auto TreeAndSubtrees::GetSubtreeTokenRange(NodeId node_id) const -> TokenRange { + TokenRange range = {.begin = tree_->node_token(node_id), + .end = Lex::TokenIndex::None}; + range.end = range.begin; + for (NodeId desc : postorder(node_id)) { + Lex::TokenIndex desc_token = tree_->node_token(desc); + if (!desc_token.has_value()) { + continue; + } + if (desc_token < range.begin) { + range.begin = desc_token; + } else if (desc_token > range.end) { + range.end = desc_token; + } + } + return range; +} + auto TreeAndSubtrees::NodeToDiagnosticLoc(NodeId node_id, bool token_only) const -> ConvertedDiagnosticLoc { // Support the invalid token as a way to emit only the filename, when there @@ -253,37 +273,25 @@ auto TreeAndSubtrees::NodeToDiagnosticLoc(NodeId node_id, bool token_only) const // Construct a location that encompasses all tokens that descend from this // node (including the root). - Lex::TokenIndex start_token = tree_->node_token(node_id); - Lex::TokenIndex end_token = start_token; - for (NodeId desc : postorder(node_id)) { - Lex::TokenIndex desc_token = tree_->node_token(desc); - if (!desc_token.has_value()) { - continue; - } - if (desc_token < start_token) { - start_token = desc_token; - } else if (desc_token > end_token) { - end_token = desc_token; - } + TokenRange token_range = GetSubtreeTokenRange(node_id); + auto begin_loc = tree_->tokens().TokenToDiagnosticLoc(token_range.begin); + if (token_range.begin == token_range.end) { + return begin_loc; } - auto start_loc = tree_->tokens().TokenToDiagnosticLoc(start_token); - if (start_token == end_token) { - return start_loc; - } - auto end_loc = tree_->tokens().TokenToDiagnosticLoc(end_token); - start_loc.last_byte_offset = end_loc.last_byte_offset; + auto end_loc = tree_->tokens().TokenToDiagnosticLoc(token_range.end); + begin_loc.last_byte_offset = end_loc.last_byte_offset; // For multiline locations we simply return the rest of the line for now // since true multiline locations are not yet supported. - if (start_loc.loc.line_number != end_loc.loc.line_number) { - start_loc.loc.length = - start_loc.loc.line.size() - start_loc.loc.column_number + 1; + if (begin_loc.loc.line_number != end_loc.loc.line_number) { + begin_loc.loc.length = + begin_loc.loc.line.size() - begin_loc.loc.column_number + 1; } else { - if (start_loc.loc.column_number != end_loc.loc.column_number) { - start_loc.loc.length = end_loc.loc.column_number + end_loc.loc.length - - start_loc.loc.column_number; + if (begin_loc.loc.column_number != end_loc.loc.column_number) { + begin_loc.loc.length = end_loc.loc.column_number + end_loc.loc.length - + begin_loc.loc.column_number; } } - return start_loc; + return begin_loc; } auto TreeAndSubtrees::SiblingIterator::Print(llvm::raw_ostream& output) const diff --git a/toolchain/parse/tree_and_subtrees.h b/toolchain/parse/tree_and_subtrees.h index 8b22eacddd7b..10f1447e7f1c 100644 --- a/toolchain/parse/tree_and_subtrees.h +++ b/toolchain/parse/tree_and_subtrees.h @@ -6,6 +6,7 @@ #define CARBON_TOOLCHAIN_PARSE_TREE_AND_SUBTREES_H_ #include "llvm/ADT/SmallVector.h" +#include "toolchain/lex/token_index.h" #include "toolchain/parse/tree.h" namespace Carbon::Parse { @@ -16,6 +17,12 @@ namespace Carbon::Parse { // This requires a complete tree. class TreeAndSubtrees { public: + // A range of tokens, returned by GetSubtreeTokenRange. + struct TokenRange { + Lex::TokenIndex begin; + Lex::TokenIndex end; + }; + class SiblingIterator; explicit TreeAndSubtrees(const Lex::TokenizedBuffer& tokens, @@ -107,6 +114,9 @@ class TreeAndSubtrees { auto CollectMemUsage(MemUsage& mem_usage, llvm::StringRef label) const -> void; + // Returns the range of tokens in the node's subtree. + auto GetSubtreeTokenRange(NodeId node_id) const -> TokenRange; + // Converts the node to a diagnostic location, covering either the full // subtree or only the token. auto NodeToDiagnosticLoc(NodeId node_id, bool token_only) const @@ -183,6 +193,9 @@ class TreeAndSubtrees { llvm::SmallVector subtree_sizes_; }; +// A standard signature for a callback to support lazy construction. +using GetTreeAndSubtreesFn = llvm::function_ref; + // A forward iterator across the siblings at a particular level in the parse // tree. It produces `Tree::NodeId` objects which are opaque handles and must // be used in conjunction with the `Tree` itself. diff --git a/toolchain/sem_ir/BUILD b/toolchain/sem_ir/BUILD index a00deff83c50..0f7088a1398b 100644 --- a/toolchain/sem_ir/BUILD +++ b/toolchain/sem_ir/BUILD @@ -194,6 +194,18 @@ cc_library( ], ) +cc_library( + name = "absolute_node_id", + srcs = ["absolute_node_id.cpp"], + hdrs = ["absolute_node_id.h"], + deps = [ + ":file", + ":typed_insts", + "//toolchain/parse:tree", + "@llvm-project//llvm:Support", + ], +) + cc_library( name = "dump", srcs = ["dump.cpp"], diff --git a/toolchain/sem_ir/absolute_node_id.cpp b/toolchain/sem_ir/absolute_node_id.cpp new file mode 100644 index 000000000000..be4d5cab906c --- /dev/null +++ b/toolchain/sem_ir/absolute_node_id.cpp @@ -0,0 +1,129 @@ +// 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 + +#include "toolchain/sem_ir/absolute_node_id.h" + +#include "toolchain/sem_ir/ids.h" + +namespace Carbon::SemIR { + +// Notes an import on the diagnostic and updates cursors to point at the +// imported IR. +static auto FollowImportRef( + llvm::SmallVector& absolute_node_ids, + const File*& cursor_ir, InstId& cursor_inst_id, + ImportIRInstId import_ir_inst_id) -> void { + auto import_ir_inst = cursor_ir->import_ir_insts().Get(import_ir_inst_id); + const auto& import_ir = cursor_ir->import_irs().Get(import_ir_inst.ir_id); + CARBON_CHECK(import_ir.decl_id.has_value(), + "If we get `None` locations here, we may need to more " + "thoroughly track ImportDecls."); + + auto import_loc_id = cursor_ir->insts().GetLocId(import_ir.decl_id); + if (import_loc_id.is_node_id()) { + // For imports in the current file, the location is simple. + absolute_node_ids.push_back({.check_ir_id = cursor_ir->check_ir_id(), + .node_id = import_loc_id.node_id()}); + } else if (import_loc_id.is_import_ir_inst_id()) { + // For implicit imports, we need to unravel the location a little + // further. + auto implicit_import_ir_inst = + cursor_ir->import_ir_insts().Get(import_loc_id.import_ir_inst_id()); + const auto& implicit_ir = + cursor_ir->import_irs().Get(implicit_import_ir_inst.ir_id); + auto implicit_loc_id = + implicit_ir.sem_ir->insts().GetLocId(implicit_import_ir_inst.inst_id); + CARBON_CHECK(implicit_loc_id.is_node_id(), + "Should only be one layer of implicit imports"); + absolute_node_ids.push_back( + {.check_ir_id = implicit_ir.sem_ir->check_ir_id(), + .node_id = implicit_loc_id.node_id()}); + } + + cursor_ir = import_ir.sem_ir; + cursor_inst_id = import_ir_inst.inst_id; +} + +// Returns true if this is the final parse node location. If the location is is +// an import, follows it and returns false. +static auto HandleLocId(llvm::SmallVector& absolute_node_ids, + const File*& cursor_ir, InstId& cursor_inst_id, + LocId loc_id) -> bool { + if (loc_id.is_import_ir_inst_id()) { + FollowImportRef(absolute_node_ids, cursor_ir, cursor_inst_id, + loc_id.import_ir_inst_id()); + return false; + } else { + // Parse nodes always refer to the current IR. + absolute_node_ids.push_back( + {.check_ir_id = cursor_ir->check_ir_id(), .node_id = loc_id.node_id()}); + return true; + } +} + +// Loops through imported instructions until the actual instruction is found. +static auto GetAbsoluteNodeIdImpl( + llvm::SmallVector& absolute_node_ids, const File* cursor_ir, + InstId cursor_inst_id) -> void { + while (true) { + if (cursor_inst_id.has_value()) { + auto cursor_inst = cursor_ir->insts().Get(cursor_inst_id); + if (auto bind_ref = cursor_inst.TryAs(); + bind_ref && bind_ref->value_id.has_value()) { + cursor_inst_id = bind_ref->value_id; + continue; + } + + // If the parse node has a value, use it for the location. + if (auto loc_id = cursor_ir->insts().GetLocId(cursor_inst_id); + loc_id.has_value()) { + if (HandleLocId(absolute_node_ids, cursor_ir, cursor_inst_id, loc_id)) { + return; + } + continue; + } + + // If a namespace has an instruction for an import, switch to looking at + // it. + if (auto ns = cursor_inst.TryAs()) { + if (ns->import_id.has_value()) { + cursor_inst_id = ns->import_id; + continue; + } + } + } + + // `None` parse node but not an import; just nothing to point at. + absolute_node_ids.push_back({.check_ir_id = cursor_ir->check_ir_id(), + .node_id = Parse::NodeId::None}); + return; + } +} + +auto GetAbsoluteNodeId(const File* sem_ir, InstId inst_id) + -> llvm::SmallVector { + llvm::SmallVector absolute_node_ids; + GetAbsoluteNodeIdImpl(absolute_node_ids, sem_ir, inst_id); + return absolute_node_ids; +} + +auto GetAbsoluteNodeId(const File* sem_ir, LocId loc_id) + -> llvm::SmallVector { + llvm::SmallVector absolute_node_ids; + if (!loc_id.has_value()) { + absolute_node_ids.push_back( + {.check_ir_id = sem_ir->check_ir_id(), .node_id = Parse::NodeId::None}); + return absolute_node_ids; + } + const File* cursor_ir = sem_ir; + InstId cursor_inst_id = InstId::None; + if (HandleLocId(absolute_node_ids, cursor_ir, cursor_inst_id, loc_id)) { + return absolute_node_ids; + } + CARBON_CHECK(cursor_inst_id.has_value(), "Should be set by HandleLocId"); + GetAbsoluteNodeIdImpl(absolute_node_ids, cursor_ir, cursor_inst_id); + return absolute_node_ids; +} + +} // namespace Carbon::SemIR diff --git a/toolchain/sem_ir/absolute_node_id.h b/toolchain/sem_ir/absolute_node_id.h new file mode 100644 index 000000000000..95591a134259 --- /dev/null +++ b/toolchain/sem_ir/absolute_node_id.h @@ -0,0 +1,33 @@ +// 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 CARBON_TOOLCHAIN_SEM_IR_ABSOLUTE_NODE_ID_H_ +#define CARBON_TOOLCHAIN_SEM_IR_ABSOLUTE_NODE_ID_H_ + +#include "toolchain/parse/tree_and_subtrees.h" +#include "toolchain/sem_ir/file.h" +#include "toolchain/sem_ir/ids.h" + +namespace Carbon::SemIR { + +// A specific node location in a file. +struct AbsoluteNodeId { + CheckIRId check_ir_id; + Parse::NodeId node_id; +}; + +// Resolves the `InstId` to a series of `NodeId`s, which may be in different +// files. The vector will have one entry if there were no imports, and multiple +// entries when imports are traversed. The final entry is the actual +// declaration. +auto GetAbsoluteNodeId(const File* sem_ir, InstId inst_id) + -> llvm::SmallVector; + +// Similar to to above overload, but starting at a `LocId`. +auto GetAbsoluteNodeId(const File* sem_ir, LocId loc_id) + -> llvm::SmallVector; + +} // namespace Carbon::SemIR + +#endif // CARBON_TOOLCHAIN_SEM_IR_ABSOLUTE_NODE_ID_H_