| # Assembly Tests |
| |
| The Benchmark library provides a number of functions whose primary |
| purpose in to affect assembly generation, including `DoNotOptimize` |
| and `ClobberMemory`. In addition there are other functions, |
| such as `KeepRunning`, for which generating good assembly is paramount. |
| |
| For these functions it's important to have tests that verify the |
| correctness and quality of the implementation. This requires testing |
| the code generated by the compiler. |
| |
| This document describes how the Benchmark library tests compiler output, |
| as well as how to properly write new tests. |
| |
| |
| ## Anatomy of a Test |
| |
| Writing a test has two steps: |
| |
| * Write the code you want to generate assembly for. |
| * Add `// CHECK` lines to match against the verified assembly. |
| |
| Example: |
| ```c++ |
| |
| // CHECK-LABEL: test_add: |
| extern "C" int test_add() { |
| extern int ExternInt; |
| return ExternInt + 1; |
| |
| // CHECK: movl ExternInt(%rip), %eax |
| // CHECK: addl %eax |
| // CHECK: ret |
| } |
| |
| ``` |
| |
| #### LLVM Filecheck |
| |
| [LLVM's Filecheck](https://llvm.org/docs/CommandGuide/FileCheck.html) |
| is used to test the generated assembly against the `// CHECK` lines |
| specified in the tests source file. Please see the documentation |
| linked above for information on how to write `CHECK` directives. |
| |
| #### Tips and Tricks: |
| |
| * Tests should match the minimal amount of output required to establish |
| correctness. `CHECK` directives don't have to match on the exact next line |
| after the previous match, so tests should omit checks for unimportant |
| bits of assembly. ([`CHECK-NEXT`](https://llvm.org/docs/CommandGuide/FileCheck.html#the-check-next-directive) |
| can be used to ensure a match occurs exactly after the previous match). |
| |
| * The tests are compiled with `-O3 -g0`. So we're only testing the |
| optimized output. |
| |
| * The assembly output is further cleaned up using `tools/strip_asm.py`. |
| This removes comments, assembler directives, and unused labels before |
| the test is run. |
| |
| * The generated and stripped assembly file for a test is output under |
| `<build-directory>/test/<test-name>.s` |
| |
| * Filecheck supports using [`CHECK` prefixes](https://llvm.org/docs/CommandGuide/FileCheck.html#cmdoption-check-prefixes) |
| to specify lines that should only match in certain situations. |
| The Benchmark tests use `CHECK-CLANG` and `CHECK-GNU` for lines that |
| are only expected to match Clang or GCC's output respectively. Normal |
| `CHECK` lines match against all compilers. (Note: `CHECK-NOT` and |
| `CHECK-LABEL` are NOT prefixes. They are versions of non-prefixed |
| `CHECK` lines) |
| |
| * Use `extern "C"` to disable name mangling for specific functions. This |
| makes them easier to name in the `CHECK` lines. |
| |
| |
| ## Problems Writing Portable Tests |
| |
| Writing tests which check the code generated by a compiler are |
| inherently non-portable. Different compilers and even different compiler |
| versions may generate entirely different code. The Benchmark tests |
| must tolerate this. |
| |
| LLVM Filecheck provides a number of mechanisms to help write |
| "more portable" tests; including [matching using regular expressions](https://llvm.org/docs/CommandGuide/FileCheck.html#filecheck-pattern-matching-syntax), |
| allowing the creation of [named variables](https://llvm.org/docs/CommandGuide/FileCheck.html#filecheck-variables) |
| for later matching, and [checking non-sequential matches](https://llvm.org/docs/CommandGuide/FileCheck.html#the-check-dag-directive). |
| |
| #### Capturing Variables |
| |
| For example, say GCC stores a variable in a register but Clang stores |
| it in memory. To write a test that tolerates both cases we "capture" |
| the destination of the store, and then use the captured expression |
| to write the remainder of the test. |
| |
| ```c++ |
| // CHECK-LABEL: test_div_no_op_into_shr: |
| extern "C" void test_div_no_op_into_shr(int value) { |
| int divisor = 2; |
| benchmark::DoNotOptimize(divisor); // hide the value from the optimizer |
| return value / divisor; |
| |
| // CHECK: movl $2, [[DEST:.*]] |
| // CHECK: idivl [[DEST]] |
| // CHECK: ret |
| } |
| ``` |
| |
| #### Using Regular Expressions to Match Differing Output |
| |
| Often tests require testing assembly lines which may subtly differ |
| between compilers or compiler versions. A common example of this |
| is matching stack frame addresses. In this case regular expressions |
| can be used to match the differing bits of output. For example: |
| |
| <!-- {% raw %} --> |
| ```c++ |
| int ExternInt; |
| struct Point { int x, y, z; }; |
| |
| // CHECK-LABEL: test_store_point: |
| extern "C" void test_store_point() { |
| Point p{ExternInt, ExternInt, ExternInt}; |
| benchmark::DoNotOptimize(p); |
| |
| // CHECK: movl ExternInt(%rip), %eax |
| // CHECK: movl %eax, -{{[0-9]+}}(%rsp) |
| // CHECK: movl %eax, -{{[0-9]+}}(%rsp) |
| // CHECK: movl %eax, -{{[0-9]+}}(%rsp) |
| // CHECK: ret |
| } |
| ``` |
| <!-- {% endraw %} --> |
| |
| ## Current Requirements and Limitations |
| |
| The tests require Filecheck to be installed along the `PATH` of the |
| build machine. Otherwise the tests will be disabled. |
| |
| Additionally, as mentioned in the previous section, codegen tests are |
| inherently non-portable. Currently the tests are limited to: |
| |
| * x86_64 targets. |
| * Compiled with GCC or Clang |
| |
| Further work could be done, at least on a limited basis, to extend the |
| tests to other architectures and compilers (using `CHECK` prefixes). |
| |
| Furthermore, the tests fail for builds which specify additional flags |
| that modify code generation, including `--coverage` or `-fsanitize=`. |
| |