1# Testing Cranelift 2 3Cranelift is tested at multiple levels of abstraction and integration. When 4possible, Rust unit tests are used to verify single functions and types. When 5testing the interaction between compiler passes, file-level tests are 6appropriate. 7 8## Rust tests 9 10Rust and Cargo have good support for testing. Cranelift uses unit tests, doc 11tests, and integration tests where appropriate. The 12[Rust By Example page on Testing] is a great illustration on how to write 13each of these forms of test. 14 15[Rust By Example page on Testing]: https://doc.rust-lang.org/rust-by-example/testing.html 16 17## File tests 18 19Compilers work with large data structures representing programs, and it quickly 20gets unwieldy to generate test data programmatically. File-level tests make it 21easier to provide substantial input functions for the compiler tests. 22 23File tests are `*.clif` files in the `filetests/` directory 24hierarchy. Each file has a header describing what to test followed by a number 25of input functions in the :doc:`Cranelift textual intermediate representation 26<ir>`: 27 28.. productionlist:: 29 test_file : test_header `function_list` 30 test_header : test_commands (`isa_specs` | `settings`) 31 test_commands : test_command { test_command } 32 test_command : "test" test_name { option } "\n" 33 34The available test commands are described below. 35 36Many test commands only make sense in the context of a target instruction set 37architecture. These tests require one or more ISA specifications in the test 38header: 39 40.. productionlist:: 41 isa_specs : { [`settings`] isa_spec } 42 isa_spec : "isa" isa_name { `option` } "\n" 43 44The options given on the `isa` line modify the ISA-specific settings defined in 45`cranelift-codegen/meta-python/isa/*/settings.py`. 46 47All types of tests allow shared Cranelift settings to be modified: 48 49.. productionlist:: 50 settings : { setting } 51 setting : "set" { option } "\n" 52 option : flag | setting "=" value 53 54The shared settings available for all target ISAs are defined in 55`cranelift-codegen/meta-python/base/settings.py`. 56 57The `set` lines apply settings cumulatively: 58 59``` 60 test legalizer 61 set opt_level=best 62 set is_pic=1 63 target riscv64 64 set is_pic=0 65 target riscv32 supports_m=false 66 67 function %foo() {} 68``` 69 70This example will run the legalizer test twice. Both runs will have 71`opt_level=best`, but they will have different `is_pic` settings. The 32-bit 72run will also have the RISC-V specific flag `supports_m` disabled. 73 74The filetests are run automatically as part of `cargo test`, and they can 75also be run manually with the `clif-util test` command. 76 77By default, the test runner will spawn a thread pool with as many threads as 78there are logical CPUs. You can explicitly control how many threads are spawned 79via the `CRANELIFT_FILETESTS_THREADS` environment variable. For example, to 80limit the test runner to a single thread, use: 81 82``` 83$ CRANELIFT_FILETESTS_THREADS=1 clif-util test path/to/file.clif 84``` 85 86### Filecheck 87 88Many of the test commands described below use *filecheck* to verify their 89output. Filecheck is a Rust implementation of the LLVM tool of the same name. 90See the `documentation <https://docs.rs/filecheck/>`_ for details of its syntax. 91 92Comments in `.clif` files are associated with the entity they follow. 93This typically means an instruction or the whole function. Those tests that 94use filecheck will extract comments associated with each function (or its 95entities) and scan them for filecheck directives. The test output for each 96function is then matched against the filecheck directives for that function. 97 98Comments appearing before the first function in a file apply to every function. 99This is useful for defining common regular expression variables with the 100`regex:` directive, for example. 101 102Note that LLVM's file tests don't separate filecheck directives by their 103associated function. It verifies the concatenated output against all filecheck 104directives in the test file. LLVM's :command:`FileCheck` command has a 105`CHECK-LABEL:` directive to help separate the output from different functions. 106Cranelift's tests don't need this. 107 108### `test cat` 109 110This is one of the simplest file tests, used for testing the conversion to and 111from textual IR. The `test cat` command simply parses each function and 112converts it back to text again. The text of each function is then matched 113against the associated filecheck directives. 114 115Example: 116 117``` 118 function %r1() -> i32, f32 { 119 block1: 120 v10 = iconst.i32 3 121 v20 = f32const 0.0 122 return v10, v20 123 } 124 ; sameln: function %r1() -> i32, f32 { 125 ; nextln: block0: 126 ; nextln: v10 = iconst.i32 3 127 ; nextln: v20 = f32const 0.0 128 ; nextln: return v10, v20 129 ; nextln: } 130``` 131 132### `test verifier` 133 134Run each function through the IR verifier and check that it produces the 135expected error messages. 136 137Expected error messages are indicated with an `error:` directive *on the 138instruction that produces the verifier error*. Both the error message and 139reported location of the error is verified: 140 141``` 142 test verifier 143 144 function %test(i32) { 145 block0(v0: i32): 146 jump block1 ; error: terminator 147 return 148 } 149``` 150 151This example test passes if the verifier fails with an error message containing 152the sub-string `"terminator"` *and* the error is reported for the `jump` 153instruction. 154 155If a function contains no `error:` annotations, the test passes if the 156function verifies correctly. 157 158### `test print-cfg` 159 160Print the control flow graph of each function as a Graphviz graph, and run 161filecheck over the result. See also the :command:`clif-util print-cfg` 162command: 163 164``` 165 ; For testing cfg generation. This code is nonsense. 166 test print-cfg 167 test verifier 168 169 function %nonsense(i32, i32) -> f32 { 170 ; check: digraph %nonsense { 171 ; regex: I=\binst\d+\b 172 ; check: label="{block0 | <$(BRIF=$I)>brif v1, block1(v2), block2 }"] 173 174 block0(v0: i32, v1: i32): 175 v2 = iconst.i32 0 176 brif v1, block1(v2), block2 ; unordered: block0:$BRIF -> block1 177 ; unordered: block0:$BRIF -> block2 178 179 block1(v5: i32): 180 return v0 181 182 block2: 183 v100 = f32const 0.0 184 return v100 185 } 186``` 187 188### `test domtree` 189 190Compute the dominator tree of each function and validate it against the 191`dominates:` annotations:: 192 193``` 194 test domtree 195 196 function %test(i32) { 197 block0(v0: i32): 198 jump block1 ; dominates: block1 199 block1: 200 brif v0, block2, block3 ; dominates: block2, block3 201 block2: 202 jump block3 203 block3: 204 return 205 } 206``` 207 208Every reachable basic block except for the entry block has an 209*immediate dominator* which is a jump or branch instruction. This test passes 210if the `dominates:` annotations on the immediate dominator instructions are 211both correct and complete. 212 213This test also sends the computed CFG post-order through filecheck. 214 215### `test legalizer` 216 217Legalize each function for the specified target ISA and run the resulting 218function through filecheck. This test command can be used to validate the 219encodings selected for legal instructions as well as the instruction 220transformations performed by the legalizer. 221 222### `test regalloc` 223 224Test the register allocator. 225 226First, each function is legalized for the specified target ISA. This is 227required for register allocation since the instruction encodings provide 228register class constraints to the register allocator. 229 230Second, the register allocator is run on the function, inserting spill code and 231assigning registers and stack slots to all values. 232 233The resulting function is then run through filecheck. 234 235 236### `test simple-gvn` 237 238Test the simple GVN pass. 239 240The simple GVN pass is run on each function, and then results are run 241through filecheck. 242 243### `test licm` 244 245Test the LICM pass. 246 247The LICM pass is run on each function, and then results are run 248through filecheck. 249 250### `test dce` 251 252Test the DCE pass. 253 254The DCE pass is run on each function, and then results are run 255through filecheck. 256 257### `test shrink` 258 259Test the instruction shrinking pass. 260 261The shrink pass is run on each function, and then results are run 262through filecheck. 263 264### `test simple_preopt` 265 266Test the preopt pass. 267 268The preopt pass is run on each function, and then results are run 269through filecheck. 270 271### `test compile` 272 273Test the whole code generation pipeline. 274 275Each function is passed through the full `Context::compile()` function 276which is normally used to compile code. This type of test often depends 277on assertions or verifier errors, but it is also possible to use 278filecheck directives which will be matched against the final form of the 279Cranelift IR right before binary machine code emission. 280 281### `test run` 282 283Compile and execute a function. 284 285This test command allows several directives: 286 - to print the result of running a function to stdout, add a `print` 287 directive and call the preceding function with arguments (see `%foo` in 288 the example below); remember to enable `--nocapture` if running these 289 tests through Cargo 290 - to check the result of a function, add a `run` directive and call the 291 preceding function with a comparison (`==` or `!=`) (see `%bar` below) 292 - for backwards compatibility, to check the result of a function with a 293 `() -> i*` signature, only the `run` directive is required, with no 294 invocation or comparison (see `%baz` below); a non zero value is 295 interpreted as a successful test execution, whereas a zero value is 296 interpreted as a failed test. 297 298Currently a `target` is required but is only used to indicate whether the host 299platform can run the test and currently only the architecture is filtered. The 300host platform's native target will be used to actually compile the test. 301 302Example: 303 304``` 305 test run 306 target x86_64 307 308 ; how to print the results of a function 309 function %foo() -> i32 { 310 block0: 311 v0 = iconst.i32 42 312 return v0 313 } 314 ; print: %foo() 315 316 ; how to check the results of a function 317 function %bar(i32) -> i32 { 318 block0(v0:i32): 319 v1 = iadd_imm v0, 1 320 return v1 321 } 322 ; run: %bar(1) == 2 323 324 ; legacy method of checking the results of a function 325 function %baz() -> i8 { 326 block0: 327 v0 = iconst.i8 1 328 return v0 329 } 330 ; run 331``` 332