diff --git a/CMakeLists.txt b/CMakeLists.txt index 640dd484b..ac14c778c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,11 +228,11 @@ foreach(_header IN LISTS rule_common_headers) list(APPEND _common_headers --common-header=${_header}) endforeach() set(cpp_rules_ir_outputs) -set(cpp_rules_index_inputs) -set(cpp_rules_index_outputs ${CMAKE_BINARY_DIR}/rules_index/c.json ${CMAKE_BINARY_DIR}/rules_index/cpp.json) +set(rules_index_parts) +set(rules_index_parts_dir ${CMAKE_BINARY_DIR}/rules_index/parts) set(rust_rules_ir_outputs) foreach(_rule_dir IN LISTS rule_subdirs) - file(GLOB _cpp_srcs ${_rule_dir}/src.c ${_rule_dir}/src.cpp) + file(GLOB _cpp_srcs ${_rule_dir}/src.c ${_rule_dir}/src.cpp ${_rule_dir}/src_cxx*.cpp) if(NOT _cpp_srcs) continue() endif() @@ -258,7 +258,15 @@ foreach(_rule_dir IN LISTS rule_subdirs) list(APPEND cpp_rules_ir_outputs ${_cpp_out}) if(NOT _rule_name STREQUAL "builtin_types") - list(APPEND cpp_rules_index_inputs ${_cpp_srcs} ${_cxxflags_file}) + set(_part ${rules_index_parts_dir}/${_rule_name}.json) + add_custom_command( + OUTPUT ${_part} + COMMAND ${CMAKE_COMMAND} -E make_directory ${rules_index_parts_dir} + COMMAND $ --dir ${_rule_dir} -o ${_part} + DEPENDS ${_cpp_srcs} ${rule_common_headers} ${rule_indexer_sources} ${RUSTLS_HEADER} ${BROTLI_HEADERS} + VERBATIM + ) + list(APPEND rules_index_parts ${_part}) endif() set(_rust_srcs) @@ -291,10 +299,11 @@ endforeach() add_custom_target("preprocess-cpp-rules" ALL DEPENDS ${cpp_rules_ir_outputs}) +set(cpp_rules_index_outputs ${CMAKE_BINARY_DIR}/rules_index/c.json ${CMAKE_BINARY_DIR}/rules_index/cpp.json) add_custom_command( OUTPUT ${cpp_rules_index_outputs} - COMMAND $ --exclude builtin_types ${_common_headers} - DEPENDS ${cpp_rules_index_inputs} ${rule_common_headers} ${rule_indexer_sources} ${RUSTLS_HEADER} ${BROTLI_HEADERS} + COMMAND $ ${_common_headers} -o ${CMAKE_BINARY_DIR}/rules_index ${rules_index_parts} + DEPENDS ${rules_index_parts} ${rule_common_headers} ${rule_indexer_sources} VERBATIM ) diff --git a/cpp2rust/converter/rules/rules_loader.cpp b/cpp2rust/converter/rules/rules_loader.cpp index 529b09601..197987eb3 100644 --- a/cpp2rust/converter/rules/rules_loader.cpp +++ b/cpp2rust/converter/rules/rules_loader.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -296,9 +297,30 @@ std::string WrapRule(const llvm::json::Object &rule, bool is_cxx) { dir, text); } +unsigned CxxStandard(const clang::LangOptions &opts) { + if (opts.CPlusPlus26) { + return 26; + } + if (opts.CPlusPlus23) { + return 23; + } + if (opts.CPlusPlus20) { + return 20; + } + if (opts.CPlusPlus17) { + return 17; + } + if (opts.CPlusPlus14) { + return 14; + } + return 11; +} + std::string BuildRulesEpilogue(const std::unordered_set &keys, - bool is_cxx) { + const clang::LangOptions &opts) { + bool is_cxx = opts.CPlusPlus; const auto &index = is_cxx ? CxxIndex() : CIndex(); + unsigned standard = is_cxx ? CxxStandard(opts) : 0; auto common = index.getString("common"); std::string out; if (!common->empty()) { @@ -312,6 +334,9 @@ std::string BuildRulesEpilogue(const std::unordered_set &keys, } for (const auto &entry : *entries) { auto rule = entry.getAsObject(); + if (auto since = rule->getInteger("since"); since && *since > standard) { + continue; + } if (auto required = rule->getArray("requires"); required && !llvm::all_of(*required, [&](const llvm::json::Value &key) { @@ -482,7 +507,7 @@ void PragmaHandler::HandlePragma(clang::Preprocessor &PP, RuleUsageCollector collector(CI_.getSema(), keys); collector.TraverseDecl(ctx.getTranslationUnitDecl()); - auto text = BuildRulesEpilogue(keys, ctx.getLangOpts().CPlusPlus); + auto text = BuildRulesEpilogue(keys, ctx.getLangOpts()); log() << "rules loaded for this translation unit:\n" << text; auto rules_file = src_mgr.createFileID( llvm::MemoryBuffer::getMemBufferCopy(text, ""), diff --git a/cpp2rust/rule_preprocessing/cpp_rule_indexer.cpp b/cpp2rust/rule_preprocessing/cpp_rule_indexer.cpp index 9aa7dfc22..6c735655e 100644 --- a/cpp2rust/rule_preprocessing/cpp_rule_indexer.cpp +++ b/cpp2rust/rule_preprocessing/cpp_rule_indexer.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -309,54 +308,91 @@ void WriteJson(const fs::path &path, llvm::json::Object object) { os << llvm::json::Value(std::move(object)); } -std::vector ReadCXXFlags(const fs::path &rule_dir) { - std::vector flags; - std::ifstream file(rule_dir / "cxxflags"); - for (std::string flag; std::getline(file, flag);) { - if (!flag.empty()) { - flags.push_back(flag); - } +llvm::json::Object ReadJson(const fs::path &path) { + auto buffer = llvm::MemoryBuffer::getFile(path.string()); + if (!buffer) { + llvm::errs() << "ERROR: cannot read " << path.string() << '\n'; + std::exit(EXIT_FAILURE); + } + auto parsed = llvm::json::parse((*buffer)->getBuffer()); + if (!parsed || !parsed->getAsObject()) { + llvm::errs() << "ERROR: cannot parse " << path.string() << '\n'; + std::exit(EXIT_FAILURE); + } + return std::move(*parsed->getAsObject()); +} + +unsigned SinceStandard(const fs::path &src_path) { + auto stem_string = src_path.stem().string(); + llvm::StringRef stem = stem_string; + unsigned standard = 11; + if (stem.consume_front("src_cxx") && stem.getAsInteger(10, standard)) { + llvm::errs() << "ERROR: unexpected rule file " << src_path.string() << '\n'; + std::exit(EXIT_FAILURE); } - return flags; + return standard; } -void IndexRules(const fs::path &rules_dir, const fs::path &index_dir, - const std::vector &excluded, - const std::vector &common_headers) { - std::vector rule_dirs; - for (const auto &entry : fs::directory_iterator(rules_dir)) { - if (entry.is_directory() && - !llvm::is_contained(excluded, entry.path().filename().string())) { - rule_dirs.push_back(entry.path()); +void MergeRules(llvm::json::Object &into, llvm::json::Object &&from) { + for (auto &[key, entries] : from) { + for (auto &entry : *entries.getAsArray()) { + AddRule(into, key.str(), std::move(entry)); } } - std::ranges::sort(rule_dirs); - fs::create_directories(index_dir); - for (const char *lang : {"c", "cpp"}) { - std::string common; - if (lang == std::string("cpp")) { - for (const auto &header : common_headers) { - common += std::format("#include \"{}\"\n", header); - } +} + +void IndexDir(const fs::path &rule_dir, const fs::path &out) { + auto dir_name = rule_dir.filename().string(); + llvm::json::Object c_rules; + if (fs::exists(rule_dir / "src.c")) { + Index(rule_dir / "src.c", /*is_c=*/true, dir_name, {}, c_rules); + } + std::vector cxx_srcs; + for (const auto &entry : fs::directory_iterator(rule_dir)) { + auto name = entry.path().filename().string(); + if (name == "src.c" || name == "src.cpp" || + (name.starts_with("src_cxx") && name.ends_with(".cpp"))) { + cxx_srcs.push_back(entry.path()); } - bool is_c = lang == std::string("c"); + } + std::ranges::sort(cxx_srcs); + llvm::json::Object cxx_rules; + for (const auto &src_path : cxx_srcs) { + auto standard = SinceStandard(src_path); llvm::json::Object rules; - for (const auto &rule_dir : rule_dirs) { - for (const char *name : {"src.c", "src.cpp"}) { - auto src_path = rule_dir / name; - if (!fs::exists(src_path) || (is_c && src_path.extension() != ".c")) { - continue; - } - llvm::errs() << "Indexing " << src_path.string() << " as " << lang - << '\n'; - Index(src_path, is_c, rule_dir.filename().string(), - ReadCXXFlags(rule_dir), rules); + Index(src_path, /*is_c=*/false, dir_name, + {std::format("-std=c++{}", standard)}, rules); + for (auto &[key, entries] : rules) { + for (auto &entry : *entries.getAsArray()) { + (*entry.getAsObject())["since"] = standard; } } - WriteJson(index_dir / (std::string(lang) + ".json"), - llvm::json::Object{{"common", std::move(common)}, - {"rules", std::move(rules)}}); + MergeRules(cxx_rules, std::move(rules)); + } + WriteJson(out, llvm::json::Object{{"c", std::move(c_rules)}, + {"cpp", std::move(cxx_rules)}}); +} + +void MergeIndexes(const std::vector &parts, + const std::vector &common_headers, + const fs::path &index_dir) { + llvm::json::Object c_rules; + llvm::json::Object cxx_rules; + for (const auto &part : parts) { + auto json = ReadJson(part); + MergeRules(c_rules, std::move(*json.getObject("c"))); + MergeRules(cxx_rules, std::move(*json.getObject("cpp"))); + } + std::string common; + for (const auto &header : common_headers) { + common += std::format("#include \"{}\"\n", header); } + fs::create_directories(index_dir); + WriteJson(index_dir / "c.json", + llvm::json::Object{{"common", ""}, {"rules", std::move(c_rules)}}); + WriteJson(index_dir / "cpp.json", + llvm::json::Object{{"common", std::move(common)}, + {"rules", std::move(cxx_rules)}}); } } // namespace cpp2rust @@ -365,23 +401,35 @@ namespace { llvm::cl::OptionCategory cat("cpp-rule-indexer options"); -llvm::cl::list - Excluded("exclude", llvm::cl::desc("Rule directory that is not indexed"), - llvm::cl::value_desc("rule-dir"), llvm::cl::ZeroOrMore, - llvm::cl::cat(cat)); +llvm::cl::opt Dir("dir", llvm::cl::desc("Rule directory to index"), + llvm::cl::value_desc("rule-dir"), + llvm::cl::cat(cat)); + +llvm::cl::list Parts(llvm::cl::Positional, + llvm::cl::desc(""), + llvm::cl::ZeroOrMore, llvm::cl::cat(cat)); llvm::cl::list CommonHeaders( "common-header", llvm::cl::desc("Header the C++ rules depend on"), llvm::cl::value_desc("header"), llvm::cl::ZeroOrMore, llvm::cl::cat(cat)); +llvm::cl::opt + Out("o", + llvm::cl::desc("Output part of a rule directory, or index directory"), + llvm::cl::value_desc("path"), llvm::cl::Required, llvm::cl::cat(cat)); + } // namespace int main(int argc, char *argv[]) { llvm::cl::HideUnrelatedOptions(cat); llvm::cl::ParseCommandLineOptions(argc, argv); - cpp2rust::IndexRules(RULES_SRC_DIR, RULES_INDEX_DIR, - {Excluded.begin(), Excluded.end()}, - {CommonHeaders.begin(), CommonHeaders.end()}); + if (!Dir.empty()) { + cpp2rust::IndexDir(Dir.getValue(), Out.getValue()); + } else { + cpp2rust::MergeIndexes({Parts.begin(), Parts.end()}, + {CommonHeaders.begin(), CommonHeaders.end()}, + Out.getValue()); + } return EXIT_SUCCESS; } diff --git a/cpp2rust/rule_preprocessing/cpp_rule_preprocessor.cpp b/cpp2rust/rule_preprocessing/cpp_rule_preprocessor.cpp index d140b5ad7..1c7b180db 100644 --- a/cpp2rust/rule_preprocessing/cpp_rule_preprocessor.cpp +++ b/cpp2rust/rule_preprocessing/cpp_rule_preprocessor.cpp @@ -27,9 +27,11 @@ #include #include +#include #include #include #include +#include #include "compat/platform_flags.h" #include "converter/converter_lib.h" @@ -1120,14 +1122,26 @@ int main(int argc, char *argv[]) { CXXFlags.end()); fs::path dir = SrcDir.getValue(); llvm::json::Object root; - for (const char *name : {"src.c", "src.cpp"}) { - auto path = dir / name; - if (!fs::exists(path)) { - continue; + std::vector paths; + for (const auto &entry : fs::directory_iterator(dir)) { + auto name = entry.path().filename().string(); + if (name == "src.c" || name == "src.cpp" || + (name.starts_with("src_cxx") && name.ends_with(".cpp"))) { + paths.push_back(entry.path()); } + } + std::ranges::sort(paths); + for (const auto &path : paths) { llvm::errs() << "Preprocessing " << path.string() << '\n'; + llvm::SmallVector file_flags(cxx_flags); + auto stem = path.stem().string(); + std::string standard_flag; + if (llvm::StringRef standard = stem; standard.consume_front("src_cxx")) { + standard_flag = "-std=c++" + standard.str(); + file_flags.push_back(standard_flag); + } llvm::json::Object file_root; - cpp2rust::Extract(path, file_root, cxx_flags); + cpp2rust::Extract(path, file_root, file_flags); for (auto &[k, v] : file_root) { if (!root.try_emplace(k, std::move(v)).second) { llvm::errs() << "ERROR: rule name " << k.str() diff --git a/docs/src/rules/format.md b/docs/src/rules/format.md index 199c4481f..063546062 100644 --- a/docs/src/rules/format.md +++ b/docs/src/rules/format.md @@ -5,6 +5,8 @@ library it covers (`rules/unistd`, `rules/vector`, `rules/string`, ...). It contains: - `src.cpp` and/or `src.c`: the C++ (or C) side of each rule. +- `src_cxxNN.cpp` (optional): C++ rules that exist from C++NN on, for example + `src_cxx17.cpp`; see below. - `tgt_unsafe.rs`: the Rust targets for the unsafe model. - `tgt_refcount.rs`: the Rust targets for the reference counting model (optional, see below). @@ -141,6 +143,21 @@ for the C++ `__builtin_mul_overflow` (returning `bool`) while `src.c` defines `f12`/`f13` for the C version (returning `int`); their Rust bodies are identical. +## Standard versions + +`src.cpp` is parsed as C++11 and its rules are available to every C++ +translation unit. A rule that needs a later standard goes into `src_cxxNN.cpp`, +which is parsed as C++NN and whose rules are only loaded into translation units +compiled as C++NN or later: + +```cpp +// rules/new/src_cxx17.cpp +template T1 *f5(T1 *a0) { return std::launder(a0); } +``` + +Rule names are shared across all the files of a module. Platform `#if`s are fine +in any file; `#if __cplusplus` is not needed. + ## The `rules` crate The whole `rules/` tree is a single Rust crate. `rules/build.rs` walks the tree, diff --git a/rules/builtin_types/cxxflags b/rules/builtin_types/cxxflags deleted file mode 100644 index e23b2aef6..000000000 --- a/rules/builtin_types/cxxflags +++ /dev/null @@ -1 +0,0 @@ --std=c++20 diff --git a/rules/builtin_types/src.cpp b/rules/builtin_types/src.cpp index ff114783c..ddc271cc4 100644 --- a/rules/builtin_types/src.cpp +++ b/rules/builtin_types/src.cpp @@ -13,9 +13,6 @@ typedef decltype(nullptr) t0; BUILTIN_TYPE(1, bool) -#if __cplusplus >= 202002L -BUILTIN_TYPE(5, char8_t) -#endif BUILTIN_TYPE(8, char16_t) BUILTIN_TYPE(11, wchar_t) BUILTIN_TYPE(12, char32_t) diff --git a/rules/builtin_types/src_cxx20.cpp b/rules/builtin_types/src_cxx20.cpp new file mode 100644 index 000000000..d577e10b5 --- /dev/null +++ b/rules/builtin_types/src_cxx20.cpp @@ -0,0 +1,8 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +typedef char8_t t50; +typedef char8_t *t51; +typedef const char8_t *t52; +typedef volatile char8_t *t53; +typedef const volatile char8_t *t54; diff --git a/rules/compare/cxxflags b/rules/compare/cxxflags deleted file mode 100644 index e23b2aef6..000000000 --- a/rules/compare/cxxflags +++ /dev/null @@ -1 +0,0 @@ --std=c++20 diff --git a/rules/compare/src.cpp b/rules/compare/src_cxx20.cpp similarity index 100% rename from rules/compare/src.cpp rename to rules/compare/src_cxx20.cpp diff --git a/rules/cstddef/src.cpp b/rules/cstddef/src_cxx17.cpp similarity index 94% rename from rules/cstddef/src.cpp rename to rules/cstddef/src_cxx17.cpp index 5cd095064..69f5a9e2c 100644 --- a/rules/cstddef/src.cpp +++ b/rules/cstddef/src_cxx17.cpp @@ -3,7 +3,6 @@ #include -#if __cplusplus >= 201703L using t1 = std::byte; typedef std::size_t t9; typedef std::size_t *t10; @@ -16,4 +15,3 @@ std::byte f2(const std::byte &a0, unsigned a1) { return operator>>(a0, a1); } std::byte f3(std::byte &a0, unsigned a1) { return operator<<=(a0, a1); } std::byte f4(std::byte &a0, unsigned a1) { return operator>>=(a0, a1); } -#endif diff --git a/rules/new/src.cpp b/rules/new/src.cpp index fe9fd99cc..ca62a01af 100644 --- a/rules/new/src.cpp +++ b/rules/new/src.cpp @@ -10,7 +10,3 @@ void f2(void *a0) { return ::operator delete(a0); } void *f3(std::size_t a0) { return ::operator new[](a0); } void f4(void *a0) { return ::operator delete[](a0); } - -#if __cplusplus >= 201703L -template T1 *f5(T1 *a0) { return std::launder(a0); } -#endif diff --git a/rules/new/src_cxx17.cpp b/rules/new/src_cxx17.cpp new file mode 100644 index 000000000..c51903cac --- /dev/null +++ b/rules/new/src_cxx17.cpp @@ -0,0 +1,6 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +#include + +template T1 *f5(T1 *a0) { return std::launder(a0); } diff --git a/rules/string/src.cpp b/rules/string/src.cpp index c155c151a..508959e74 100644 --- a/rules/string/src.cpp +++ b/rules/string/src.cpp @@ -23,10 +23,6 @@ std::string &f4(std::string &s, const char *p, std::size_t n) { const char *f5(const std::string &s) { return s.c_str(); } -#if __cplusplus >= 201703L -auto f6(std::string &s) -> decltype(s.data()) { return s.data(); } -#endif - std::string f7(const char *s, std::size_t n) { return std::string(s, n); } std::string f8(std::istreambuf_iterator first, diff --git a/rules/string/src_cxx17.cpp b/rules/string/src_cxx17.cpp new file mode 100644 index 000000000..3ad1e6587 --- /dev/null +++ b/rules/string/src_cxx17.cpp @@ -0,0 +1,6 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +#include + +auto f6(std::string &s) -> decltype(s.data()) { return s.data(); } diff --git a/rules/unique_ptr/src.cpp b/rules/unique_ptr/src.cpp index 6afa5b0ac..8b4991391 100644 --- a/rules/unique_ptr/src.cpp +++ b/rules/unique_ptr/src.cpp @@ -9,12 +9,6 @@ template using t1 = std::unique_ptr; template using t2 = std::unique_ptr; -#if __cplusplus >= 201402L -template std::unique_ptr f1(std::size_t n) { - return std::make_unique(n); -} -#endif - template T1 *f2(std::unique_ptr &o) { return o.get(); } template std::unique_ptr f3(T1 *p) { @@ -35,13 +29,6 @@ template void f6(std::unique_ptr &o, T1 *p) { template T1 *f7(std::unique_ptr &o) { return o.get(); } -#if __cplusplus >= 201402L -template -std::unique_ptr f8(Init &&...args) { - return std::make_unique(std::forward(args)...); -} -#endif - template void f9(std::unique_ptr &o) { return o.reset(nullptr); } diff --git a/rules/unique_ptr/src_cxx14.cpp b/rules/unique_ptr/src_cxx14.cpp new file mode 100644 index 000000000..aa00bc51a --- /dev/null +++ b/rules/unique_ptr/src_cxx14.cpp @@ -0,0 +1,17 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +#include +#include +#include + +#include "../common/init.h" + +template std::unique_ptr f1(std::size_t n) { + return std::make_unique(n); +} + +template +std::unique_ptr f8(Init &&...args) { + return std::make_unique(std::forward(args)...); +}