Advertisement
chadjoan

xdc: A hypothetical D cross-compiler and AST manipulation to

Jul 17th, 2013
562
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 22.04 KB | None | 0 0
  1.  
  2. I'd like to present my vision for a new D compiler. I call it xdc, a loose abbreviation for "Cross D Compiler" (if confused, see http://english.stackexchange.com/questions/37394/why-do-some-words-have-x-as-a-substitute). It could also mean other fun things like "Crossbone D Compiler" (I imagine a logo with some crossbones having a metal D atop where the skull normally goes), "eXperimental D Compiler", or something sounding like "ectasy"
  3.  
  4. We usually think of language features as being rewritten into simpler features. The simple features eventually get rewritten into machine instructions. Compilers are, fundamentally, responsible for performing "lowering" operations.
  5.  
  6. It makes sense to me, then, to make a compiler whose internals /look/ like a bunch of these rewrites and lowering operations. There should be a bunch of input patterns matched to the desired results. This has the happy side-effect of giving us a pleasant way to do AST manipulations from within D code.
  7.  
  8. I've also had a long-standing desire to see D on many more platforms. It should make an appearance on console platforms and on smartphones. I've tried doing this with a retargetable compiler like GCC before, and the work was surprisingly large. Even if the compiler already emits code for the target system's CPU, there are still a large number of details involving calling conventions, executable format, and any number of CPU/OS specific tweaks to object file output. It makes a lot more sense to me to just output C/C++ code and feed that to a native toolchain. That would skip a lot of the platform-specific nonsense that creates a barrier to entry for people who, say, just want to write a simple game for Android/iPhone/PS(3|4)/etc in D, and don't want to become compiler experts first. Ideally, some day, this compiler would also emit code or bytecode for Javascript, AS3/Flash, Java, and any other popular targets that the market desires. This can probably be done with DMD, but I'd like to make the process more approachable, and make backend authoring as easy as possible. It should be possible (and easy) to tell the compiler exactly what lowerings should be applied before the AST hits the backend.
  9.  
  10. xdc should bring all of that cross-platform targeting together with a compiler infrastructure that can blow everything else away (I hope!).
  11.  
  12. xdc is my dream for a D compiler that gives us our first step (of few) towards having what haXe has already (http://haxe.org/) : a compiler that can land code just about anywhere.
  13.  
  14. What follows is a collection of my thoughts written down as notes.
  15.  
  16. == Ideal Outcomes ==
  17.  
  18. .- D to C/C++ compiler that can easily reach target platforms that are
  19. . currently either unsupported or poorly supported by current D
  20. . compilers.
  21. . - Useful for game developers wishing to write D code on the
  22. . next big console platform.
  23. . - Useful for embedded systems developers wishing to write D code
  24. . on obscure or potentially proprietary microcontrollers.
  25.  
  26. .- Other backends (ex: llvm, Java bytecode, AS3/Flash bytecode, etc)
  27. . possible in the future. Community desires considered when
  28. . selecting new backend targets.
  29.  
  30. .- Interpreter backend: a notable backend that would be implemented as
  31. . a necessity for making CTFE work. A dedicated interpreter
  32. . backend would hopefully be much faster and easier on memory than
  33. . DMD's souped-up constant folder. (Even though DMD has become
  34. . considerably better at this in the past year or two.)
  35.  
  36. .- Abstract Syntax Tree (AST) manipulation toolchain, possibly usable
  37. . in CTFE. It would work like this:
  38. . (1) Pass some D code or Domain Specific Language (DSL) of your
  39. . choice (as text) into xdc at compile-time.
  40. . (2) xdc returns an AST.
  41. . (3) Use xdc's pattern-matching and substitution DSL to
  42. . manipulate the AST.
  43. . (4) xdc consumes the AST and emits modified D code.
  44. . (5) mixin(...) the result.
  45. . - If xdc is the compiler used to execute itself in CTFE, then
  46. . it might be possible to optimize this by having it expose
  47. . itself as a set of intrinsics.
  48.  
  49. .- Reference counting available by default on all platforms.
  50. . - Gets you into the action with minimal effort and little or no
  51. . compiler hacking. (More complete GC tends to require platform
  52. . specific ASM and/or operating system API support).
  53.  
  54. .- Full garbage collection available if supported.
  55. . - Ex: The C backends would default to ref-counting until the ASM
  56. . and OS-level code is written to support full GC.
  57. . - Ex: A Java backend would probably use the Java JVM by default.
  58.  
  59. .- Threading model determined by compiler configuration or available
  60. . platform hints.
  61. . - Ex: The user may have a posix-threads implementation available,
  62. . but know little other details about the target system. It
  63. . should be possible for xdc to use pthreads to emulate the
  64. . TLS and synchronization mechanisms needed to make D tick.
  65. . (Or at least emulate as many features as possible.)
  66. . - Ex: Possible "no threading" target for people who don't need
  67. . threading but DO need other D features to be available NOW
  68. . on an alien platform. Errors when the source code passed
  69. . into xdc assumes that threading features are present.
  70.  
  71. .- D compiler that is easy to hack on.
  72. . - "Looks like the problem it solves."
  73. . (To quote Walter's DConf2013 keynote.)
  74. . - Made of a bunch of patterns that describe
  75. . code rewrites/lowerings.
  76. . - Few or no null value checks necessary.
  77. . - null checks don't look like pattern matching or lowering.
  78. . - Few or no convoluted if-while-if-for-etc nests.
  79. . - These also don't look like pattern matching or lowering.
  80. . - It should be largely made of "pattern handlers" (see below).
  81. . - Each pattern handler will have one part that closely resembles
  82. . the AST fragment for the D code that it recognizes, and
  83. . another part that resembles the lowered form that it outputs.
  84. . - Dependency analysis that prevents your AST manipulation from
  85. . happening either too early or too late.
  86. . - Because the code that actually does lowerings is generated from
  87. . a DSL, it is possible to make it automate a lot of tedious
  88. . tasks, like updating the symbol table when nodes are added or
  89. . removed from the AST.
  90. . - This makes it easier to test experimental features.
  91.  
  92. .- A step-by-step view of what the compiler is doing to your code.
  93. . - Since the semantic analysis of xdc would be composed of
  94. . "pattern handlers" (see below), then each time one of them
  95. . completes the compiler could output the result of calling
  96. . .toString() (or .toDCode() or whatever) on the entire AST.
  97. . - This could be attached to an ncurses interface that would be
  98. . activated by passing a flag to the compiler, which would then
  99. . proceed to show the AST at every stage of compilation.
  100. . Press ENTER to see the next step, etc.
  101. . - This could also be exposed as API functionality that IDEs could
  102. . use to show developers how the compiler sees their code.
  103.  
  104. .- D code analysis engine that might be usable to automatically
  105. . translate D1 code into D2 code, or maybe D2 into D3 in the far
  106. . future.
  107.  
  108. == Architectural Overview ==
  109.  
  110. .- xdc will largely consist of "pattern handlers" that recognize
  111. . patterns in its AST and replace them with AST fragments that
  112. . contain successively fewer high-level features (lowering).
  113. . - These pattern handlers would feature a DSL that should make
  114. . the whole task fairly easy.
  115. . - The DSL proposed would be similar to regular expressions in
  116. . semantics but different in syntax.
  117. . - It will have operators for choice, repetition, optional
  118. . matches, capturing, and so on.
  119. . - The DSL must support nested structures well.
  120. . - The DSL must support vertical layout of patterns well.
  121. . - Because of the vertical patterns, most operators will either
  122. . be prefix or will be written in block style:
  123. . some_block_header { block_stmt1; block_stmt2; etc; }
  124. . - Actions like entering and leaving nodes are given their own
  125. . special syntax. The machine will treat them like tokens
  126. . that can be matched the same as any AST node. Notably,
  127. . node-entry and node-exit do not require introducing
  128. . non-regular elements to the DSL. node-entry and node-exit
  129. . may be subsumed into Deterministic Finite Automatons (DFAs).
  130. . - An example pattern handler might look like this:
  131.  
  132. const lowerWhileStatement =
  133. {
  134. // Apologies in advance if this isn't actually valid D code:
  135. // This is a design sketch and I currently don't have a way to compile it.
  136. //
  137. // The Pattern template, PatternMatch template, and PatternHandler class
  138. // have not yet been written. This is an example of how I might expect
  139. // them to be used.
  140. //
  141.  
  142. auto consumes = "while_statement";
  143. auto produces = "if_statement","goto","label");
  144.  
  145. auto recognizer = Pattern!
  146. "WhileStatement has
  147. {
  148. // Capture the conditional expression (call it \"expr\") and
  149. // capture the loop body (call it \"statement\").
  150. .expression $expr;
  151. .statement $statement has
  152. {
  153. // Capture any continue/break statements.
  154. any_amount_of {
  155. any_amount_of .; // Same as .* in regexes.
  156. one_of
  157. {
  158. ContinueStatement $continues;
  159. BreakStatement $breaks;
  160. }
  161. }
  162. any_amount_of .;
  163. }
  164. }";
  165.  
  166. auto action = (PatternMatch!(recognizer) m)
  167. {
  168. m.captures.add("uniqLoopAgain", getUniqueLabel(syntaxNode.enclosingScope))
  169. m.captures.add("uniqExitLoop", getUniqueLabel(syntaxNode.enclosingScope))
  170.  
  171. // The "recognizes" clause defines m.getCapture!"continues" with:
  172. // "ContinueStatement $continues;"
  173. // That line appears in a repitition context ("any_amount_of") and is
  174. // therefore typed as an array.
  175. foreach( ref node; m.getCapture!"continues" )
  176. node.replaceWith( m, "GotoStatement has $uniqLoopAgain" )
  177.  
  178. // Ditto for m.getCapture!"breaks" and "BreakStatement $breaks;".
  179. foreach( ref node; m.getCapture!"breaks" )
  180. node.replaceWith( m, "GotoStatement has $uniqExitLoop" )
  181. };
  182.  
  183. auto synthesizer = Pattern!
  184. "Label has $uniqLoopAgain
  185. IfStatement has
  186. {
  187. OpNegate has $expr
  188. GotoStatement has $uniqExitLoop
  189. }
  190. $statement
  191. GotoStatement has $uniqLoopAgain
  192. Label has $uniqExitLoop
  193. ";
  194.  
  195. return new PatternHandler(produces, consumes, recognizer, action, synthesizer);
  196. };
  197.  
  198. (Also available at: http://pastebin.com/0mBQxhLs )
  199.  
  200. .- Dispatch to pattern handlers is performed by the execution of a
  201. . DFA/Packrat hybrid instead of the traditional OOP inheritance
  202. . with method calls.
  203. . - Each pattern handler's recognizer gets treated like a regex
  204. . or Parsing Expression Grammar (PEG) fragment.
  205. . - All of the recognizers in the same semantic pass are pasted
  206. . together in an ordered-choice expression. The ordering is
  207. . determined by dependency analysis.
  208. . - A recognizer's pattern handler is invoked when the recognizer's
  209. . AST expression is matched.
  210. . - Once any substitutions are completed, then the machine executing
  211. . the pattern engine will set its cursor to the beginning of
  212. . the newly substituted AST nodes and continue running.
  213. . - Executing potentially hundreds of pattern handlers in a single
  214. . ordered-choice expression would be obnoxious for a packrat
  215. . parser (packrat machine?). Thankfully, ordered-choice is
  216. . possible in regular grammars, so it can be lowered into regex
  217. . operations and the whole thing turned into a DFA.
  218. . - If pattern recognizers end up needing recursive elements,
  219. . then they will probably not appear at the very beginning of
  220. . the pattern. Patterns with enough regular elements at the
  221. . start will be able to merge those regular elements into the
  222. . DFA with the rest of the pattern recognizers, and it all
  223. . becomes very fast table lookups in small tables.
  224.  
  225. .- This compiler would involve the creation of a parser-generator
  226. . API that allows code to programmatically create grammars, and
  227. . to do so without a bunch of clumsy string formatting and string
  228. . concatenation.
  229. . - These grammars could be written such that things like AST nodes
  230. . are seen as terminals. This expands possibilities and allows
  231. . all of the pattern handlers to be coalesced into a grammar
  232. . that operates on ASTs and fires off semantic actions whenever
  233. . one of the recognizer patterns gets tickled by the right AST
  234. . fragment.
  235. . - Using strings as terminals is still cool; and necessary for
  236. . xdc's text/D-code parser.
  237. . - A simple parser-generator API example:
  238.  
  239. ---------------------------------------
  240. string makeParser()
  241. {
  242. auto builder = new ParserBuilder!char;
  243. builder.pushSequence();
  244. builder.literal('x');
  245. builder.pushMaybe();
  246. builder.literal('y');
  247. builder.pop();
  248. builder.pop();
  249. return builder.toDCode("callMe");
  250. }
  251.  
  252. const foo = makeParser();
  253.  
  254. pragma(msg, foo);
  255. ---------------------------------------
  256. Current output:
  257. http://pastebin.com/V3E0Ubbc
  258. ---------------------------------------
  259.  
  260. . - Humans would probably never directly write grammars using this
  261. . API; it is intended for use by code that needs to write
  262. . grammars. xdc would be such code: it's given a bunch of
  263. . pattern handlers and needs to turn them into a grammar.
  264. . - This API could also make it easier to write the parser
  265. . generators that humans /would/ use. For example, it could be
  266. . used as an experimental backend for a regular expression
  267. . engine that can handle limited recursion.
  268. . - The packrats usually generated from PEGs are nice and all, but
  269. . I'd really like to generate DFAs whenever possible, because
  270. . those seem to be regarded as being /very fast/.
  271. . - DFAs can't handle the recursive elements of PEGs, but they
  272. . should be able to handle everything non-recursive that
  273. . precedes or follows the recursive elements.
  274. . - The parser-generator API would be responsible for aggressively
  275. . converting PEG-like elements into regex/DFA elements whenever
  276. . possible.
  277. . - Regular expressions can be embedded in PEGs as long as you tell
  278. . them how much text to match. You have to give them concrete
  279. . success/failure conditions that can be determined without
  280. . help from the rest of the PEG: things like "match as many
  281. . characters as possible" or "match as few characters as
  282. . possible". Without that, the regex's backtracking (DFA'd
  283. . or otherwise) won't mesh with the PEG. Give it a concrete
  284. . win/fail condition, however, and the embedded regex becomes
  285. . just another PEG building block that chews through some
  286. . source material and yields a yes/no result. Such regular
  287. . expressions allow DFAs to be introduced into a recursive
  288. . descent or packrat parser.
  289. . - Many PEG elements can be converted into these well-behaved
  290. . regular expressions.
  291. . - PEG repetition is just regular expression repetition with
  292. . a wrapper around it that says "match as many characters
  293. . as possible".
  294. . - PEG ordered choice can be lowered into regular expression
  295. . unordered choice, which can then be converted into DFAs:
  296. . I suspect that this is true: (uv/xy)c == (uv|(^(uv)&xy))c
  297. . (or, by De Morgan's law: (uv/xy)c == (uv|(^(uv|^(xy))))c )
  298. . & is intersection.
  299. . ^ is negation.
  300. . Each letter (u,v,x,y,c) can be a subexpression
  301. . (non-recursive).
  302. . - PEG label matching can be inlined up to the point where
  303. . recursion occurs, thus allowing more elements to be
  304. . considered for DFA conversion.
  305. . - etc.
  306.  
  307. .- The parser would be defined using a PEG (most likely using Pegged
  308. . specifically).
  309. . - Although Pegged is an awesome achievement, I suspect its output
  310. . could be improved considerably. The templated code it
  311. . generates is slow to compile and ALWAYS allocates parse
  312. . tree nodes at every symbol.
  313. . - I want to experiment with making Pegged (or a branch of it) emit
  314. . DFA/Packrat parser hybrids. This could be done by making a
  315. . version of Pegged that uses the aforementioned
  316. . parser-generator API to create its parsers.
  317. . - Design principle: avoid memory allocations like the plague.
  318. . The output should be a well-pruned AST, and not just a parse
  319. . tree that causes a bunch of allocations and needs massaging to
  320. . become useful.
  321. . - I really like Pegged and would contribute this stuff upward, if
  322. . accepted.
  323.  
  324. .- When hacking on xdc, you don't need to be aware of WHEN your code
  325. . code gets executed in semantic analysis. The dependency analysis
  326. . will guarantee that it always gets performed both
  327. . (a) when it's needed, and (b) when it has what it needs.
  328. . - This is what the "consumes" and "produces" variables are all
  329. . about in the above example.
  330.  
  331. .- Successfully lowering a D AST into the target backend's input will
  332. . almost certainly require multiple passes. xdc's dependency
  333. . analyzer would automatically minimize the number of passes by
  334. . looking for patterns that are "siblings" in the dependency graph
  335. . (eg. neither depends on the other) and bunching as many such
  336. . patterns as possible into each pass.
  337. . - It really shouldn't generate very many more than the number of
  338. . passes that DMD has coded into it. Ideally: no more than DMD,
  339. . if not fewer.
  340. . - I'd like to make the dependency analyzer output a graph that
  341. . can be used to track which patterns cause which passes to
  342. . exist, and show which patterns are in which passes.
  343.  
  344. .- Planned availability of backends.
  345. . - My first pick for a backend would be an ANSI C89 target. I feel
  346. . that this would give it the most reach.
  347. . - The interpreter backend is along for the ride, as mentioned.
  348. . - Because the semantic analysis is composed of distinct and
  349. . loosely-coupled patterns, it is possible for xdc to generate
  350. . an analysis chain with the minimum number of lowerings needed
  351. . for a given backend.
  352. . - The interpreter backend would benefit from having the most
  353. . lowerings. By requiring a lot of lowering, the interpreter
  354. . would only need to support a small number of constructs:
  355. . - if statements
  356. . - gotos
  357. . - function calls
  358. . - arithmetic expression evaluation
  359. . - builtin types (byte, short, int, long, float, double, etc)
  360. . - pointers
  361. . - Even structs are unnecessary: they can be seen as
  362. . typed dereferencing of untyped pointers.
  363. . - The C backend would benefit from slightly less lowering than
  364. . the interpreter backend. It is useful for debugging if
  365. . you can mostly-sorta read the resulting C code, and your
  366. . C compiler will appreciate the extra optimization
  367. . opportunities.
  368. . - Looping constructs like while and for are welcome here.
  369. . - structs would be more readable.
  370. . - In the future, a Java or C# backend might use an entirely
  371. . different set of lowerings in later passes.
  372. . - Pointers are no longer considered "low".
  373. . - Classes should be kept as long as possible;
  374. . I'm pretty sure they bytecode (at least for Java)
  375. . has opcodes dedicated to classes. Removing them
  376. . may cause pessimisation.
  377. . - The backend writer should not have to worry about rewriting
  378. . the semantic analysis to suit their needs. They just define
  379. . some features and say which ones they need available in the
  380. . AST, and xdc's semantic-analysis-generator will handle the
  381. . rest.
  382. . - Notably, a backend should just be more lowerings, with the
  383. . result being text or binary code instead of AST nodes.
  384. . - Backends are essentially defined by the set of AST/language
  385. . features that they consume and any special lowerings needed
  386. . to convert generic AST/language features into
  387. . backend-specific AST/language features.
  388.  
  389.  
  390. == Closing Thoughts ==
  391.  
  392. I am realizing that there are multiple reasons that compel me to write this document:
  393. - To share my ideas with others, on the off-chance that someone else might see this vision too and be better equipped to deliver.
  394. - To suggest capabilities that any community-endorsed compiler tool (ex: compiler-as-a-ctfe-library) should have.
  395. - To see if I might be able to get the help I need to make it a reality.
  396.  
  397. I just can't decide which reasons are more important. But there is a common thread: I want this vision to become reality and do really cool things while filling a bunch of missing links in D's ecosystem.
  398.  
  399. I have to ask:
  400.  
  401. Would you pay for this?
  402. If so, then I might be able to do a kickstarter at some point.
  403. I am not independently wealthy or retired (or both?) like Walter, nor am I able to survive on zero hours of sleep each night like Andrei, and this would be a big project. I think it would need full-time attention or it would never become useful in a reasonable timeframe.
  404.  
  405. Also, assuming you understand the design, are there any gaping holes in this?
  406. This is my first attempt to share these ideas with a larger group, and thus an opportunity to anticipate troubles.
  407.  
  408. ...
  409.  
  410. Well, I'm anxious to see how well the venerable D community receives this bundle of ideas. Be chatty. I'll try to keep up.
  411.  
  412. Thank you for reading.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement