source_gen.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #ifndef CARBON_TESTING_BASE_SOURCE_GEN_H_
  5. #define CARBON_TESTING_BASE_SOURCE_GEN_H_
  6. #include <string>
  7. #include "absl/random/random.h"
  8. #include "common/map.h"
  9. #include "common/set.h"
  10. #include "llvm/ADT/ArrayRef.h"
  11. #include "llvm/ADT/StringRef.h"
  12. #include "llvm/Support/Allocator.h"
  13. namespace Carbon::Testing {
  14. // Provides source code generation facilities.
  15. //
  16. // This class works to generate valid but random & meaningless source code in
  17. // interesting patterns for benchmarking. It is very incomplete. A high level
  18. // set of long-term goals:
  19. //
  20. // - Generate interesting patterns and structures of code that have emerged as
  21. // toolchain performance bottlenecks in practice in C++ codebases.
  22. // - Generate code that includes most Carbon language features (and whatever
  23. // reasonable C++ analogs could be used for comparative purposes):
  24. // - Functions
  25. // - Classes with class functions, methods, and fields
  26. // - Interfaces
  27. // - Checked generics and templates
  28. // - Nested and unnested impls
  29. // - Nested classes
  30. // - Inline and out-of-line function and method definitions
  31. // - Imports and exports
  32. // - API files and impl files.
  33. // - Be random but deterministic. The goal is benchmarking and so while this
  34. // code should strive for not producing trivially predictable patterns, it
  35. // should also strive to be consistent and suitable for benchmarking. Wherever
  36. // possible, it should permute the order and content without randomizing the
  37. // total count, size, or complexity.
  38. //
  39. // Note that the default and primary generation target is interesting Carbon
  40. // source code. We have a best-effort to alternatively generate comparable C++
  41. // constructs to the Carbon ones for comparative benchmarking, but there is no
  42. // goal to cover all the interesting C++ patterns we might want to benchmark,
  43. // and we don't aim for perfectly synthesizing C++ analogs. We can always drop
  44. // fidelity for the C++ code path if needed for simplicity.
  45. //
  46. // TODO: There are numerous places where we hard code a fixed quantity. Instead,
  47. // we should build a rich but general system to easily encode a discrete
  48. // distribution that is sampled. We have a specialized version of this for
  49. // identifiers that should be generalized.
  50. class SourceGen {
  51. public:
  52. enum class Language : uint8_t {
  53. Carbon,
  54. Cpp,
  55. };
  56. struct FunctionDeclParams {
  57. // TODD: Arbitrary default, should switch to a distribution from data.
  58. int max_params = 4;
  59. };
  60. struct MethodDeclParams {
  61. // TODD: Arbitrary default, should switch to a distribution from data.
  62. int max_params = 4;
  63. };
  64. // Parameters used to generate a class in a generated file.
  65. //
  66. // Currently, this uses a fixed number of each kind of declaration, with
  67. // arbitrary defaults chosen. The defaults currently skew towards large
  68. // classes with lots of nested declarations.
  69. // TODO: Switch these to distributions based on data.
  70. //
  71. // TODO: Add support for generating definitions and parameters to control
  72. // them.
  73. //
  74. // TODO: Add heuristic for how many functions have return types.
  75. struct ClassParams {
  76. int public_function_decls = 4;
  77. FunctionDeclParams public_function_decl_params = {.max_params = 8};
  78. int public_method_decls = 10;
  79. MethodDeclParams public_method_decl_params;
  80. int private_function_decls = 2;
  81. FunctionDeclParams private_function_decl_params = {.max_params = 6};
  82. int private_method_decls = 8;
  83. MethodDeclParams private_method_decl_params = {.max_params = 6};
  84. int private_field_decls = 6;
  85. };
  86. // Parameters used to select type _uses_, as opposed to definitions.
  87. //
  88. // These govern what distribution of types are used for function parameters,
  89. // returns, and fields.
  90. //
  91. // Mainly these provide a coarse histogram of weights to shape the
  92. // distribution of different type options, and try to fit that as closely as
  93. // possible.
  94. //
  95. // The default weights in the histogram were arbitrarily selected based on
  96. // intuition about importance for benchmarking and not based on any
  97. // measurement. We arrange for them to sum to 100 so that the weights can be
  98. // view as %s of the type uses.
  99. //
  100. // The specific builtin type options used in the weights were also selected
  101. // arbitrarily.
  102. //
  103. // TODO: Improve the set of builtin types and the weighting if real world code
  104. // ends up sharply different.
  105. //
  106. // TODO: Add a heuristic to make some % of type references via pointers (or
  107. // other compound types).
  108. struct TypeUseParams {
  109. // The weights in the histogram start with a sequence fixed types described
  110. // with a Carbon and C++ string, and their associated weight.
  111. struct FixedTypeWeight {
  112. llvm::StringRef carbon_spelling;
  113. llvm::StringRef cpp_spelling;
  114. int weight;
  115. };
  116. llvm::SmallVector<FixedTypeWeight> fixed_type_weights = {
  117. // Combined weight of 65 for a core set of builtin types.
  118. {.carbon_spelling = "bool", .cpp_spelling = "bool", .weight = 25},
  119. {.carbon_spelling = "i32", .cpp_spelling = "int", .weight = 20},
  120. {.carbon_spelling = "i64",
  121. .cpp_spelling = "std::int64_t",
  122. .weight = 10},
  123. {.carbon_spelling = "i32*", .cpp_spelling = "int*", .weight = 5},
  124. {.carbon_spelling = "i64*",
  125. .cpp_spelling = "std::int64_t*",
  126. .weight = 5},
  127. // A weight of 5 distributed across tuple structures
  128. {.carbon_spelling = "(bool, i64)",
  129. .cpp_spelling = "std::pair<bool, std::int64_t>",
  130. .weight = 2},
  131. {.carbon_spelling = "(i32, i64*)",
  132. .cpp_spelling = "std::pair<int, std::int64_t*>",
  133. .weight = 3},
  134. };
  135. // The weight for using types declared in the file. These will be randomly
  136. // shuffled references, and when there are more type references than
  137. // declared, include repeated references.
  138. int declared_types_weight = 30;
  139. };
  140. // Parameters used to generate a file with dense declarations.
  141. struct DenseDeclParams {
  142. // TODO: Add more parameters to control generating top-level constructs
  143. // other than class definitions.
  144. // Parameters used when generating class definitions.
  145. ClassParams class_params = {};
  146. // Parameters used to guide the selection of types for use in declarations.
  147. TypeUseParams type_use_params = {};
  148. };
  149. // Access a global instance of this type to generate Carbon code for
  150. // benchmarks, tests, or other places where sharing a common instance is
  151. // useful. Note that there is nothing thread safe about this instance or type.
  152. static auto Global() -> SourceGen&;
  153. // Construct a source generator for the provided language, by default Carbon.
  154. explicit SourceGen(Language language = Language::Carbon);
  155. // Generate an API file with dense classes containing function forward
  156. // declarations.
  157. //
  158. // Accepts a number of `target_lines` for the resulting source code. This is a
  159. // rough approximation used to scale all the other constructs up and down
  160. // accordingly. For C++ source generation, we work to generate the same number
  161. // of constructs as Carbon would for the given line count over keeping the
  162. // actual line count close to the target.
  163. //
  164. // TODO: Currently, the formatting and line breaks of generating code are
  165. // extremely rough still, and those are a large factor in adherence to
  166. // `target_lines`. Long term, the goal is to get as close as we can to any
  167. // automatically formatted code while still keeping the stability of
  168. // benchmarking.
  169. auto GenAPIFileDenseDecls(int target_lines, const DenseDeclParams& params)
  170. -> std::string;
  171. // Get some number of randomly shuffled identifiers.
  172. //
  173. // The identifiers start with a character [A-Za-z], other characters may also
  174. // include [0-9_]. Both Carbon and C++ keywords are excluded along with any
  175. // other non-identifier syntaxes that overlap to ensure all of these can be
  176. // used as identifiers.
  177. //
  178. // The order will be different for each call to this function, but the
  179. // specific identifiers may remain the same in order to reduce the cost of
  180. // repeated calls. However, the sum of the identifier sizes returned is
  181. // guaranteed to be the same for every call with the same number of
  182. // identifiers so that benchmarking all of these identifiers has predictable
  183. // and stable cost.
  184. //
  185. // Optionally, callers can request a minimum and maximum length. By default,
  186. // the length distribution used across the identifiers will mirror the
  187. // observed distribution of identifiers in C++ source code and our expectation
  188. // of them in Carbon source code. The maximum length in this default
  189. // distribution cannot be more than 64.
  190. //
  191. // Callers can request a uniform distribution across [min_length, max_length],
  192. // and when it is requested there is no limit on `max_length`.
  193. auto GetShuffledIdentifiers(int number, int min_length = 1,
  194. int max_length = 64, bool uniform = false)
  195. -> llvm::SmallVector<llvm::StringRef>;
  196. // Same as `GetShuffledIdentifiers`, but ensures there are no collisions.
  197. auto GetShuffledUniqueIdentifiers(int number, int min_length = 4,
  198. int max_length = 64, bool uniform = false)
  199. -> llvm::SmallVector<llvm::StringRef>;
  200. // Returns a collection of un-shuffled identifiers, otherwise the same as
  201. // `GetShuffledIdentifiers`.
  202. //
  203. // Usually, benchmarks should use the shuffled version. However, this is
  204. // useful when deterministic access to the identifiers is needed to avoid
  205. // introducing noise, or if there is already a post-processing step to shuffle
  206. // things, since shuffling is very expensive in debug builds.
  207. auto GetIdentifiers(int number, int min_length = 1, int max_length = 64,
  208. bool uniform = false)
  209. -> llvm::SmallVector<llvm::StringRef>;
  210. // Returns a collection of un-shuffled unique identifiers, otherwise the same
  211. // as `GetShuffledUniqueIdentifiers`.
  212. //
  213. // Usually, benchmarks should use the shuffled version. However, this is
  214. // useful when deterministic access to the identifiers is needed to avoid
  215. // introducing noise, or if there is already a post-processing step to shuffle
  216. // things, since shuffling is very expensive in debug builds.
  217. auto GetUniqueIdentifiers(int number, int min_length = 1, int max_length = 64,
  218. bool uniform = false)
  219. -> llvm::SmallVector<llvm::StringRef>;
  220. // Returns a shared collection of random identifiers of a specific length.
  221. //
  222. // For a single, exact length, we have an even cheaper routine to return
  223. // access to a shared collection of identifiers. The order of these is a
  224. // single fixed random order for a given execution. The returned array
  225. // reference is only valid until the next call any method on this generator.
  226. auto GetSingleLengthIdentifiers(int length, int number)
  227. -> llvm::ArrayRef<llvm::StringRef>;
  228. private:
  229. class ClassGenState;
  230. friend ClassGenState;
  231. class UniqueIdentifierPopper;
  232. friend UniqueIdentifierPopper;
  233. using AppendFn = auto(int length, int number,
  234. llvm::SmallVectorImpl<llvm::StringRef>& dest) -> void;
  235. auto IsCpp() -> bool { return language_ == Language::Cpp; }
  236. auto GenerateRandomIdentifier(llvm::MutableArrayRef<char> dest_storage)
  237. -> void;
  238. auto AppendUniqueIdentifiers(int length, int number,
  239. llvm::SmallVectorImpl<llvm::StringRef>& dest)
  240. -> void;
  241. auto GetIdentifiersImpl(int number, int min_length, int max_length,
  242. bool uniform, llvm::function_ref<AppendFn> append)
  243. -> llvm::SmallVector<llvm::StringRef>;
  244. auto GetShuffledInts(int number, int min, int max) -> llvm::SmallVector<int>;
  245. auto GenerateFunctionDecl(
  246. llvm::StringRef name, bool is_private, bool is_method, int param_count,
  247. llvm::StringRef indent,
  248. llvm::SmallVectorImpl<llvm::StringRef>& param_names,
  249. llvm::function_ref<auto()->llvm::StringRef> get_type_name,
  250. llvm::raw_ostream& os) -> void;
  251. auto GenerateClassDef(const ClassParams& params, ClassGenState& state,
  252. llvm::raw_ostream& os) -> void;
  253. absl::BitGen rng_;
  254. llvm::BumpPtrAllocator storage_;
  255. Map<int, llvm::SmallVector<llvm::StringRef>> identifiers_by_length_;
  256. Map<int, std::pair<int, Set<llvm::StringRef>>> unique_identifiers_by_length_;
  257. Language language_;
  258. };
  259. } // namespace Carbon::Testing
  260. #endif // CARBON_TESTING_BASE_SOURCE_GEN_H_