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 isa riscv64 64 set is_pic=0 65 isa 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 ebb1: 120 v10 = iconst.i32 3 121 v20 = f32const 0.0 122 return v10, v20 123 } 124 ; sameln: function %r1() -> i32, f32 { 125 ; nextln: ebb0: 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 ebb0(v0: i32): 146 jump ebb1 ; 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="{ebb0 | <$(BRZ=$I)>brz ebb2 | <$(JUMP=$I)>jump ebb1}"] 173 174 ebb0(v0: i32, v1: i32): 175 brz v1, ebb2 ; unordered: ebb0:$BRZ -> ebb2 176 v2 = iconst.i32 0 177 jump ebb1(v2) ; unordered: ebb0:$JUMP -> ebb1 178 179 ebb1(v5: i32): 180 return v0 181 182 ebb2: 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 ebb0(v0: i32): 198 jump ebb1 ; dominates: ebb1 199 ebb1: 200 brz v0, ebb3 ; dominates: ebb3 201 jump ebb2 ; dominates: ebb2 202 ebb2: 203 jump ebb3 204 ebb3: 205 return 206 } 207``` 208 209Every reachable extended basic block except for the entry block has an 210*immediate dominator* which is a jump or branch instruction. This test passes 211if the `dominates:` annotations on the immediate dominator instructions are 212both correct and complete. 213 214This test also sends the computed CFG post-order through filecheck. 215 216### `test legalizer` 217 218Legalize each function for the specified target ISA and run the resulting 219function through filecheck. This test command can be used to validate the 220encodings selected for legal instructions as well as the instruction 221transformations performed by the legalizer. 222 223### `test regalloc` 224 225Test the register allocator. 226 227First, each function is legalized for the specified target ISA. This is 228required for register allocation since the instruction encodings provide 229register class constraints to the register allocator. 230 231Second, the register allocator is run on the function, inserting spill code and 232assigning registers and stack slots to all values. 233 234The resulting function is then run through filecheck. 235 236### `test binemit` 237 238Test the emission of binary machine code. 239 240The functions must contains instructions that are annotated with both encodings 241and value locations (registers or stack slots). For instructions that are 242annotated with a `bin:` directive, the emitted hexadecimal machine code for 243that instruction is compared to the directive: 244 245``` 246 test binemit 247 isa riscv 248 249 function %int32() { 250 ebb0: 251 [-,%x5] v0 = iconst.i32 1 252 [-,%x6] v1 = iconst.i32 2 253 [R#0c,%x7] v10 = iadd v0, v1 ; bin: 006283b3 254 [R#200c,%x8] v11 = isub v0, v1 ; bin: 40628433 255 return 256 } 257``` 258 259If any instructions are unencoded (indicated with a `[-]` encoding field), they 260will be encoded using the same mechanism as the legalizer uses. However, 261illegal instructions for the ISA won't be expanded into other instruction 262sequences. Instead the test will fail. 263 264Value locations must be present if they are required to compute the binary 265bits. Missing value locations will cause the test to crash. 266 267### `test simple-gvn` 268 269Test the simple GVN pass. 270 271The simple GVN pass is run on each function, and then results are run 272through filecheck. 273 274### `test licm` 275 276Test the LICM pass. 277 278The LICM pass is run on each function, and then results are run 279through filecheck. 280 281### `test dce` 282 283Test the DCE pass. 284 285The DCE pass is run on each function, and then results are run 286through filecheck. 287 288### `test shrink` 289 290Test the instruction shrinking pass. 291 292The shrink pass is run on each function, and then results are run 293through filecheck. 294 295### `test preopt` 296 297Test the preopt pass. 298 299The preopt pass is run on each function, and then results are run 300through filecheck. 301 302### `test compile` 303 304Test the whole code generation pipeline. 305 306Each function is passed through the full `Context::compile()` function 307which is normally used to compile code. This type of test often depends 308on assertions or verifier errors, but it is also possible to use 309filecheck directives which will be matched against the final form of the 310Cranelift IR right before binary machine code emission. 311 312### `test run` 313 314Compile and execute a function. 315 316This test command allows several directives: 317 - to print the result of running a function to stdout, add a `print` 318 directive and call the preceding function with arguments (see `%foo` in 319 the example below); remember to enable `--nocapture` if running these 320 tests through Cargo 321 - to check the result of a function, add a `run` directive and call the 322 preceding function with a comparison (`==` or `!=`) (see `%bar` below) 323 - for backwards compatibility, to check the result of a function with a 324 `() -> b*` signature, only the `run` directive is required, with no 325 invocation or comparison (see `%baz` below); a `true` value is 326 interpreted as a successful test execution, whereas a `false` value is 327 interpreted as a failed test. 328 329Currently a `target` is required but is only used to indicate whether the host 330platform can run the test and currently only the architecture is filtered. The 331host platform's native target will be used to actually compile the test. 332 333Example: 334 335``` 336 test run 337 target x86_64 338 339 ; how to print the results of a function 340 function %foo() -> i32 { 341 block0: 342 v0 = iconst.i32 42 343 return v0 344 } 345 ; print: %foo() 346 347 ; how to check the results of a function 348 function %bar(i32) -> i32 { 349 block0(v0:i32): 350 v1 = iadd_imm v0, 1 351 return v1 352 } 353 ; run: %bar(1) == 2 354 355 ; legacy method of checking the results of a function 356 function %baz() -> b1 { 357 block0: 358 v0 = bconst.b1 true 359 return v0 360 } 361 ; run 362``` 363 364#### Environment directives 365 366Some tests need additional resources to be provided by the filetest infrastructure. 367 368When any of the following directives is present the first argument of the function is *required* to be a `i64 vmctx`. 369The filetest infrastructure will then pass a pointer to the environment struct via this argument. 370 371The environment struct is essentially a list of pointers with info about the resources requested by the directives. These 372pointers are always 8 bytes, and laid out sequentially in memory. Even for 32 bit machines, where we only fill the first 3734 bytes of the pointer slot. 374 375Currently, we only support requesting heaps, however this is a generic mechanism that should 376be able to introduce any sort of environment support that we may need later. (e.g. tables, global values, external functions) 377 378##### `heap` directive 379 380The `heap` directive allows a test to request a heap to be allocated and passed to the test via the environment struct. 381 382 383A sample heap annotation is the following: 384``` 385; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8 386``` 387 388This indicates the following: 389* `static`: We have requested a non-resizable and non-movable static heap. 390* `size=0x1000`: It has to have a size of 4096 bytes. 391* `ptr=vmctx+0`: The pointer to the address to the start of this heap is placed at offset 0 in the `vmctx` struct 392* `bound=vmctx+8`: The pointer to the address to the end of this heap is placed at offset 8 in the `vmctx` struct 393 394The `ptr` and `bound` arguments make explicit the placement of the pointers to the start and end of the heap memory in 395the environment struct. `vmctx+0` means that at offset 0 of the environment struct there will be the pointer to the start 396similarly, at offset 8 the pointer to the end. 397 398 399You can combine multiple heap annotations, in which case, their pointers are laid out sequentially in memory in 400the order that the annotations appear in the source file. 401 402``` 403; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8 404; heap: dynamic, size=0x1000, ptr=vmctx+16, bound=vmctx+24 405``` 406 407An invalid or unexpected offset will raise an error when the test is run. 408 409See the diagram below, on how the `vmctx` struct ends up if with multiple heaps: 410 411``` 412 ┌─────────────────────┐ vmctx+0 413 │heap0: start address │ 414 ├─────────────────────┤ vmctx+8 415 │heap0: end address │ 416 ├─────────────────────┤ vmctx+16 417 │heap1: start address │ 418 ├─────────────────────┤ vmctx+24 419 │heap1: end address │ 420 ├─────────────────────┤ vmctx+32 421 │etc... │ 422 └─────────────────────┘ 423``` 424 425With this setup, you can now use the global values to load heaps, and load / store to them. 426 427Example: 428 429``` 430function %heap_load_store(i64 vmctx, i64, i32) -> i32 { 431 gv0 = vmctx 432 gv1 = load.i64 notrap aligned gv0+0 433 gv2 = load.i64 notrap aligned gv0+8 434 heap0 = dynamic gv1, bound gv2, offset_guard 0, index_type i64 435 436block0(v0: i64, v1: i64, v2: i32): 437 v3 = heap_addr.i64 heap0, v1, 4 438 store.i32 v2, v3 439 v4 = load.i32 v3 440 return v4 441} 442; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8 443; run: %heap_load_store(0, 1) == 1 444``` 445 446 447### `test interpret` 448 449Test the CLIF interpreter 450 451This test supports the same commands as `test run`, but runs the code in the cranelift 452interpreter instead of the host machine. 453