# RLC This document describes the architecture of `RLC`, the rulebook compiler, and of the other tools of the RLC suite. The intent is to provide a high level description of how the various parts of Rulebook fit into a product built on top of it, as well as a description of RLC command line facilities. More exhaustive descriptions of all tools can be found in the [reference](./language-reference.md). RLC is a [LLVM](https://llvm.org) based compiler, it ingest one or more rulebook files and various possible outputs depending on the user request. ```{graphviz} digraph RLC_Compiler { /*––Layout––*/ rankdir = LR; nodesep = 0.6; ranksep = 1; /*––Global node / edge styling––*/ node [shape=box, style=filled, color="#dddddd", fontname="Helvetica", fontsize=10]; edge [arrowsize=0.7]; /*––Input & compiler––*/ rl_file [label="file.rl", shape=note, style="filled,rounded", color="#8ecae6", fontcolor="#0a3045"]; rlc [label="rlc", shape=box3d, color="#219ebc", fontcolor="white"]; /*––Outputs, all kept on the same vertical line––*/ subgraph cluster_outputs { rank=same; executable [label="Executable"]; shared [label="Shared Library"]; static [label="Static Library"]; header [label="header.h"]; godot [label="Godot-CPP Module"]; csharp [label="C# Wrapper"]; python [label="Python Wrapper"]; } /*––Edges––*/ rl_file -> rlc; rlc -> executable; rlc -> shared ; rlc -> static ; rlc -> header; rlc -> godot; rlc -> csharp ; rlc -> python; } ``` RLC produces: * **native executable**, pretty much the same as the output of a c/cpp file compiled with clang. * native libraries to reuse in other languages. * a wrapper to use the library in that language. ## rlc-lsp and autocomplete rlc-lsp is a [language server](https://en.wikipedia.org/wiki/Language_Server_Protocol) for the Rulebook language. It allows users to get autocomplete in their ide. Notice that rlc-lsp must be in PATH, so if you are using the PIP package of rulebook, you must start your editor from the shell that has already enabled the virtual environment that installed rl\_language or rl\_language\_core. ### VSCODE `vscode` has a plugin available in the plugin store called `rl-lsp` and `rl-language` that enables autocomplete and syntax highlighting. ### VIM with YouCompleteMe On `vim`, if you use `YouCompleteMe` you can get access to automplete by adding the followings to your .vimrc file. ``` au BufRead,BufNewFile *.rl set filetype=rl let g:ycm_language_server = [ { \ 'name': 'rulebook', \ 'cmdline': [ 'rlc-lsp', '--stdio' ], \ 'filetypes': [ 'rl' ] \ }] ``` ## rlc-test rlc-test runs all functions with no arguments found in the input file that return a bool called test_\* . ```rlc fun test_return_success() -> Bool: return true ``` ```bash rlc-test file.rl ``` This program is only available in a pip installation. ## rlc-random rlc-random runs random actions on a program with a finite amount of actions, and does until it reaches the end of the program. It prints the selected actions. This tool is usefull as a command line utility to quickly test a interactive program, or to produce a trace usefull to some other program. To be usable with rlc-random, a program just requires that to have a entry point with the following signature `act play() -> Game`, and that all `action statements` that appear in `play` are enumerable. This program is only available in a pip installation. ## rlc-learn rlc-learn uses a off-the-shelf implmentation of PPO to maximize some metric in a given Rulebook program. This tool is intended to be used to perform sanity checks by those that wish to roll out their own machine learning algorithm, or by those that do not have knowledge of reinforcement learning and wish to use a acceptable off-the-shelf implementation. A basic tutorial is shown [here](./tutorial.md). This program is only available in a pip installation. ## rlc-play Given a Rulebook program file.rl, and the network obtained from `rlc-learn file.rl`, rlc-play generates a playout of that environment from start to finish according to the probabilities assigned by the network. A basic tutorial is shown [here](./tutorial.md). This program is only available in a pip installation. ## rlc-probs rlc-probs uses the network generated by rlc-learn to display on screen the actions available to the network in a given state of the input interactive program, and their respective probabilities. This tool is intended to be use to inspect the decision making skills of a trained network, beside gaining more insight than observing a single decision at the time. A basic tutorial is shown [here](./tutorial.md). This program is only available in a pip installation. ## rlc-action Applies a execution trace to a rulebook program, and then prints the final result. This tool is usefull to check if a trace is valid, if the traced program is correct. This program is only available in a pip installation. ## Tools for compiler people This section of the document talks about the internal components of RLC, and is meant for compiler developers, not users of the language. If you are confused about what this means and you are wondering if this section is for your, it is not, altough you can still read it if you are interested in knowing what goes on under rlc hood. ## RLC internals RLC is a [LLVM](https://llvm.org) and [MLIR](https://mlir.llvm.org) based compiler. It has a custom recursive descent parser, and a custom AST built on top of MLIR, called the RLC Dialect. ```{graphviz} digraph RLC_Pipeline_V { rankdir = TB; /* vertical (top-to-bottom) layout */ nodesep = 0.6; ranksep = 1; dpi = 100; /* higher resolution */ node [shape=box, style=filled, color="#dddddd", fontname="Helvetica", fontsize=11]; edge [arrowsize=0.7]; /* main pipeline */ lexer [label="Lexer"]; parser [label="Parser"]; unchecked_ast [label="Unchecked AST"]; typechecked_ast [label="Typechecked AST"]; implicit_instantiation[label="Implicit Instantiation"]; flattened [label="Flattened"]; action_rewriting [label="Action Rewriting"]; llvm_ir [label="LLVM IR"]; file_a [label="file.a"]; linker [label="Linker"]; lib_so [label="lib.so"]; /* auxiliary */ libruntime [label="libruntime", shape=note, style="filled,rounded", color="#8ecae6", fontcolor="#0a3045"]; wrappers [label="Wrappers"]; /* sequential flow */ lexer -> parser parser -> unchecked_ast unchecked_ast -> typechecked_ast typechecked_ast -> implicit_instantiation implicit_instantiation -> flattened flattened -> action_rewriting action_rewriting -> llvm_ir llvm_ir -> file_a file_a -> linker linker -> lib_so /* extra edges */ libruntime -> linker ; implicit_instantiation -> wrappers; } ``` ### Lexer The lexer of RLC takes the input file and turns them into token to be consumed by the parser. There is nothing fancy here except that since the language has semantical whitespaces, the lexer must keep track of the indentation. You can see the token stream with `rlc --token file.rl` ### Parser The parser is a recursive descent handwritten parser, the parser takes the tokens, and creates on the fly the unchecked AST. ### Unchecked AST Before typechecking the AST of the language is unchecked, it means that almost every non trivial operation is marked as having unkown type. You can see the token stream with `rlc --unchecked file.rl` ### Typechecked AST The typechecked IR knows all types of all expressions, except for templates, which have been typechecked, but are not expanded yet. Ideally, all static errors in the input program should be discovered during typechecking. In practice this is true for every error except errors relating to `drop`, `init` and `assign` when they are used inside templates. You can see the token stream with `rlc --type-checked file.rl` ### Implicit instantiation During the implicit instantiation step templates end up being generated, and implicit init, drop and assign functions emitted. After the implicit step, no object of the module can be a template, and all non external declared entities must have been discovered. You can see the token stream with `rlc --after-implicit file.rl` Wrappers for other languages are emitted after this step, when all concrete functions are accounted for. ### Flattened During the flattening step all control flow constructs are removed and replaced with jumps. You can see the token stream with `rlc --flattened file.rl` ### Action rewriting During action rewriting all Action Functions `act $name() -> $Type:` end up being replaced with the functions they declare. This includes: * `fun $name() -> $Type:` * `fun can_$name() -> Bool:` if the action function `$name` has a precondition. * `fun $act($Type, args...)` for each action statement called `$act` in `$name` * `fun can_$act($Type, args...) -> Bool` for each action statement called `$act` in `$name` * `fun is_done($Type) -> Bool` After action rewrting the module contains only types and functions. ### Lowering to LLVM IR The module is translated to LLVM IR to be optimized and compiled. You can see the token stream with `rlc --ir file.rl` to see the LLVM ir. You can use `rlc --ir file.rl -O2` if you want to see the optimized IR. ### Making IR more readable When priting the RLC IR you can pass `--hide-positions` and `--hide-dl` to omit the module data layout and the module debug positions. That makes the IR slightly more readable. ### MLIR I know you are looking for a explanation of how MLIR works since MLIR documentation is arcane. Good luck! Once you figure it out: RLC uses MLIR as a mere intermediate rappresentation generator, and as a way to convert to LLVM IR. We use nothing about Dialect interoperability or tensorial stuff. Every MLIR operation we use(except when converting to LLVM IR) is custom made for RLC. In practice what we get out MLIR is: * the pipeline infrastructure * the declarative way of creating new operation types * the textual serializers for MLIR * mlir-opt which runs single passes of mlir * some basic type interfaces * MLIR dataflow analisys ### Running single RLC passes If you want to run a RLC pass in isolation after a particular step of the rlc pipeline, say the typechecking step, you can run ``` rlc --type-checked file.rl -o - | rlc-opt --PASS_NAME ```