Make items that stem from decl context have a list of children item ids and generate code recursively
After this cl:
* We use a new method `Importer::GetItemIdsInSourceOrder()` to obtain a list of their children items (including comments and unsupported items, excluding children `decl`s whose `ImportDecl()` returned `std::nullopt`, as they don't have a corresponding item.)
* We generate source for items recursively, starting from `flat_ir.top_level_item_ids`.
* This required some changes to `generate_record()`, as it used to not be responsible for generating (unsupported) items for its own children, and now it is.
* Comments: We store all the comments, so we can find the relevant comments for each decl context.
* We now generate free comments that appear within a `Record`; however, comments between fields now appear out of place, after the struct definition. I will address this in a followup cl.
PiperOrigin-RevId: 440906345
diff --git a/rs_bindings_from_cc/importer.cc b/rs_bindings_from_cc/importer.cc
index c06fcbf..2a50fba 100644
--- a/rs_bindings_from_cc/importer.cc
+++ b/rs_bindings_from_cc/importer.cc
@@ -48,6 +48,7 @@
#include "third_party/llvm/llvm-project/clang/include/clang/Basic/Specifiers.h"
#include "third_party/llvm/llvm-project/clang/include/clang/Sema/Sema.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/Optional.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/STLExtras.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/SmallPtrSet.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/Casting.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/ErrorHandling.h"
@@ -188,54 +189,6 @@
} // namespace
-std::vector<clang::RawComment*> Importer::ImportFreeComments() {
- clang::SourceManager& sm = ctx_.getSourceManager();
-
- // We put all comments into an ordered set in source order. Later we'll remove
- // the comments that we don't want or that we get by other means.
- auto source_order = [&sm](const clang::SourceLocation& a,
- const clang::SourceLocation& b) {
- return b.isValid() && (a.isInvalid() || sm.isBeforeInTranslationUnit(a, b));
- };
- auto ordered_comments = std::map<clang::SourceLocation, clang::RawComment*,
- decltype(source_order)>(source_order);
-
- // We start off by getting the comments from all entry header files...
- for (const auto& header : invocation_.entry_headers_) {
- if (auto file = sm.getFileManager().getFile(header.IncludePath())) {
- if (auto comments = ctx_.Comments.getCommentsInFile(
- sm.getOrCreateFileID(*file, clang::SrcMgr::C_User))) {
- for (const auto& [_, comment] : *comments) {
- ordered_comments.insert({comment->getBeginLoc(), comment});
- }
- }
- }
- }
-
- // ... and then we remove those that "conflict" with an IR item.
- for (const auto& [decl, result] : import_cache_) {
- if (result.has_value()) {
- // Remove doc comments of imported items.
- if (auto raw_comment = ctx_.getRawCommentForDeclNoCache(decl)) {
- ordered_comments.erase(raw_comment->getBeginLoc());
- }
- // Remove comments that are within a visited decl.
- // TODO(forster): We should retain floating comments in decls like
- // records and namespaces.
- ordered_comments.erase(ordered_comments.lower_bound(decl->getBeginLoc()),
- ordered_comments.upper_bound(decl->getEndLoc()));
- }
- }
-
- // Return the remaining comments as a `std::vector`.
- std::vector<clang::RawComment*> result;
- result.reserve(ordered_comments.size());
- for (auto& [_, comment] : ordered_comments) {
- result.push_back(comment);
- }
- return result;
-}
-
// Multiple IR items can be associated with the same source location (e.g. the
// implicitly defined constructors and assignment operators). To produce
// deterministic output, we order such items based on GetDeclOrder. The order
@@ -267,12 +220,30 @@
return 999;
}
-void Importer::Import(clang::TranslationUnitDecl* translation_unit_decl) {
- ImportDeclsFromDeclContext(translation_unit_decl);
+class SourceLocationComparator {
+ public:
+ const bool operator()(const clang::SourceLocation& a,
+ const clang::SourceLocation& b) const {
+ return b.isValid() && a.isValid() && sm.isBeforeInTranslationUnit(a, b);
+ }
+ const bool operator()(const clang::RawComment* a,
+ const clang::SourceLocation& b) const {
+ return this->operator()(a->getBeginLoc(), b);
+ }
+ const bool operator()(const clang::SourceLocation& a,
+ const clang::RawComment* b) const {
+ return this->operator()(a, b->getBeginLoc());
+ }
+ const bool operator()(const clang::RawComment* a,
+ const clang::RawComment* b) const {
+ return this->operator()(a->getBeginLoc(), b->getBeginLoc());
+ }
+ using OrderedItemId = std::tuple<clang::SourceRange, int, ItemId>;
using OrderedItem = std::tuple<clang::SourceRange, int, IR::Item>;
- clang::SourceManager& sm = ctx_.getSourceManager();
- auto is_less_than = [&sm](const OrderedItem& a, const OrderedItem& b) {
+
+ template <typename OrderedItemOrId>
+ bool operator()(const OrderedItemOrId& a, const OrderedItemOrId& b) const {
auto a_range = std::get<0>(a);
auto b_range = std::get<0>(b);
if (!a_range.isValid() || !b_range.isValid()) {
@@ -287,82 +258,138 @@
return sm.isBeforeInTranslationUnit(a_range.getEnd(), b_range.getEnd());
}
}
-
auto a_decl_order = std::get<1>(a);
auto b_decl_order = std::get<1>(b);
- if (a_decl_order != b_decl_order) return a_decl_order < b_decl_order;
+ return a_decl_order < b_decl_order;
+ }
- // A single FunctionDecl can be associated with multiple UnsupportedItems.
- // Comparing the fields allows deterministic order between items like:
- // Non-trivial_abi type '...' is not supported by value as a parameter.
- // Non-trivial_abi type '...' is not supported by value as a return type.
- const auto& a_variant = std::get<2>(a);
- const auto& b_variant = std::get<2>(b);
- const auto* a_unsupported = std::get_if<UnsupportedItem>(&a_variant);
- const auto* b_unsupported = std::get_if<UnsupportedItem>(&b_variant);
- if (a_unsupported && b_unsupported) {
- if (a_unsupported->name != b_unsupported->name)
- return a_unsupported->name < b_unsupported->name;
- return a_unsupported->message < b_unsupported->message;
+ SourceLocationComparator(clang::SourceManager& sm) : sm(sm) {}
+
+ private:
+ clang::SourceManager& sm;
+};
+
+std::vector<ItemId> Importer::GetItemIdsInSourceOrder(
+ clang::Decl* parent_decl) {
+ auto decl_context = clang::cast<clang::DeclContext>(parent_decl);
+
+ clang::SourceManager& sm = ctx_.getSourceManager();
+ std::vector<SourceLocationComparator::OrderedItemId> items;
+ auto compare_locations = SourceLocationComparator(sm);
+
+ // We are only interested in comments within this decl context.
+ std::vector<const clang::RawComment*> comments_in_range(
+ llvm::lower_bound(comments_, parent_decl->getBeginLoc(),
+ compare_locations),
+ llvm::upper_bound(comments_, parent_decl->getEndLoc(),
+ compare_locations));
+
+ std::map<clang::SourceLocation, const clang::RawComment*,
+ SourceLocationComparator>
+ ordered_comments(compare_locations);
+ for (auto& comment : comments_in_range) {
+ ordered_comments.insert({comment->getBeginLoc(), comment});
+ }
+
+ absl::flat_hash_set<ItemId> visited_item_ids;
+ for (auto child : decl_context->decls()) {
+ auto decl = child->getCanonicalDecl();
+ if (!IsFromCurrentTarget(decl)) continue;
+
+ // We remove comments attached to a child decl or that are within a child
+ // decl.
+ if (auto raw_comment = ctx_.getRawCommentForDeclNoCache(decl)) {
+ ordered_comments.erase(raw_comment->getBeginLoc());
}
+ ordered_comments.erase(ordered_comments.lower_bound(decl->getBeginLoc()),
+ ordered_comments.upper_bound(decl->getEndLoc()));
- return false;
- };
- auto are_equal = [&is_less_than](const OrderedItem& a, const OrderedItem& b) {
- return !is_less_than(a, b) && !is_less_than(b, a);
- };
+ // We assume that all the decls of the given parameter decl are already
+ // imported. Some calls to ImportDecl() may have returned std::nullopt, and
+ // those decls don't have corresponding items in the ir, so we don't add
+ // their item id to the list.
+ auto item = import_cache_.find(decl);
+ CRUBIT_CHECK(item != import_cache_.end() && "Found non-imported decl");
+ if (!item->second.has_value()) {
+ continue;
+ }
+ auto item_id = GenerateItemId(decl);
+ // TODO(rosica): Drop this check when we start importing also other redecls,
+ // not just the canonical
+ if (visited_item_ids.find(item_id) == visited_item_ids.end()) {
+ visited_item_ids.insert(item_id);
+ items.push_back({decl->getSourceRange(), GetDeclOrder(decl), item_id});
+ }
+ }
- // We emit IR items in the order of the decls they were generated for.
- // For decls that emit multiple items we use a stable, but arbitrary order.
- std::vector<OrderedItem> items;
+ for (auto& [_, comment] : ordered_comments) {
+ items.push_back({comment->getSourceRange(), 0, GenerateItemId(comment)});
+ }
+ std::sort(items.begin(), items.end(), compare_locations);
+
+ std::vector<ItemId> ordered_item_ids;
+ ordered_item_ids.reserve(items.size());
+ for (auto& ordered_item : items) {
+ ordered_item_ids.push_back(std::get<2>(ordered_item));
+ }
+ return ordered_item_ids;
+}
+
+void Importer::ImportFreeComments() {
+ clang::SourceManager& sm = ctx_.getSourceManager();
+ for (const auto& header : invocation_.entry_headers_) {
+ if (auto file = sm.getFileManager().getFile(header.IncludePath())) {
+ if (auto comments_in_file = ctx_.Comments.getCommentsInFile(
+ sm.getOrCreateFileID(*file, clang::SrcMgr::C_User))) {
+ for (const auto& [_, comment] : *comments_in_file) {
+ comments_.push_back(comment);
+ }
+ }
+ }
+ }
+ std::sort(comments_.begin(), comments_.end(), SourceLocationComparator(sm));
+}
+
+void Importer::Import(clang::TranslationUnitDecl* translation_unit_decl) {
+ ImportFreeComments();
+ clang::SourceManager& sm = ctx_.getSourceManager();
+ std::vector<SourceLocationComparator::OrderedItem> ordered_items;
+
+ for (auto& comment : comments_) {
+ ordered_items.push_back(
+ {comment->getSourceRange(), 0,
+ Comment{.text = comment->getFormattedText(sm, sm.getDiagnostics()),
+ .id = GenerateItemId(comment)}});
+ }
+
+ ImportDeclsFromDeclContext(translation_unit_decl);
for (const auto& [decl, item] : import_cache_) {
if (item.has_value()) {
if (std::holds_alternative<UnsupportedItem>(*item) &&
!IsFromCurrentTarget(decl)) {
continue;
}
-
- items.push_back(
- std::make_tuple(decl->getSourceRange(), GetDeclOrder(decl), *item));
+ ordered_items.push_back(
+ {decl->getSourceRange(), GetDeclOrder(decl), *item});
}
}
- for (auto comment : ImportFreeComments()) {
- items.push_back(std::make_tuple(
- comment->getSourceRange(), 0 /* decl_order */,
- Comment{.text = comment->getFormattedText(sm, sm.getDiagnostics()),
- .id = GenerateItemId(comment)}));
- }
- std::sort(items.begin(), items.end(), is_less_than);
+ std::sort(ordered_items.begin(), ordered_items.end(),
+ SourceLocationComparator(sm));
- for (size_t i = 0; i < items.size(); i++) {
- const auto& item = items[i];
- if (i > 0) {
- const auto& prev = items[i - 1];
- if (are_equal(item, prev)) {
- llvm::json::Value prev_json = std::visit(
- [&](auto&& item) { return item.ToJson(); }, std::get<2>(prev));
- llvm::json::Value curr_json = std::visit(
- [&](auto&& item) { return item.ToJson(); }, std::get<2>(item));
- if (prev_json != curr_json) {
- llvm::report_fatal_error(
- llvm::formatv("Non-deterministic order of IR items: {0} -VS- {1}",
- prev_json, curr_json));
- } else {
- // TODO(lukasza): Avoid generating duplicate IR items. Currently
- // known example: UnsupportedItem: name=std::signbit; message=
- // Items contained in namespaces are not supported yet.
- continue;
- }
- }
- }
- invocation_.ir_.items.push_back(std::get<2>(item));
+ invocation_.ir_.items.reserve(ordered_items.size());
+ for (auto& ordered_item : ordered_items) {
+ invocation_.ir_.items.push_back(std::get<2>(ordered_item));
}
+ invocation_.ir_.top_level_item_ids =
+ GetItemIdsInSourceOrder(translation_unit_decl);
}
void Importer::ImportDeclsFromDeclContext(
const clang::DeclContext* decl_context) {
for (auto decl : decl_context->decls()) {
+ // TODO(rosica): We don't always want the canonical decl here (especially
+ // not in namespaces).
ImportDeclIfNeeded(decl->getCanonicalDecl());
}
}
@@ -382,7 +409,8 @@
return ImportFunction(function_decl);
} else if (auto* function_template_decl =
clang::dyn_cast<clang::FunctionTemplateDecl>(decl)) {
- return ImportFunction(function_template_decl->getTemplatedDecl());
+ return ImportFunction(function_template_decl->getTemplatedDecl(),
+ function_template_decl);
} else if (auto* record_decl = clang::dyn_cast<clang::CXXRecordDecl>(decl)) {
auto result = ImportRecord(record_decl);
// TODO(forster): Should we even visit the nested decl if we couldn't
@@ -403,11 +431,12 @@
}
std::optional<IR::Item> Importer::ImportFunction(
- clang::FunctionDecl* function_decl) {
+ clang::FunctionDecl* function_decl,
+ clang::FunctionTemplateDecl* function_template_decl) {
if (!IsFromCurrentTarget(function_decl)) return std::nullopt;
if (function_decl->isDeleted()) return std::nullopt;
if (function_decl->isTemplated()) {
- return ImportUnsupportedItem(function_decl,
+ return ImportUnsupportedItem(function_template_decl,
"Function templates are not supported yet");
}
@@ -604,7 +633,9 @@
.member_func_metadata = std::move(member_func_metadata),
.has_c_calling_convention = has_c_calling_convention,
.source_loc = ConvertSourceLocation(function_decl->getBeginLoc()),
- .id = GenerateItemId(function_decl),
+ .id = function_template_decl == nullptr
+ ? GenerateItemId(function_decl)
+ : GenerateItemId(function_template_decl),
};
}
return std::nullopt;
@@ -716,6 +747,8 @@
}
}
+ ImportDeclsFromDeclContext(record_decl);
+ auto item_ids = GetItemIdsInSourceOrder(record_decl);
return Record{
.rs_name = std::string(record_name->Ident()),
.cc_name = std::string(record_name->Ident()),
@@ -734,7 +767,8 @@
.is_trivial_abi = record_decl->canPassInRegisters(),
.is_inheritable =
!record_decl->isEffectivelyFinal() && !record_decl->isUnion(),
- .is_union = record_decl->isUnion()};
+ .is_union = record_decl->isUnion(),
+ .child_item_ids = std::move(item_ids)};
}
std::optional<IR::Item> Importer::ImportEnum(clang::EnumDecl* enum_decl) {