Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame^] | 1 | // Copyright 2020 Google LLC |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 4 | // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 5 | // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| 6 | // option. This file may not be copied, modified, or distributed |
| 7 | // except according to those terms. |
| 8 | |
| 9 | mod analysis; |
| 10 | mod api; |
| 11 | mod apivec; |
| 12 | mod codegen_cpp; |
| 13 | mod codegen_rs; |
| 14 | #[cfg(test)] |
| 15 | mod conversion_tests; |
| 16 | mod convert_error; |
| 17 | mod doc_attr; |
| 18 | mod error_reporter; |
| 19 | mod parse; |
| 20 | mod utilities; |
| 21 | |
| 22 | use analysis::fun::FnAnalyzer; |
| 23 | use autocxx_parser::IncludeCppConfig; |
| 24 | pub(crate) use codegen_cpp::CppCodeGenerator; |
| 25 | pub(crate) use convert_error::ConvertError; |
| 26 | use itertools::Itertools; |
| 27 | use syn::{Item, ItemMod}; |
| 28 | |
| 29 | use crate::{ |
| 30 | conversion::analysis::deps::HasDependencies, CppCodegenOptions, CppFilePair, UnsafePolicy, |
| 31 | }; |
| 32 | |
| 33 | use self::{ |
| 34 | analysis::{ |
| 35 | abstract_types::{discard_ignored_functions, mark_types_abstract}, |
| 36 | allocators::create_alloc_and_frees, |
| 37 | casts::add_casts, |
| 38 | check_names, |
| 39 | constructor_deps::decorate_types_with_constructor_deps, |
| 40 | fun::FnPhase, |
| 41 | gc::filter_apis_by_following_edges_from_allowlist, |
| 42 | pod::analyze_pod_apis, |
| 43 | remove_ignored::filter_apis_by_ignored_dependents, |
| 44 | replace_hopeless_typedef_targets, |
| 45 | tdef::convert_typedef_targets, |
| 46 | }, |
| 47 | api::AnalysisPhase, |
| 48 | apivec::ApiVec, |
| 49 | codegen_rs::RsCodeGenerator, |
| 50 | parse::ParseBindgen, |
| 51 | }; |
| 52 | |
| 53 | const LOG_APIS: bool = true; |
| 54 | |
| 55 | /// Converts the bindings generated by bindgen into a form suitable |
| 56 | /// for use with `cxx`. |
| 57 | /// In fact, most of the actual operation happens within an |
| 58 | /// individual `BridgeConversion`. |
| 59 | /// |
| 60 | /// # Flexibility in handling bindgen output |
| 61 | /// |
| 62 | /// autocxx is inevitably tied to the details of the bindgen output; |
| 63 | /// e.g. the creation of a 'root' mod when namespaces are enabled. |
| 64 | /// At the moment this crate takes the view that it's OK to panic |
| 65 | /// if the bindgen output is not as expected. It may be in future that |
| 66 | /// we need to be a bit more graceful, but for now, that's OK. |
| 67 | pub(crate) struct BridgeConverter<'a> { |
| 68 | include_list: &'a [String], |
| 69 | config: &'a IncludeCppConfig, |
| 70 | } |
| 71 | |
| 72 | /// C++ and Rust code generation output. |
| 73 | pub(crate) struct CodegenResults { |
| 74 | pub(crate) rs: Vec<Item>, |
| 75 | pub(crate) cpp: Option<CppFilePair>, |
| 76 | pub(crate) cxxgen_header_name: String, |
| 77 | } |
| 78 | |
| 79 | impl<'a> BridgeConverter<'a> { |
| 80 | pub fn new(include_list: &'a [String], config: &'a IncludeCppConfig) -> Self { |
| 81 | Self { |
| 82 | include_list, |
| 83 | config, |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | fn dump_apis<T: AnalysisPhase>(label: &str, apis: &ApiVec<T>) { |
| 88 | if LOG_APIS { |
| 89 | log::info!( |
| 90 | "APIs after {}:\n{}", |
| 91 | label, |
| 92 | apis.iter() |
| 93 | .map(|api| { format!(" {:?}", api) }) |
| 94 | .sorted() |
| 95 | .join("\n") |
| 96 | ) |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | fn dump_apis_with_deps(label: &str, apis: &ApiVec<FnPhase>) { |
| 101 | if LOG_APIS { |
| 102 | log::info!( |
| 103 | "APIs after {}:\n{}", |
| 104 | label, |
| 105 | apis.iter() |
| 106 | .map(|api| { format!(" {:?}, deps={}", api, api.format_deps()) }) |
| 107 | .sorted() |
| 108 | .join("\n") |
| 109 | ) |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | /// Convert a TokenStream of bindgen-generated bindings to a form |
| 114 | /// suitable for cxx. |
| 115 | /// |
| 116 | /// This is really the heart of autocxx. It parses the output of `bindgen` |
| 117 | /// (although really by "parse" we mean to interpret the structures already built |
| 118 | /// up by the `syn` crate). |
| 119 | pub(crate) fn convert( |
| 120 | &self, |
| 121 | mut bindgen_mod: ItemMod, |
| 122 | unsafe_policy: UnsafePolicy, |
| 123 | inclusions: String, |
| 124 | cpp_codegen_options: &CppCodegenOptions, |
| 125 | ) -> Result<CodegenResults, ConvertError> { |
| 126 | match &mut bindgen_mod.content { |
| 127 | None => Err(ConvertError::NoContent), |
| 128 | Some((_, items)) => { |
| 129 | // Parse the bindgen mod. |
| 130 | let items_to_process = items.drain(..).collect(); |
| 131 | let parser = ParseBindgen::new(self.config); |
| 132 | let apis = parser.parse_items(items_to_process)?; |
| 133 | Self::dump_apis("parsing", &apis); |
| 134 | // Inside parse_results, we now have a list of APIs. |
| 135 | // We now enter various analysis phases. |
| 136 | // Next, convert any typedefs. |
| 137 | // "Convert" means replacing bindgen-style type targets |
| 138 | // (e.g. root::std::unique_ptr) with cxx-style targets (e.g. UniquePtr). |
| 139 | let apis = convert_typedef_targets(self.config, apis); |
| 140 | Self::dump_apis("typedefs", &apis); |
| 141 | // Now analyze which of them can be POD (i.e. trivial, movable, pass-by-value |
| 142 | // versus which need to be opaque). |
| 143 | // Specifically, let's confirm that the items requested by the user to be |
| 144 | // POD really are POD, and duly mark any dependent types. |
| 145 | // This returns a new list of `Api`s, which will be parameterized with |
| 146 | // the analysis results. It also returns an object which can be used |
| 147 | // by subsequent phases to work out which objects are POD. |
| 148 | let analyzed_apis = analyze_pod_apis(apis, self.config)?; |
| 149 | Self::dump_apis("pod analysis", &analyzed_apis); |
| 150 | let analyzed_apis = replace_hopeless_typedef_targets(self.config, analyzed_apis); |
| 151 | let analyzed_apis = add_casts(analyzed_apis); |
| 152 | let analyzed_apis = create_alloc_and_frees(analyzed_apis); |
| 153 | // Next, figure out how we materialize different functions. |
| 154 | // Some will be simple entries in the cxx::bridge module; others will |
| 155 | // require C++ wrapper functions. This is probably the most complex |
| 156 | // part of `autocxx`. Again, this returns a new set of `Api`s, but |
| 157 | // parameterized by a richer set of metadata. |
| 158 | Self::dump_apis("adding casts", &analyzed_apis); |
| 159 | let analyzed_apis = |
| 160 | FnAnalyzer::analyze_functions(analyzed_apis, unsafe_policy, self.config); |
| 161 | // If any of those functions turned out to be pure virtual, don't attempt |
| 162 | // to generate UniquePtr implementations for the type, since it can't |
| 163 | // be instantiated. |
| 164 | Self::dump_apis("analyze fns", &analyzed_apis); |
| 165 | let analyzed_apis = mark_types_abstract(analyzed_apis); |
| 166 | Self::dump_apis("marking abstract", &analyzed_apis); |
| 167 | // Annotate structs with a note of any copy/move constructors which |
| 168 | // we may want to retain to avoid garbage collecting them later. |
| 169 | let analyzed_apis = decorate_types_with_constructor_deps(analyzed_apis); |
| 170 | Self::dump_apis_with_deps("adding constructor deps", &analyzed_apis); |
| 171 | let analyzed_apis = discard_ignored_functions(analyzed_apis); |
| 172 | Self::dump_apis_with_deps("ignoring ignorable fns", &analyzed_apis); |
| 173 | // Remove any APIs whose names are not compatible with cxx. |
| 174 | let analyzed_apis = check_names(analyzed_apis); |
| 175 | // During parsing or subsequent processing we might have encountered |
| 176 | // items which we couldn't process due to as-yet-unsupported features. |
| 177 | // There might be other items depending on such things. Let's remove them |
| 178 | // too. |
| 179 | let analyzed_apis = filter_apis_by_ignored_dependents(analyzed_apis); |
| 180 | Self::dump_apis_with_deps("removing ignored dependents", &analyzed_apis); |
| 181 | |
| 182 | // We now garbage collect the ones we don't need... |
| 183 | let mut analyzed_apis = |
| 184 | filter_apis_by_following_edges_from_allowlist(analyzed_apis, self.config); |
| 185 | // Determine what variably-sized C types (e.g. int) we need to include |
| 186 | analysis::ctypes::append_ctype_information(&mut analyzed_apis); |
| 187 | Self::dump_apis_with_deps("GC", &analyzed_apis); |
| 188 | // And finally pass them to the code gen phases, which outputs |
| 189 | // code suitable for cxx to consume. |
| 190 | let cxxgen_header_name = cpp_codegen_options.cxxgen_header_namer.name_header(); |
| 191 | let cpp = CppCodeGenerator::generate_cpp_code( |
| 192 | inclusions, |
| 193 | &analyzed_apis, |
| 194 | self.config, |
| 195 | cpp_codegen_options, |
| 196 | &cxxgen_header_name, |
| 197 | )?; |
| 198 | let rs = RsCodeGenerator::generate_rs_code( |
| 199 | analyzed_apis, |
| 200 | self.include_list, |
| 201 | bindgen_mod, |
| 202 | self.config, |
| 203 | cpp.as_ref().map(|file_pair| file_pair.header_name.clone()), |
| 204 | ); |
| 205 | Ok(CodegenResults { |
| 206 | rs, |
| 207 | cpp, |
| 208 | cxxgen_header_name, |
| 209 | }) |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | } |