Skip to content

Commit 57aa030

Browse files
authored
Merge ffd44c9 into e668e2a
2 parents e668e2a + ffd44c9 commit 57aa030

File tree

13 files changed

+668
-12
lines changed

13 files changed

+668
-12
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*.dylib
77
*.cmake
88
!/cmake/*.cmake
9+
!/test/AssemblyTests.cmake
910
*~
1011
*.pyc
1112
__pycache__

.travis.yml

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ matrix:
3434
env:
3535
- INSTALL_GCC6_FROM_PPA=1
3636
- COMPILER=g++-6 C_COMPILER=gcc-6 BUILD_TYPE=Debug
37+
- ENABLE_SANITIZER=1
3738
- EXTRA_FLAGS="-fno-omit-frame-pointer -g -O2 -fsanitize=undefined,address -fuse-ld=gold"
3839
- compiler: clang
3940
env: COMPILER=clang++ C_COMPILER=clang BUILD_TYPE=Debug
@@ -91,6 +92,7 @@ matrix:
9192
env:
9293
- COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug
9394
- LIBCXX_BUILD=1 LIBCXX_SANITIZER="Undefined;Address"
95+
- ENABLE_SANITIZER=1
9496
- EXTRA_FLAGS="-stdlib=libc++ -g -O2 -fno-omit-frame-pointer -fsanitize=undefined,address -fno-sanitize-recover=all"
9597
- UBSAN_OPTIONS=print_stacktrace=1
9698
# Clang w/ libc++ and MSAN
@@ -102,6 +104,7 @@ matrix:
102104
env:
103105
- COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug
104106
- LIBCXX_BUILD=1 LIBCXX_SANITIZER=MemoryWithOrigins
107+
- ENABLE_SANITIZER=1
105108
- EXTRA_FLAGS="-stdlib=libc++ -g -O2 -fno-omit-frame-pointer -fsanitize=memory -fsanitize-memory-track-origins"
106109
# Clang w/ libc++ and MSAN
107110
- compiler: clang
@@ -112,8 +115,8 @@ matrix:
112115
env:
113116
- COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=RelWithDebInfo
114117
- LIBCXX_BUILD=1 LIBCXX_SANITIZER=Thread
118+
- ENABLE_SANITIZER=1
115119
- EXTRA_FLAGS="-stdlib=libc++ -g -O2 -fno-omit-frame-pointer -fsanitize=thread -fno-sanitize-recover=all"
116-
117120
- os: osx
118121
osx_image: xcode8.3
119122
compiler: clang
@@ -131,23 +134,32 @@ matrix:
131134
- COMPILER=g++-7 C_COMPILER=gcc-7 BUILD_TYPE=Debug
132135

133136
before_script:
134-
- if [ -z "$BUILD_32_BITS" ]; then
135-
export BUILD_32_BITS=OFF && echo disabling 32 bit build;
136-
fi
137137
- if [ -n "${LIBCXX_BUILD}" ]; then
138138
source .travis-libcxx-setup.sh;
139139
fi
140+
- if [ -n "${ENABLE_SANITIZER}" ]; then
141+
export EXTRA_OPTIONS="-DBENCHMARK_ENABLE_ASSEMBLY_TESTS=OFF";
142+
else
143+
export EXTRA_OPTIONS="";
144+
fi
140145
- mkdir -p build && cd build
141146

142147
before_install:
148+
- if [ -z "$BUILD_32_BITS" ]; then
149+
export BUILD_32_BITS=OFF && echo disabling 32 bit build;
150+
fi
143151
- if [ -n "${INSTALL_GCC6_FROM_PPA}" ]; then
144152
sudo add-apt-repository -y "ppa:ubuntu-toolchain-r/test";
145153
sudo apt-get update --option Acquire::Retries=100 --option Acquire::http::Timeout="60";
146154
fi
147155

148156
install:
149157
- if [ -n "${INSTALL_GCC6_FROM_PPA}" ]; then
150-
sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install g++-6;
158+
sudo -E apt-get -yq --no-install-suggests --no-install-recommends install g++-6;
159+
fi
160+
- if [ "${TRAVIS_OS_NAME}" == "linux" -a "${BUILD_32_BITS}" == "OFF" ]; then
161+
sudo -E apt-get -y --no-install-suggests --no-install-recommends install llvm-3.9-tools;
162+
sudo cp /usr/lib/llvm-3.9/bin/FileCheck /usr/local/bin/;
151163
fi
152164
- if [ "${BUILD_TYPE}" == "Coverage" -a "${TRAVIS_OS_NAME}" == "linux" ]; then
153165
PATH=~/.local/bin:${PATH};
@@ -171,7 +183,7 @@ install:
171183
fi
172184

173185
script:
174-
- cmake -DCMAKE_C_COMPILER=${C_COMPILER} -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DCMAKE_CXX_FLAGS="${EXTRA_FLAGS}" -DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON -DBENCHMARK_BUILD_32_BITS=${BUILD_32_BITS} ..
186+
- cmake -DCMAKE_C_COMPILER=${C_COMPILER} -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DCMAKE_CXX_FLAGS="${EXTRA_FLAGS}" -DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON -DBENCHMARK_BUILD_32_BITS=${BUILD_32_BITS} ${EXTRA_OPTIONS} ..
175187
- make
176188
- ctest -C ${BUILD_TYPE} --output-on-failure
177189
- bazel test -c dbg --define google_benchmark.have_regex=posix --announce_rc --verbose_failures --test_output=errors --keep_going //test/...

CMakeLists.txt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,48 @@ option(BENCHMARK_DOWNLOAD_DEPENDENCIES "Allow the downloading and in-tree buildi
2727
# in cases where it is not possible to build or find a valid version of gtest.
2828
option(BENCHMARK_ENABLE_GTEST_TESTS "Enable building the unit tests which depend on gtest" ON)
2929

30+
set(ENABLE_ASSEMBLY_TESTS_DEFAULT OFF)
31+
function(should_enable_assembly_tests)
32+
if(CMAKE_BUILD_TYPE)
33+
string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_LOWER)
34+
if (${CMAKE_BUILD_TYPE_LOWER} MATCHES "coverage")
35+
# FIXME: The --coverage flag needs to be removed when building assembly
36+
# tests for this to work.
37+
return()
38+
endif()
39+
endif()
40+
if (MSVC)
41+
return()
42+
elseif(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
43+
return()
44+
elseif(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
45+
# FIXME: Make these work on 32 bit builds
46+
return()
47+
elseif(BENCHMARK_BUILD_32_BITS)
48+
# FIXME: Make these work on 32 bit builds
49+
return()
50+
endif()
51+
find_program(LLVM_FILECHECK_EXE FileCheck)
52+
if (LLVM_FILECHECK_EXE)
53+
set(LLVM_FILECHECK_EXE "${LLVM_FILECHECK_EXE}" CACHE PATH "llvm filecheck" FORCE)
54+
message(STATUS "LLVM FileCheck Found: ${LLVM_FILECHECK_EXE}")
55+
else()
56+
message(STATUS "Failed to find LLVM FileCheck")
57+
return()
58+
endif()
59+
set(ENABLE_ASSEMBLY_TESTS_DEFAULT ON PARENT_SCOPE)
60+
endfunction()
61+
should_enable_assembly_tests()
62+
63+
# This option disables the building and running of the assembly verification tests
64+
option(BENCHMARK_ENABLE_ASSEMBLY_TESTS "Enable building and running the assembly tests"
65+
${ENABLE_ASSEMBLY_TESTS_DEFAULT})
66+
3067
# Make sure we can import out CMake functions
3168
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules")
3269
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
3370

71+
3472
# Read the git tags to determine the project version
3573
include(GetGitVersion)
3674
get_git_version(GIT_VERSION)

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ IRC channel: https://freenode.net #googlebenchmark
1414

1515
[Additional Tooling Documentation](docs/tools.md)
1616

17+
[Assembly Testing Documentation](docs/AssemblyTests.md)
18+
1719

1820
## Building
1921

docs/AssemblyTests.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Assembly Tests
2+
3+
The Benchmark library provides a number of functions whose primary
4+
purpose in to affect assembly generation, including `DoNotOptimize`
5+
and `ClobberMemory`. In addition there are other functions,
6+
such as `KeepRunning`, for which generating good assembly is paramount.
7+
8+
For these functions it's important to have tests that verify the
9+
correctness and quality of the implementation. This requires testing
10+
the code generated by the compiler.
11+
12+
This document describes how the Benchmark library tests compiler output,
13+
as well as how to properly write new tests.
14+
15+
16+
## Anatomy of a Test
17+
18+
Writing a test has two steps:
19+
20+
* Write the code you want to generate assembly for.
21+
* Add `// CHECK` lines to match against the verified assembly.
22+
23+
Example:
24+
```c++
25+
26+
// CHECK-LABEL: test_add:
27+
extern "C" int test_add() {
28+
extern int ExternInt;
29+
return ExternInt + 1;
30+
31+
// CHECK: movl ExternInt(%rip), %eax
32+
// CHECK: addl %eax
33+
// CHECK: ret
34+
}
35+
36+
```
37+
38+
#### LLVM Filecheck
39+
40+
[LLVM's Filecheck](https://llvm.org/docs/CommandGuide/FileCheck.html)
41+
is used to test the generated assembly against the `// CHECK` lines
42+
specified in the tests source file. Please see the documentation
43+
linked above for information on how to write `CHECK` directives.
44+
45+
#### Tips and Tricks:
46+
47+
* Tests should match the minimal amount of output required to establish
48+
correctness. `CHECK` directives don't have to match on the exact next line
49+
after the previous match, so tests should omit checks for unimportant
50+
bits of assembly. ([`CHECK-NEXT`](https://llvm.org/docs/CommandGuide/FileCheck.html#the-check-next-directive)
51+
can be used to ensure a match occurs exactly after the previous match).
52+
53+
* The tests are compiled with `-O3 -g0`. So we're only testing the
54+
optimized output.
55+
56+
* The assembly output is further cleaned up using `tools/strip_asm.py`.
57+
This removes comments, assembler directives, and unused labels before
58+
the test is run.
59+
60+
* The generated and stripped assembly file for a test is output under
61+
`<build-directory>/test/<test-name>.s`
62+
63+
* Filecheck supports using [`CHECK` prefixes](https://llvm.org/docs/CommandGuide/FileCheck.html#cmdoption-check-prefixes)
64+
to specify lines that should only match in certain situations.
65+
The Benchmark tests use `CHECK-CLANG` and `CHECK-GNU` for lines that
66+
are only expected to match Clang or GCC's output respectively. Normal
67+
`CHECK` lines match against all compilers. (Note: `CHECK-NOT` and
68+
`CHECK-LABEL` are NOT prefixes. They are versions of non-prefixed
69+
`CHECK` lines)
70+
71+
* Use `extern "C"` to disable name mangling for specific functions. This
72+
makes them easier to name in the `CHECK` lines.
73+
74+
75+
## Problems Writing Portable Tests
76+
77+
Writing tests which check the code generated by a compiler are
78+
inherently non-portable. Different compilers and even different compiler
79+
versions may generate entirely different code. The Benchmark tests
80+
must tolerate this.
81+
82+
LLVM Filecheck provides a number of mechanisms to help write
83+
"more portable" tests; including [matching using regular expressions](https://llvm.org/docs/CommandGuide/FileCheck.html#filecheck-pattern-matching-syntax),
84+
allowing the creation of [named variables](https://llvm.org/docs/CommandGuide/FileCheck.html#filecheck-variables)
85+
for later matching, and [checking non-sequential matches](https://llvm.org/docs/CommandGuide/FileCheck.html#the-check-dag-directive).
86+
87+
#### Capturing Variables
88+
89+
For example, say GCC stores a variable in a register but Clang stores
90+
it in memory. To write a test that tolerates both cases we "capture"
91+
the destination of the store, and then use the captured expression
92+
to write the remainder of the test.
93+
94+
```c++
95+
// CHECK-LABEL: test_div_no_op_into_shr:
96+
extern "C" void test_div_no_op_into_shr(int value) {
97+
int divisor = 2;
98+
benchmark::DoNotOptimize(divisor); // hide the value from the optimizer
99+
return value / divisor;
100+
101+
// CHECK: movl $2, [[DEST:.*]]
102+
// CHECK: idivl [[DEST]]
103+
// CHECK: ret
104+
}
105+
```
106+
107+
#### Using Regular Expressions to Match Differing Output
108+
109+
Often tests require testing assembly lines which may subtly differ
110+
between compilers or compiler versions. A common example of this
111+
is matching stack frame addresses. In this case regular expressions
112+
can be used to match the differing bits of output. For example:
113+
114+
```c++
115+
int ExternInt;
116+
struct Point { int x, y, z; };
117+
118+
// CHECK-LABEL: test_store_point:
119+
extern "C" void test_store_point() {
120+
Point p{ExternInt, ExternInt, ExternInt};
121+
benchmark::DoNotOptimize(p);
122+
123+
// CHECK: movl ExternInt(%rip), %eax
124+
// CHECK: movl %eax, -{{[0-9]+}}(%rsp)
125+
// CHECK: movl %eax, -{{[0-9]+}}(%rsp)
126+
// CHECK: movl %eax, -{{[0-9]+}}(%rsp)
127+
// CHECK: ret
128+
}
129+
```
130+
131+
## Current Requirements and Limitations
132+
133+
The tests require Filecheck to be installed along the `PATH` of the
134+
build machine. Otherwise the tests will be disabled.
135+
136+
Additionally, as mentioned in the previous section, codegen tests are
137+
inherently non-portable. Currently the tests are limited to:
138+
139+
* x86_64 targets.
140+
* Compiled with GCC or Clang
141+
142+
Further work could be done, at least on a limited basis, to extend the
143+
tests to other architectures and compilers (using `CHECK` prefixes).
144+
145+
Furthermore, the tests fail for builds which specify additional flags
146+
that modify code generation, including `--coverage` or `-fsanitize=`.
147+

include/benchmark/benchmark.h

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -303,15 +303,20 @@ BENCHMARK_UNUSED static int stream_init_anchor = InitializeStreams();
303303
// See: https://youtu.be/nXaxk27zwlk?t=2441
304304
#ifndef BENCHMARK_HAS_NO_INLINE_ASSEMBLY
305305
template <class Tp>
306-
inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp const& value) {
307-
// Clang doesn't like the 'X' constraint on `value` and certain GCC versions
308-
// don't like the 'g' constraint. Attempt to placate them both.
306+
inline BENCHMARK_ALWAYS_INLINE
307+
void DoNotOptimize(Tp const& value) {
308+
asm volatile("" : : "r,m"(value) : "memory");
309+
}
310+
311+
template <class Tp>
312+
inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp& value) {
309313
#if defined(__clang__)
310-
asm volatile("" : : "g"(value) : "memory");
314+
asm volatile("" : "+r,m"(value) : : "memory");
311315
#else
312-
asm volatile("" : : "i,r,m"(value) : "memory");
316+
asm volatile("" : "+m,r"(value) : : "memory");
313317
#endif
314318
}
319+
315320
// Force the compiler to flush pending writes to global memory. Acts as an
316321
// effective read/write barrier
317322
inline BENCHMARK_ALWAYS_INLINE void ClobberMemory() {

test/AssemblyTests.cmake

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
3+
set(ASM_TEST_FLAGS "")
4+
check_cxx_compiler_flag(-O3 BENCHMARK_HAS_O3_FLAG)
5+
if (BENCHMARK_HAS_O3_FLAG)
6+
list(APPEND ASM_TEST_FLAGS -O3)
7+
endif()
8+
9+
check_cxx_compiler_flag(-g0 BENCHMARK_HAS_G0_FLAG)
10+
if (BENCHMARK_HAS_G0_FLAG)
11+
list(APPEND ASM_TEST_FLAGS -g0)
12+
endif()
13+
14+
check_cxx_compiler_flag(-fno-stack-protector BENCHMARK_HAS_FNO_STACK_PROTECTOR_FLAG)
15+
if (BENCHMARK_HAS_FNO_STACK_PROTECTOR_FLAG)
16+
list(APPEND ASM_TEST_FLAGS -fno-stack-protector)
17+
endif()
18+
19+
split_list(ASM_TEST_FLAGS)
20+
string(TOUPPER "${CMAKE_CXX_COMPILER_ID}" ASM_TEST_COMPILER)
21+
22+
macro(add_filecheck_test name)
23+
cmake_parse_arguments(ARG "" "" "CHECK_PREFIXES" ${ARGV})
24+
add_library(${name} OBJECT ${name}.cc)
25+
set_target_properties(${name} PROPERTIES COMPILE_FLAGS "-S ${ASM_TEST_FLAGS}")
26+
set(ASM_OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/${name}.s")
27+
add_custom_target(copy_${name} ALL
28+
COMMAND ${PROJECT_SOURCE_DIR}/tools/strip_asm.py
29+
$<TARGET_OBJECTS:${name}>
30+
${ASM_OUTPUT_FILE}
31+
BYPRODUCTS ${ASM_OUTPUT_FILE})
32+
add_dependencies(copy_${name} ${name})
33+
if (NOT ARG_CHECK_PREFIXES)
34+
set(ARG_CHECK_PREFIXES "CHECK")
35+
endif()
36+
foreach(prefix ${ARG_CHECK_PREFIXES})
37+
add_test(NAME run_${name}_${prefix}
38+
COMMAND
39+
${LLVM_FILECHECK_EXE} ${name}.cc
40+
--input-file=${ASM_OUTPUT_FILE}
41+
--check-prefixes=CHECK,CHECK-${ASM_TEST_COMPILER}
42+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
43+
endforeach()
44+
endmacro()
45+

test/BUILD

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,5 @@ cc_library(
4444
] + (
4545
["@com_google_googletest//:gtest_main"] if (test_src in NEEDS_GTEST_MAIN) else []
4646
),
47-
) for test_src in glob(["*_test.cc"])]
47+
# FIXME: Add support for assembly tests to bazel.
48+
) for test_src in glob(["*_test.cc"], exclude = ["*_assembly_test.cc"])]

0 commit comments

Comments
 (0)