From 3e770b630ba3f9ea76bd84a98d4b50f6cef1f5e4 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Thu, 25 Jul 2024 21:02:54 -0400 Subject: [PATCH 1/6] generator --- .github/workflows/main.yml | 120 +-- README.md | 15 +- docs.in/reference/generator.md | 88 ++ docs.in/reference/policy-std_rtti.md | 14 +- docs/reference/policy-std_rtti.md | 14 +- examples/README.cpp | 15 +- examples/static-slots/README.md | 74 ++ examples/static-slots/animals.cpp | 55 ++ examples/static-slots/animals.hpp | 33 + examples/static-slots/app.cpp | 123 +++ .../static-slots/generate-static-slots.cpp | 25 + include/yorel/yomm2/core.hpp | 73 +- include/yorel/yomm2/detail.hpp | 68 +- include/yorel/yomm2/{ => detail}/compiler.hpp | 811 ++++++++++-------- include/yorel/yomm2/detail/forward.hpp | 20 + include/yorel/yomm2/generator.hpp | 604 +++++++++++++ include/yorel/yomm2/policy.hpp | 70 +- src/yomm2.cpp | 11 +- tests/benchmarks.cpp | 6 +- tests/benchmarks_parameters.hpp | 4 + tests/blackbox.cpp | 96 ++- tests/compiler.cpp | 246 ++---- tests/rdtsc-benchmark.cpp | 393 +++++++++ tests/run-rdtsc-benchmark | 17 + tests/runtime.cpp | 797 ----------------- tests/test_core.cpp | 30 + tests/test_generator.cpp | 230 +++++ vcpkg-configuration.json | 14 + vcpkg.json | 13 + 29 files changed, 2571 insertions(+), 1508 deletions(-) create mode 100644 docs.in/reference/generator.md create mode 100644 examples/static-slots/README.md create mode 100644 examples/static-slots/animals.cpp create mode 100644 examples/static-slots/animals.hpp create mode 100644 examples/static-slots/app.cpp create mode 100644 examples/static-slots/generate-static-slots.cpp rename include/yorel/yomm2/{ => detail}/compiler.hpp (61%) create mode 100644 include/yorel/yomm2/detail/forward.hpp create mode 100644 include/yorel/yomm2/generator.hpp create mode 100644 tests/rdtsc-benchmark.cpp create mode 100755 tests/run-rdtsc-benchmark delete mode 100644 tests/runtime.cpp create mode 100644 tests/test_generator.cpp create mode 100644 vcpkg-configuration.json create mode 100644 vcpkg.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1eb3e04c..5ed2389f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,110 +8,67 @@ on: [push, pull_request, workflow_dispatch] jobs: ubuntu: runs-on: ubuntu-latest - container: - ghcr.io/jll63/yomm2-gha:latest strategy: matrix: - compiler: [clang++-15, clang++-16, g++-12, g++-13] + compiler: [clang++, g++] standard: [17, 20] - config: [Debug, Release] + config: [debug, release] steps: - uses: actions/checkout@v4 - name: Configure run: | - mkdir build - cd build - cmake .. -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.config }} -DCMAKE_CXX_STANDARD=${{ matrix.standard }} -DYOMM2_ENABLE_TESTS=1 -DYOMM2_ENABLE_BENCHMARKS=1 + git clone https://github.com/microsoft/vcpkg "$HOME/vcpkg" + export VCPKG_ROOT="$HOME/vcpkg" + cmake --preset ci-${{ matrix.config }} -B build -DCMAKE_CXX_STANDARD=${{ matrix.standard }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} - name: Build - run: VERBOSE=1 cmake --build build -j 8 + run: cmake --build build -j 8 - name: Unit Tests run: | - cd build - YOMM2_TRACE=1 ctest --rerun-failed --output-on-failure . + ctest --output-on-failure --test-dir build - name: Examples run: | - cd build/examples - YOMM2_TRACE=1 find . -type f -executable -exec {} \; + find build -type f -executable -exec {} \; ubuntu-shared: runs-on: ubuntu-latest - container: - ghcr.io/jll63/yomm2-gha:latest strategy: matrix: - compiler: [clang++-16, g++-13] + compiler: [clang++, g++] check_abi_compatibility: [OFF, ON] steps: - uses: actions/checkout@v4 - name: Configure run: | - mkdir build - cd build - cmake .. -DCMAKE_BUILD_TYPE=Release -DYOMM2_SHARED=1 -DYOMM2_CHECK_ABI_COMPATIBILITY=${{ matrix.check_abi_compatibility }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} -DCMAKE_CXX_STANDARD=17 -DYOMM2_ENABLE_TESTS=1 + git clone https://github.com/microsoft/vcpkg "$HOME/vcpkg" + export VCPKG_ROOT="$HOME/vcpkg" + cmake --preset ci-release -B build -DYOMM2_SHARED=1 -DYOMM2_CHECK_ABI_COMPATIBILITY=${{ matrix.check_abi_compatibility }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} - name: Build - run: VERBOSE=1 cmake --build build + run: cmake --build build -j 8 - name: Unit Tests - run: | - cd build - ctest --rerun-failed --output-on-failure . + run: YOMM2_TRACE=1 ctest --output-on-failure --test-dir build - name: Examples - run: | - pwd - cd build/examples - find . -type f -executable -exec {} \; - ubuntu-cmake: - runs-on: ubuntu-latest - container: - ghcr.io/jll63/yomm2-gha:latest - strategy: - matrix: - compiler: [clang++-16, g++-13] - steps: - - uses: actions/checkout@v4 - - name: Add git, remove Boost and Google Benchmarks - run: | - apt-get update - apt-get install -y git - apt-get remove -y libbenchmark-dev libboost-all-dev - - name: Configure - run: | - mkdir build - cd build - cmake .. -DCMAKE_BUILD_TYPE=Release -DYOMM2_SHARED=1 -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} -DCMAKE_CXX_STANDARD=17 -DYOMM2_DOWNLOAD_DEPENDENCIES=1 -DYOMM2_ENABLE_TESTS=1 - - name: Build - run: VERBOSE=1 cmake --build build - - name: Unit Tests - run: | - cd build - ctest --rerun-failed --output-on-failure . - - name: Examples - run: | - pwd - cd build/examples - find . -type f -executable -exec {} \; + run: YOMM2_TRACE=1 find build -type f -executable -exec {} \; windows: runs-on: windows-latest strategy: matrix: - config: [Debug, Release] + config: [debug, release] standard: [17, 20] steps: - uses: actions/checkout@v4 - uses: ilammy/msvc-dev-cmd@v1 - name: Configure run: | - mkdir build - cd build - cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=${{ matrix.config }} -DCMAKE_CXX_STANDARD=${{ matrix.standard }} -DYOMM2_DOWNLOAD_DEPENDENCIES=1 -DYOMM2_ENABLE_TESTS=1 + git clone https://github.com/microsoft/vcpkg "$env:USERPROFILE/vcpkg" + $env:VCPKG_ROOT = "$env:USERPROFILE/vcpkg" + cmake --preset ci-${{ matrix.config }} -DCMAKE_CXX_STANDARD=${{ matrix.standard }} -B build - name: Build run: | cl - cd build - nmake + cmake --build build -j 8 - name: Unit Tests run: | - cd build $env:YOMM2_TRACE = 1 - ctest --rerun-failed --output-on-failure . + ctest --output-on-failure --test-dir build $global:LASTEXITCODE = 0 windows-shared: strategy: @@ -125,43 +82,34 @@ jobs: vsversion: 2022 - name: Configure run: | - mkdir build - cd build - cmake .. -G "NMake Makefiles" -DYOMM2_SHARED=1 -DCMAKE_BUILD_TYPE=Release -DYOMM2_CHECK_ABI_COMPATIBILITY=${{ matrix.check_abi_compatibility }} -DCMAKE_CXX_STANDARD=17 -DYOMM2_DOWNLOAD_DEPENDENCIES=1 -DYOMM2_ENABLE_TESTS=1 + git clone https://github.com/microsoft/vcpkg "$env:USERPROFILE/vcpkg" + $env:VCPKG_ROOT = "$env:USERPROFILE/vcpkg" + cmake --preset ci-release -DYOMM2_SHARED=1 -DYOMM2_CHECK_ABI_COMPATIBILITY=${{ matrix.check_abi_compatibility }} -B build - name: Build - run: | - cd build - nmake + run: cmake --build build -j 8 - name: Unit Tests run: | - cd build $env:YOMM2_TRACE = 1 - $env:PATH += ";" + (Get-Item .).FullName + "/src" - ctest --rerun-failed --output-on-failure . + $env:PATH += ";" + (Get-Item .).FullName + "/build/src" + ctest --output-on-failure --test-dir build $global:LASTEXITCODE = 0 mac: runs-on: macOS-latest strategy: matrix: - # config: [Debug, Release, ReleaseDebug, DebugRelease] - config: [Debug, Release] + config: [debug, release] steps: - uses: actions/checkout@v4 - name: Configure run: | - brew install boost - mkdir build - cd build - cmake .. -DCMAKE_BUILD_TYPE=${{ matrix.config }} -DCMAKE_CXX_STANDARD=${{ matrix.standard }} -DYOMM2_DOWNLOAD_DEPENDENCIES=1 -DYOMM2_ENABLE_TESTS=1 + git clone https://github.com/microsoft/vcpkg "$HOME/vcpkg" + export VCPKG_ROOT="$HOME/vcpkg" + cmake --preset ci-${{ matrix.config }} -B build - name: Build - run: | - cd build - make + run: cmake --build build -j 8 - name: Unit Tests run: | - cd build - ctest --rerun-failed --output-on-failure . + ctest --output-on-failure --test-dir build - name: Examples run: | - cd build/examples - find . -type f -perm +0111 -exec {} \; + find build -type f -perm +0111 -exec {} \; diff --git a/README.md b/README.md index 42f979a6..44ab1dc6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # YOMM2 + [![CI](https://github.com/jll63/yomm2/actions/workflows/main.yml/badge.svg)](https://github.com/jll63/yomm2/actions/workflows/main.yml) [![ConanCenter package](https://repology.org/badge/version-for-repo/conancenter/yomm2.svg)](https://repology.org/project/yomm2/versions) [![Vcpkg package](https://repology.org/badge/version-for-repo/vcpkg/yomm2.svg)](https://repology.org/project/yomm2/versions) @@ -11,6 +12,7 @@ inspired by the papers by Peter Pirkelbauer, Yuriy Solodkyy, and Bjarne Stroustrup. - [YOMM2](#yomm2) + - [](#) - [TL;DR](#tldr) - [Open Methods in a Nutshell](#open-methods-in-a-nutshell) - [Cross-cutting Concerns and the Expression Problem](#cross-cutting-concerns-and-the-expression-problem) @@ -225,14 +227,8 @@ ctest --test-dir build.yomm2 ``` YOMM2 uses the following Boost libraries: -* Preprocessor, DynamicBitset: included by YOMM2 headers -* Boost.Test: only used to run the test suite - -If these libraries are already available on your machine, and they can be found -by `cmake`, they will be used. In this case, make sure that the pre-installed -libraries are at version 1.74 or above. You can also tell the cmake script to -attempt to download and build the dependencies by setting -`YOMM2_DOWNLOAD_DEPENDENCIES` to `ON`. +* Mp11, Preprocessor, DynamicBitset: included by YOMM2 headers +* Test: only used to run the test suite If you want to run the benchmarks (and in this case you really want a release build): @@ -242,8 +238,7 @@ cmake -S yomm2 -B build.yomm2 -DYOMM2_ENABLE_TESTS=1 -DYOMM2_ENABLE_BENCHMARKS=1 ./build.yomm2/tests/benchmarks ``` The benchmarks use the [Google benchmark](https://github.com/google/benchmark) -library. Again, if it is not found, and `YOMM2_DOWNLOAD_DEPENDENCIES` is `ON`, -it will be built from its source. +library. If you like YOMM2, and you want to install it, either system-wide: diff --git a/docs.in/reference/generator.md b/docs.in/reference/generator.md new file mode 100644 index 00000000..d9b8900e --- /dev/null +++ b/docs.in/reference/generator.md @@ -0,0 +1,88 @@ +entry: generator +headers: yorel/yomm2/generator.hpp + +```c++ +class generator; +``` + +This mechanism generates header files that can be included in a project to speed +up method dispatch. + +Like virtual functions, methods occupy slots in v-tables associated to classes. +Unlike virtual functions, the slots cannot be determined by looking at a single +translation unit; the entire program has to be examined before the slots +are known. By default, method dispatch reads the slots from variables set by +->`update`. The additional reads put open methods at a disadvantage, compared to +ordinary virtual functions. + +`write_static_slots` generates C++ code that enables method dispatch to use +"static" slots - i.e. slots known at compile time. Static slots should be made +visible (typically by means including the generated code) before these methods +are called. A program may contain a mixture of methods that use static slots, +and methods that do not. However, this should be consistent across translation +units; failing to ensure this is a ODR violation. + +Using static slots shaves off 2*N-1 memory reads from a method call, where N is +the number of virtual parameters in a method. A 1-method call via a +->`virtual_ptr`, using static offsets, takes 2 instructions on a x64 CPU, the +same as a virtual function call, but one fewer memory read. See the example for +assembly listings. + +The code generated by `write_static_slots` requires that types used by the +method (parameter types, return type and method key) to be known. This is easy +when using static slots for specific methods - by including the generated static +offsets for the method just before the method declaration; it is more +challenging when using static offsets for an entire program. +`write_forward_declarations` attempts to generate suitable forward declarations, +but it has limitations. See its documentation. + +## Member functions + +| Name | Description | +| --------------------------------------------------------- | --------------------------------------------------- | +| [write_static_slots](#write_static_slots) | write static slots for a method or a policy | +| [add_forward_declaration](#add_forward_declaration) | register types for forward declaration generation | +| [write_forward_declarations](#write_forward_declarations) | write forward declarations for the registered types | + +## write_static_slots + +```c++ +template void write_static_slots(std::ostream& os) const; (1) +template void write_static_slots(std::ostream& os) const; (2) +``` +Add the method to the policy's method list. + +1) Write static slots for a single method to `os`. +2) Write static slots for all the methods in a policy to `os`. + +## add_forward_declaration + +```c++ +void add_forward_declaration(std::string_view decl); (1) +void add_forward_declaration(const std::type_info& type); (2) +template void add_forward_declaration(); (3) +template void add_forward_declarations(); (4) +``` +1) Add `decl` to the list of declarations. +2) Add a declaration for `type` to the list of declarations. +3) Equivalent to `add_forward_declaration(typeid(T))`. +4) Add declarations for the return, parameter and key types used by all the + methods in `Policy`. + +(2), (3) and (4) use `boost::demangle` to extract type names. The result is not +guaranteed, as it depends on the availability and the output of a ABI specific +demangling mechanism. Note that no attempt is made at extracting templates, +because it is impossible to guess the tewmplate parameter list. + +## write_forward_declarations + +```c++ +void write_forward_declarations(std::ostream& os) const; +``` + +Write forward declarations for all the types extracted by +`add_forward_declaration(s)` to `os`. + +## Example + +See the [static-slots](https://github.com/jll63/yomm2/tree/master/examples/static-slots) example. diff --git a/docs.in/reference/policy-std_rtti.md b/docs.in/reference/policy-std_rtti.md index dc590dea..40fa55c6 100644 --- a/docs.in/reference/policy-std_rtti.md +++ b/docs.in/reference/policy-std_rtti.md @@ -9,13 +9,13 @@ Implement `rtti` using the RTTI facilities provided by standard C++. ## Static member functions -| Name | Description | -| --------------------------------------------- | ----------------------------------------------- | -| type_id [**static_type\**](#static_type) | return a `type_id` for `T` | -| type_id [**dynamic_type\**](#dynamic_type) | return a `type_id` for an object's dynamic type | -| void [**type_name\**](#type_name) | write a description of `type_id` to a stream | -| *unspecified* [**type_index**](#type_index) | return a unique key for a `type_id` | -| D [**dynamic_cast_ref\**](#dynamic_cast_ref) | cast from base class to derived class | +| Name | Description | +| -------------------------------------------------- | ----------------------------------------------- | +| type_id [**static_type\**](#static_type) | return a `type_id` for `T` | +| type_id [**dynamic_type\**](#dynamic_type) | return a `type_id` for an object's dynamic type | +| void [**type_name\**](#type_name) | write a description of `type_id` to a stream | +| *unspecified* [**type_index**](#type_index) | return a unique key for a `type_id` | +| D [**dynamic_cast_ref\**](#dynamic_cast_ref) | cast from base class to derived class | ### static_type diff --git a/docs/reference/policy-std_rtti.md b/docs/reference/policy-std_rtti.md index 7a0fa1ca..75e5a120 100644 --- a/docs/reference/policy-std_rtti.md +++ b/docs/reference/policy-std_rtti.md @@ -9,13 +9,13 @@ Implement `rtti` using the RTTI facilities provided by standard C++. ## Static member functions -| Name | Description | -| --------------------------------------------- | ----------------------------------------------- | -| type_id [**static_type\**](#static_type) | return a `type_id` for `T` | -| type_id [**dynamic_type\**](#dynamic_type) | return a `type_id` for an object's dynamic type | -| void [**type_name\**](#type_name) | write a description of `type_id` to a stream | -| *unspecified* [**type_index**](#type_index) | return a unique key for a `type_id` | -| D [**dynamic_cast_ref\**](#dynamic_cast_ref) | cast from base class to derived class | +| Name | Description | +| -------------------------------------------------- | ----------------------------------------------- | +| type_id [**static_type\**](#static_type) | return a `type_id` for `T` | +| type_id [**dynamic_type\**](#dynamic_type) | return a `type_id` for an object's dynamic type | +| void [**type_name\**](#type_name) | write a description of `type_id` to a stream | +| *unspecified* [**type_index**](#type_index) | return a unique key for a `type_id` | +| D [**dynamic_cast_ref\**](#dynamic_cast_ref) | cast from base class to derived class | ### static_type diff --git a/examples/README.cpp b/examples/README.cpp index f9cf641a..dc178552 100644 --- a/examples/README.cpp +++ b/examples/README.cpp @@ -1,6 +1,7 @@ /*** # YOMM2 + [![CI](https://github.com/jll63/yomm2/actions/workflows/main.yml/badge.svg)](https://github.com/jll63/yomm2/actions/workflows/main.yml) [![ConanCenter package](https://repology.org/badge/version-for-repo/conancenter/yomm2.svg)](https://repology.org/project/yomm2/versions) [![Vcpkg package](https://repology.org/badge/version-for-repo/vcpkg/yomm2.svg)](https://repology.org/project/yomm2/versions) @@ -11,6 +12,7 @@ inspired by the papers by Peter Pirkelbauer, Yuriy Solodkyy, and Bjarne Stroustrup. - [YOMM2](#yomm2) + - [](#) - [TL;DR](#tldr) - [Open Methods in a Nutshell](#open-methods-in-a-nutshell) - [Cross-cutting Concerns and the Expression Problem](#cross-cutting-concerns-and-the-expression-problem) @@ -232,14 +234,8 @@ ctest --test-dir build.yomm2 ``` YOMM2 uses the following Boost libraries: -* Preprocessor, DynamicBitset: included by YOMM2 headers -* Boost.Test: only used to run the test suite - -If these libraries are already available on your machine, and they can be found -by `cmake`, they will be used. In this case, make sure that the pre-installed -libraries are at version 1.74 or above. You can also tell the cmake script to -attempt to download and build the dependencies by setting -`YOMM2_DOWNLOAD_DEPENDENCIES` to `ON`. +* Mp11, Preprocessor, DynamicBitset: included by YOMM2 headers +* Test: only used to run the test suite If you want to run the benchmarks (and in this case you really want a release build): @@ -249,8 +245,7 @@ cmake -S yomm2 -B build.yomm2 -DYOMM2_ENABLE_TESTS=1 -DYOMM2_ENABLE_BENCHMARKS=1 ./build.yomm2/tests/benchmarks ``` The benchmarks use the [Google benchmark](https://github.com/google/benchmark) -library. Again, if it is not found, and `YOMM2_DOWNLOAD_DEPENDENCIES` is `ON`, -it will be built from its source. +library. If you like YOMM2, and you want to install it, either system-wide: diff --git a/examples/static-slots/README.md b/examples/static-slots/README.md new file mode 100644 index 00000000..e2a763c4 --- /dev/null +++ b/examples/static-slots/README.md @@ -0,0 +1,74 @@ +# Maximum performance with static slots and `virtual_ptr` + +As described in the documentation of `generator`, static slots, in combination +with `virtual_ptr`, make open methods faster than virtual functions. This +example demonstrates how to use the feature and organize a cmake-based project. + + +## Project structure + +The project consists of a collection of domain classes and methods, a generator +program, and an application. + +The domain classes are a subset (`Animal`, `Cat` and `Dog`) of the synopsis +example. They are declared in a [header](animals.hpp), along with two methods +(`kick` and `meet`). [animal.cpp](animal.cpp) provides the implementation of the +classes and methods. For comparison, the Animal hierarchy also has a `kick` +virtual function. + +`animals.hpp` contains a conditional include directive: + +```c++ +#if __has_include("slots.hpp") +#include "slots.hpp" +#endif +``` + +This header is not source code, and it is not checked in the repository; instead +it is generated by + + +## Building + +## Performance + +virtual function call + +```asm + mov rax, qword ptr [rdi] + jmp qword ptr [rax + 16] +``` + +method, via virtual_ptr, dynamic offsets +```asm + mov rax, qword ptr [rip + method)>::fn] + mov rax, qword ptr [rsi + 8*rax] + jmp rax +``` + +method, via virtual_ptr, static offsets +```asm + mov rax, qword ptr [rsi + 16] + jmp rax +``` + +2-method, virtual_ptr, dynamic offsets +```asm + mov rax, qword ptr [rip + method, virtual_ptr)>::fn] + mov rax, qword ptr [rsi + 8*rax] + mov r8, qword ptr [rip + method, virtual_ptr)>::fn+8] + mov r8, qword ptr [rcx + 8*r8] + imul r8, qword ptr [rip + method, virtual_ptr)>::fn+16] + mov rax, qword ptr [rax + 8*r8] + jmp rax +``` + + +2-method, virtual_ptr, static offsets +```asm + mov rax, qword ptr [rsi] + mov r8, qword ptr [rcx + 8] + lea r8, [r8 + 2*r8] + mov rax, qword ptr [rax + 8*r8] + jmp rax +``` diff --git a/examples/static-slots/animals.cpp b/examples/static-slots/animals.cpp new file mode 100644 index 00000000..611e4efd --- /dev/null +++ b/examples/static-slots/animals.cpp @@ -0,0 +1,55 @@ +#include "animals.hpp" + +Animal::~Animal() { +} + +register_classes(Animal, Dog, Cat); + +define_method(void, kick, (virtual_ptr dog)) { + std::cout << "hiss\n"; +} + +define_method(void, kick, (virtual_ptr dog)) { + std::cout << "bark\n"; +} + +define_method(void, pet, (virtual_ptr dog)) { + std::cout << "purr\n"; +} + +define_method(void, pet, (virtual_ptr dog)) { + std::cout << "wag tail\n"; +} + +// 'meet' catch-all implementation. +define_method(void, meet, (virtual_ptr, virtual_ptr)) { + std::cout << "ignore\n"; +} + +// Add definitions for specific pairs of animals. +define_method(void, meet, (virtual_ptr dog1, virtual_ptr dog2)) { + std::cout << "wag tail\n"; +} + +define_method(void, meet, (virtual_ptr dog, virtual_ptr cat)) { + std::cout << "chase\n"; +} + +define_method(void, meet, (virtual_ptr cat, virtual_ptr dog)) { + std::cout << "run\n"; +} + +// 'mate' catch-all implementation. +define_method(void, mate, (virtual_ptr, virtual_ptr)) { + std::cout << "error C1001\n"; +} + +// Add definitions for specific pairs of animals. +define_method(void, mate, (virtual_ptr dog1, virtual_ptr dog2)) { + std::cout << "puppy\n"; +} + +// Add definitions for specific pairs of animals. +define_method(void, mate, (virtual_ptr cat1, virtual_ptr cat2)) { + std::cout << "kitten\n"; +} diff --git a/examples/static-slots/animals.hpp b/examples/static-slots/animals.hpp new file mode 100644 index 00000000..4ea931ce --- /dev/null +++ b/examples/static-slots/animals.hpp @@ -0,0 +1,33 @@ +#ifndef ANIMALS_HPP +#define ANIMALS_HPP + +#include +#include + +using yorel::yomm2::virtual_ptr; + +struct Animal { + virtual ~Animal(); + virtual void kick() = 0; +}; + +struct Dog : Animal { + virtual void kick() { + } +}; + +struct Cat : Animal { + virtual void kick() { + } +}; + +declare_method(void, kick, (virtual_ptr)); +declare_method(void, pet, (virtual_ptr)); +declare_method(void, meet, (virtual_ptr, virtual_ptr)); +declare_method(void, mate, (virtual_ptr, virtual_ptr)); + +#if __has_include("slots.hpp") +#include "slots.hpp" +#endif + +#endif diff --git a/examples/static-slots/app.cpp b/examples/static-slots/app.cpp new file mode 100644 index 00000000..55273492 --- /dev/null +++ b/examples/static-slots/app.cpp @@ -0,0 +1,123 @@ +#include "animals.hpp" +#include + +#include + +// https://godbolt.org/z/rf1bjb544 + +void call_vf(Animal& a) { + // yardstick + a.kick(); +} + +void call_kick(virtual_ptr animal) { + // using dynamic offsets + kick(animal); +} + +void call_pet(virtual_ptr animal) { + // using static offsets + pet(animal); +} + +void call_meet(virtual_ptr a1, virtual_ptr a2) { + // using dynamic offsets + meet(a1, a2); +} + +void call_mate(virtual_ptr a1, virtual_ptr a2) { + // using static offsets + mate(a1, a2); +} + +#include "tables.hpp" + +template +auto collect_slots() { + std::vector slots_strides; + + for (auto& method : Policy::methods) { + std::copy_n( + method.slots_strides_ptr, method.arity(), + std::back_inserter(slots_strides)); + } + + return slots_strides; +} + +int main() { + using namespace yorel::yomm2; + + #ifdef CHECK + + auto compiler = update(); + + std::vector expected_slots_strides = + collect_slots(); + auto expected_dispatch_data = default_policy::dispatch_data; + + for (auto& cls : default_policy::classes) { + *cls.static_vptr = nullptr; + } + + #endif + + auto unpacked = generated(); + + #ifdef CHECK + + auto actual_slots_strides = collect_slots(); + + if (actual_slots_strides.size() != expected_slots_strides.size()) { + std::cerr << "slot count mismatch: " << actual_slots_strides.size() + << " != " << expected_slots_strides.size() << "\n"; + } + +#undef min + auto n = + std::min(actual_slots_strides.size(), expected_slots_strides.size()); + + for (size_t i = 0; i < n; ++i) { + if (actual_slots_strides[i] != expected_slots_strides[i]) { + std::cerr << "slot " << i << " mismatch: " << std::hex + << actual_slots_strides[i] + << " != " << expected_slots_strides[i] << "\n"; + } + } + + auto expected_size = expected_dispatch_data.size(); + auto actual_size = default_policy::dispatch_data.size(); + n = std::min( + default_policy::dispatch_data.size(), expected_dispatch_data.size()); + + if (actual_size != expected_size) { + std::cerr << "dispatch_data mismatch: " << actual_size + << " != " << expected_size << "\n"; + } + + // for (size_t i = 0; i < n; ++i) { + // if (default_policy::dispatch_data[i] != unpacked[i]) { + // std::cerr << "dispatch_data " << i << " : " << std::hex + // << default_policy::dispatch_data[i] + // << " != " << unpacked[i] << "\n"; + // } + // } + + #endif + + Animal&& felix = Cat(); + virtual_ptr cat = felix; + Animal&& snoopy = Dog(); + virtual_ptr dog = snoopy; + + // // our yardstick: an ordinary virtual function call + // felix.kick(); + + kick(cat); + pet(dog); + meet(cat, cat); + mate(dog, dog); + call_mate(dog, dog); + + return 0; +} diff --git a/examples/static-slots/generate-static-slots.cpp b/examples/static-slots/generate-static-slots.cpp new file mode 100644 index 00000000..27fc345a --- /dev/null +++ b/examples/static-slots/generate-static-slots.cpp @@ -0,0 +1,25 @@ +#include "animals.hpp" + +#include + +#include + +int main(int argc, char* argv[]) { + using namespace yorel::yomm2; + + detail::compiler comp; + comp.update(); + generator generator; + + std::ofstream slots(argv[1]); + generator + .write_static_slots))>( + slots) + .write_static_slots, virtual_ptr))>(slots); + + std::ofstream tables(argv[2]); + generator.encode_dispatch_data(comp, tables); + + return 0; +} diff --git a/include/yorel/yomm2/core.hpp b/include/yorel/yomm2/core.hpp index bc74ab61..6561cbdc 100644 --- a/include/yorel/yomm2/core.hpp +++ b/include/yorel/yomm2/core.hpp @@ -4,6 +4,8 @@ #include #include +#include + #include #pragma push_macro("min") @@ -51,8 +53,7 @@ template struct method; template -struct method - : detail::slots_strides_base>, detail::method_info { +struct method : detail::method_info { using self_type = method; using policy_type = Policy; using declared_argument_types = detail::types; @@ -68,8 +69,16 @@ struct method static constexpr auto arity = detail::arity; static_assert(arity > 0, "method must have at least one virtual argument"); + static size_t slots_strides[2 * arity - 1]; + // Slots followed by strides. No stride for first virtual argument. + // For 1-method: the offset of the method in the method table, which + // contains a pointer to a function. + // For multi-methods: the offset of the first virtual argument in the + // method table, which contains a pointer to the corresponding cell in + // the dispatch table, followed by the offset of the second argument and + // the stride in the second dimension, etc. + static method fn; - static function_pointer_type fake_definition; method(); @@ -181,10 +190,6 @@ struct method template method method::fn; -template -typename method::function_pointer_type - method::fake_definition; - template template typename method::next_type @@ -205,8 +210,7 @@ struct class_declaration> template using use_classes = typename detail::use_classes_aux< - detail::get_policy, - detail::remove_policy>::type; + detail::get_policy, detail::remove_policy>::type; // ----------------------------------------------------------------------------- // virtual_ptr @@ -424,8 +428,8 @@ inline auto final_virtual_ptr(Class& obj) { template method::method() { + this->slots_strides_ptr = slots_strides; this->name = detail::default_method_name(); - this->slots_strides_p = this->slots_strides; using virtual_type_ids = detail::type_id_list< Policy, boost::mp11::mp_transform_q< @@ -435,12 +439,16 @@ method::method() { this->vp_end = virtual_type_ids::end; this->not_implemented = (void*)not_implemented_handler; this->ambiguous = (void*)ambiguous_handler; - Policy::catalog.methods.push_front(*this); + this->method_type = Policy::template static_type(); + Policy::methods.push_front(*this); } +template +size_t method::slots_strides[2 * arity - 1]; + template method::~method() { - Policy::catalog.methods.remove(*this); + Policy::methods.remove(*this); } template @@ -601,16 +609,14 @@ inline std::uintptr_t method::resolve_multi_next( slot = static_offsets::slots[VirtualArg]; stride = static_offsets::strides[VirtualArg - 1]; if constexpr (Policy::template has_facet) { - check_static_offset( - static_offsets::strides[VirtualArg - 1], - this->slots_strides[2 * VirtualArg]); check_static_offset( - static_offsets::slots[VirtualArg], - this->slots_strides[2 * VirtualArg - 1]); + this->slots_strides[VirtualArg], slot); + check_static_offset( + this->slots_strides[2 * VirtualArg], stride); } } else { - slot = this->slots_strides[2 * VirtualArg - 1]; - stride = this->slots_strides[2 * VirtualArg]; + slot = this->slots_strides[VirtualArg]; + stride = this->slots_strides[arity + VirtualArg - 1]; } dispatch = dispatch + vtbl[slot] * stride; @@ -668,12 +674,20 @@ method::ambiguous_handler( abort(); // in case user handler "forgets" to abort } -template -void update(); +} // namespace yomm2 +} // namespace yorel + +#include + +namespace yorel { +namespace yomm2 { #ifdef YOMM2_SHARED -yOMM2_API void update(); +#if defined(__GXX_RTTI) || defined(_HAS_STATIC_RTTI) +yOMM2_API auto update() -> detail::compiler; +#endif + yOMM2_API error_handler_type set_error_handler(error_handler_type handler); yOMM2_API method_call_error_handler set_method_call_error_handler(method_call_error_handler handler); @@ -682,9 +696,8 @@ set_method_call_error_handler(method_call_error_handler handler); #if defined(__GXX_RTTI) || defined(_HAS_STATIC_RTTI) -inline void update() { - update(); -} +template +auto update() -> detail::compiler; inline error_handler_type set_error_handler(error_handler_type handler) { auto p = &default_policy::error; @@ -712,10 +725,18 @@ set_method_call_error_handler(method_call_error_handler handler) { #endif +template +auto update() -> typename detail::compiler { + detail::compiler compiler; + compiler.update(); + + return compiler; +} + } // namespace yomm2 } // namespace yorel -#include +#include #pragma pop_macro("min") diff --git a/include/yorel/yomm2/detail.hpp b/include/yorel/yomm2/detail.hpp index 8f887268..2c5e629b 100644 --- a/include/yorel/yomm2/detail.hpp +++ b/include/yorel/yomm2/detail.hpp @@ -54,18 +54,23 @@ struct type_id_list> { }; template -struct type_range { +struct range { + range(Iterator first, Iterator last) : first(first), last(last) { + } + Iterator first, last; + Iterator begin() const { return first; } + Iterator end() const { return last; } }; template -type_range(Iterator b, Iterator e) -> type_range; +range(Iterator b, Iterator e) -> range; struct yomm2_end_of_dump {}; @@ -220,10 +225,26 @@ class pair_first_iterator { // class info struct class_info : static_chain::static_link { - type_id ti; + type_id type; std::uintptr_t** static_vptr; type_id *first_base, *last_base; bool is_abstract{false}; + + const std::uintptr_t* vptr() const { + return *static_vptr; + } + + const std::uintptr_t* const* indirect_vptr() const { + return static_vptr; + } + + auto type_id_begin() const { + return &type; + } + + auto type_id_end() const { + return &type + 1; + } }; template @@ -233,16 +254,16 @@ template struct class_declaration_aux> : class_info { class_declaration_aux() { - this->ti = collect_static_type_id(); + this->type = collect_static_type_id(); this->first_base = type_id_list>::begin; this->last_base = type_id_list>::end; - Policy::catalog.classes.push_front(*this); + Policy::classes.push_front(*this); this->is_abstract = std::is_abstract_v; this->static_vptr = &Policy::template static_vptr; } ~class_declaration_aux() { - Policy::catalog.classes.remove(*this); + Policy::classes.remove(*this); } }; @@ -264,24 +285,14 @@ template constexpr auto arity = boost::mp11::mp_count_if, is_virtual>::value; -template -struct slots_strides_base { - size_t slots_strides[2 * Arity - 1]; - // For 1-method: the offset of the method in the method table, which - // contains a pointer to a function. - // For multi-methods: the offset of the first virtual argument in the - // method table, which contains a pointer to the corresponding cell in - // the dispatch table, followed by the offset of the second argument and - // the stride in the second dimension, etc. -}; - struct yOMM2_API method_info : static_chain::static_link { - size_t* slots_strides_p; std::string_view name; type_id *vp_begin, *vp_end; static_chain specs; void* ambiguous; void* not_implemented; + type_id method_type; + size_t* slots_strides_ptr; auto arity() const { return std::distance(vp_begin, vp_end); @@ -306,6 +317,15 @@ constexpr bool is_policy = is_policy_aux::value; template constexpr bool is_not_policy = !is_policy; +template +struct is_method_aux : std::false_type {}; + +template +struct is_method_aux> : std::true_type {}; + +template +constexpr bool is_method = is_method_aux::value; + template struct next_ptr_t; @@ -786,6 +806,18 @@ struct has_static_offsets< Method, std::void_t::slots)>> : std::true_type {}; +// ----------------------------------------------------------------------------- +// report + +struct update_method_report { + size_t cells = 0; + size_t concrete_cells = 0; + size_t not_implemented = 0; + size_t concrete_not_implemented = 0; + size_t ambiguous = 0; + size_t concrete_ambiguous = 0; +}; + } // namespace detail } // namespace yomm2 } // namespace yorel diff --git a/include/yorel/yomm2/compiler.hpp b/include/yorel/yomm2/detail/compiler.hpp similarity index 61% rename from include/yorel/yomm2/compiler.hpp rename to include/yorel/yomm2/detail/compiler.hpp index 76d67977..3bf530c0 100644 --- a/include/yorel/yomm2/compiler.hpp +++ b/include/yorel/yomm2/detail/compiler.hpp @@ -3,8 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#ifndef YOREL_YOMM2_RUNTIME_INCLUDED -#define YOREL_YOMM2_RUNTIME_INCLUDED +#ifndef YOREL_YOMM2_DETAIL_COMPILER_INCLUDED +#define YOREL_YOMM2_DETAIL_COMPILER_INCLUDED #include // for max, transform, copy #include // for assert @@ -30,327 +30,363 @@ namespace yorel { namespace yomm2 { namespace detail { -struct rt_method; - -struct rt_arg { - rt_method* method; - size_t param; +struct rflush { + size_t width; + size_t value; + explicit rflush(size_t width, size_t value) : width(width), value(value) { + } }; -struct rt_class { - bool is_abstract{false}; - std::vector type_ids; - std::vector transitive_bases; - std::vector direct_bases; - std::vector direct_derived; - std::unordered_set compatible_classes; - std::vector used_by_vp; - int next_slot{0}; - int first_used_slot{-1}; - int layer{0}; - size_t mark{0}; // temporary mark to detect cycles - size_t weight{0}; // number of proper direct or indirect bases - std::vector vtbl; - std::uintptr_t** static_vptr; - - const std::uintptr_t* vptr() const { - return *static_vptr; - } +struct generic_compiler { - const std::uintptr_t* const* indirect_vptr() const { - return static_vptr; - } + struct method; - auto type_id_begin() const { - return type_ids.begin(); - } - - auto type_id_end() const { - return type_ids.end(); - } -}; + struct parameter { + struct method* method; + size_t param; + }; -struct rt_spec { - const definition_info* info; - std::vector vp; - std::uintptr_t pf; -}; + struct vtbl_entry { + size_t method_index, vp_index, group_index; + }; -using bitvec = boost::dynamic_bitset<>; + struct class_ { + bool is_abstract{false}; + std::vector type_ids; + std::vector transitive_bases; + std::vector direct_bases; + std::vector direct_derived; + std::unordered_set compatible_classes; + std::vector used_by_vp; + int next_slot{0}; + int first_used_slot{-1}; + int layer{0}; + size_t mark{0}; // temporary mark to detect cycles + size_t weight{0}; // number of proper direct or indirect bases + std::vector vtbl; + std::uintptr_t** static_vptr; + + const std::uintptr_t* vptr() const { + return *static_vptr; + } -struct group { - std::vector classes; - bool has_concrete_classes{false}; -}; + const std::uintptr_t* const* indirect_vptr() const { + return static_vptr; + } -using group_map = std::map; + auto type_id_begin() const { + return type_ids.begin(); + } -struct dispatch_stats_t { - size_t cells{0}; - size_t concrete_cells{0}; - size_t not_implemented{0}; - size_t concrete_not_implemented{0}; - size_t ambiguous{0}; - size_t concrete_ambiguous{0}; + auto type_id_end() const { + return type_ids.end(); + } + }; - void accumulate(const dispatch_stats_t& other); -}; + struct definition { + const detail::definition_info* info; + std::vector vp; + std::uintptr_t pf; + size_t method_index, group_index; + }; -struct rt_method { - method_info* info; - std::vector vp; - std::vector specs; - std::vector slots; - std::vector strides; - std::vector dispatch_table; - // following two are dummies, when converting to a function pointer, we will - // get the corresponding pointer from method_info - rt_spec not_implemented; - rt_spec ambiguous; - const std::uintptr_t* gv_dispatch_table{nullptr}; - auto arity() const { - return vp.size(); - } - dispatch_stats_t stats; -}; + using bitvec = boost::dynamic_bitset<>; -struct metrics_t : dispatch_stats_t { - size_t method_table_size, dispatch_table_size; - size_t hash_search_attempts; -}; + struct group { + std::vector classes; + bool has_concrete_classes{false}; + }; -inline std::ostream* log_on(std::ostream* os) { - auto prev = logs; - logs = os; - return prev; -} + using group_map = std::map; + + static void + accumulate(const update_method_report& partial, update_report& total); + + struct method { + detail::method_info* info; + std::vector vp; + std::vector specs; + std::vector slots; + std::vector strides; + std::vector dispatch_table; + // following two are dummies, when converting to a function pointer, we will + // get the corresponding pointer from method_info + definition not_implemented; + definition ambiguous; + const std::uintptr_t* gv_dispatch_table{nullptr}; + auto arity() const { + return vp.size(); + } + update_method_report report; + }; -inline std::ostream* log_off() { - auto prev = logs; - logs = nullptr; - return prev; -} + struct type_name { + type_name(type_id type) : type(type) { + } + type_id type; + }; -struct type_name { - type_name(type_id type) : type(type) { - } - type_id type; + std::deque classes; + std::vector methods; + size_t class_visit = 0; + bool compilation_done = false; + size_t max_specs = 0; }; template -struct runtime { - using policy_type = Policy; - using type_index_type = decltype(Policy::type_index(0)); +struct trace_type { static constexpr bool trace_enabled = Policy::template has_facet; - std::unordered_map class_map; - std::deque classes; - std::vector methods; - size_t class_visit{0}; - metrics_t metrics; + size_t indentation_level{0}; - runtime(); + trace_type& operator++() { + if constexpr (trace_enabled) { + if (Policy::trace_enabled) { + for (int i = 0; i < indentation_level; ++i) { + Policy::trace_stream << " "; + } + } + } - void update(); + return *this; + } - void resolve_static_type_ids(); - void augment_classes(); - void calculate_compatible_classes(rt_class& cls); - void augment_methods(); - std::vector layer_classes(); - void allocate_slots(); - void allocate_slot_down(rt_class* cls, size_t slot); - void allocate_slot_up(rt_class* cls, size_t slot); - void build_dispatch_tables(); - void build_dispatch_table( - rt_method& m, size_t dim, std::vector::const_iterator group, - const bitvec& candidates, bool concrete); - void install_gv(); - void optimize(); - void print(const dispatch_stats_t& stats); - static std::vector - best(std::vector& candidates); - static bool is_more_specific(const rt_spec* a, const rt_spec* b); - static bool is_base(const rt_spec* a, const rt_spec* b); + trace_type& operator<<(const rflush& rf) { + if constexpr (trace_enabled) { + auto pad = rf.width; + auto remain = rf.value; - static type_id static_type(type_id type) { - if constexpr (std::is_base_of_v< - policy::deferred_static_rtti, policy::rtti>) { - return reinterpret_cast(type)(); - } else { - return type; - } - } + int digits = 1; + auto tmp = rf.value / 10; - struct rflush { - size_t width; - size_t value; - explicit rflush(size_t width, size_t value) - : width(width), value(value) { + while (tmp) { + ++digits; + tmp /= 10; + } + + while (digits < rf.width) { + *this << " "; + ++digits; + } + + *this << rf.value; } - }; - struct trace_type { - size_t indentation_level{0}; + return *this; + } - trace_type& operator++() { - if constexpr (trace_enabled) { - if (Policy::trace_enabled) { - for (int i = 0; i < indentation_level; ++i) { - Policy::trace_stream << " "; - } + trace_type& operator<<(const boost::dynamic_bitset<>& bits) { + if constexpr (trace_enabled) { + if (Policy::trace_enabled) { + auto i = bits.size(); + while (i != 0) { + --i; + Policy::trace_stream << bits[i]; } } + } + return *this; + } - return *this; + template + trace_type& operator<<(const T& value) { + if constexpr (trace_enabled) { + if (Policy::trace_enabled) { + Policy::trace_stream << value; + } } + return *this; + } - trace_type& operator<<(const rflush& rf) { - if constexpr (trace_enabled) { - auto pad = rf.width; - auto remain = rf.value; + template + trace_type& write_range(detail::range range, F fn) { + if constexpr (trace_enabled) { + *this << "("; + const char* sep = ""; + for (auto value : range) { + *this << sep << fn(value); + sep = ", "; + } - int digits = 1; - auto tmp = rf.value / 10; + *this << ")"; + } - while (tmp) { - ++digits; - tmp /= 10; - } + return *this; + } - while (digits < rf.width) { - *this << " "; - ++digits; - } + template + trace_type& operator<<(detail::range range) { + return write_range(range, [](auto value) { return value; }); + } - *this << rf.value; - } + trace_type& operator<<(detail::range tips) { + return write_range( + tips, [](auto tip) { return generic_compiler::type_name(tip); }); + } - return *this; - } + struct indent { + trace_type& trace; + int by; - trace_type& operator<<(const boost::dynamic_bitset<>& bits) { - if constexpr (trace_enabled) { - if (Policy::trace_enabled) { - auto i = bits.size(); - while (i != 0) { - --i; - Policy::trace_stream << bits[i]; - } - } - } - return *this; + explicit indent(trace_type& trace, int by = 2) : trace(trace), by(by) { + trace.indentation_level += by; } - template - trace_type& operator<<(const T& value) { - if constexpr (trace_enabled) { - if (Policy::trace_enabled) { - Policy::trace_stream << value; - } - } - return *this; + ~indent() { + trace.indentation_level -= by; } + }; - trace_type& operator<<(type_range tips) { - if constexpr (trace_enabled) { - *this << "("; - const char* sep = ""; - for (auto t : tips) { - *this << sep << type_name(t); - sep = ", "; - } + trace_type& operator<<(generic_compiler::vtbl_entry entry) { + return *this << entry.method_index << "/" << entry.vp_index << "/" + << entry.group_index; + } - *this << ")"; - } + trace_type& operator<<(const generic_compiler::definition* def) { + return *this << def->method_index << "/" << def->group_index; + } - return *this; + trace_type& operator<<(const generic_compiler::class_& cls) { + if constexpr (trace_enabled) { + *this << generic_compiler::type_name(cls.type_ids[0]); } - template typename Container, typename... T> - trace_type& operator<<(Container& classes) { - if constexpr (trace_enabled) { - *this << "("; - const char* sep = ""; - for (auto cls : classes) { - *this << sep << *cls; - sep = ", "; - } + return *this; + } - *this << ")"; + template typename Container, typename... T> + trace_type& + operator<<(Container& classes) { + if constexpr (trace_enabled) { + *this << "("; + const char* sep = ""; + for (auto cls : classes) { + *this << sep << *cls; + sep = ", "; } - return *this; + *this << ")"; } - trace_type& operator<<(type_name manip) { - if constexpr (trace_enabled) { - Policy::type_name(manip.type, *this); - } + return *this; + } - return *this; + trace_type& operator<<(generic_compiler::type_name manip) { + if constexpr (trace_enabled) { + Policy::type_name(manip.type, *this); } - trace_type& operator<<(const rt_class& cls) { - if constexpr (trace_enabled) { - *this << type_name(cls.type_ids[0]); - } + return *this; + } +}; - return *this; - } - }; +template +struct compiler : generic_compiler { + using policy_type = Policy; + using type_index_type = decltype(Policy::type_index(0)); - trace_type trace; + typename aggregate_reports< + types, typename Policy::facets>::type report; - struct indent { - trace_type& trace; - int by; + std::unordered_map class_map; - explicit indent(trace_type& trace, int by = 2) : trace(trace), by(by) { - trace.indentation_level += by; - } + compiler(); - ~indent() { - trace.indentation_level -= by; + auto compile(); + auto update(); + void install_global_tables(); + + void resolve_static_type_ids(); + void augment_classes(); + void calculate_compatible_classes(class_& cls); + void augment_methods(); + std::vector layer_classes(); + void allocate_slots(); + void allocate_slot_down(class_* cls, size_t slot); + void allocate_slot_up(class_* cls, size_t slot); + void build_dispatch_tables(); + void build_dispatch_table( + method& m, size_t dim, std::vector::const_iterator group, + const bitvec& candidates, bool concrete); + void install_gv(); + void optimize(); + void print(const update_method_report& report) const; + static std::vector + best(std::vector& candidates); + static bool is_more_specific(const definition* a, const definition* b); + static bool is_base(const definition* a, const definition* b); + + static type_id static_type(type_id type) { + if constexpr (std::is_base_of_v< + policy::deferred_static_rtti, policy::rtti>) { + return reinterpret_cast(type)(); + } else { + return type; } - }; + } + + mutable trace_type trace; + static constexpr bool trace_enabled = + Policy::template has_facet; + using indent = typename trace_type::indent; }; +compiler() -> compiler; + +template +void compiler::install_global_tables() { + if (!compilation_done) { + abort(); + } + + install_gv(); + optimize(); + + print(report); + ++trace << "Finished\n"; +} + template -void runtime::update() { +auto compiler::compile() { resolve_static_type_ids(); augment_classes(); augment_methods(); allocate_slots(); build_dispatch_tables(); - install_gv(); - optimize(); - print(metrics); + compilation_done = true; - ++trace << "Finished\n"; + return report; +} + +template +auto compiler::update() { + compile(); + install_global_tables(); + + return *this; } template -runtime::runtime() { +compiler::compiler() { } template -void runtime::resolve_static_type_ids() { +void compiler::resolve_static_type_ids() { auto resolve = [](type_id* p) { auto pf = reinterpret_cast(*p); *p = pf(); }; if constexpr (std::is_base_of_v) { - if (!Policy::catalog.classes.empty()) - for (auto& ci : Policy::catalog.classes) { - resolve(&ci.ti); + if (!Policy::classes.empty()) + for (auto& ci : Policy::classes) { + resolve(&ci.type); if (*ci.last_base == 0) { for (auto& ti : - detail::type_range{ci.first_base, ci.last_base}) { + detail::range{ci.first_base, ci.last_base}) { resolve(&ti); } @@ -358,10 +394,9 @@ void runtime::resolve_static_type_ids() { } } - if (!Policy::catalog.methods.empty()) - for (auto& method : Policy::catalog.methods) { - for (auto& ti : - detail::type_range{method.vp_begin, method.vp_end}) { + if (!Policy::methods.empty()) + for (auto& method : Policy::methods) { + for (auto& ti : detail::range{method.vp_begin, method.vp_end}) { if (*method.vp_end == 0) { resolve(&ti); *method.vp_end = 1; @@ -370,7 +405,7 @@ void runtime::resolve_static_type_ids() { if (!method.specs.empty()) for (auto& definition : method.specs) { if (*definition.vp_end == 0) { - for (auto& ti : detail::type_range{ + for (auto& ti : detail::range{ definition.vp_begin, definition.vp_end}) { resolve(&ti); @@ -385,7 +420,9 @@ void runtime::resolve_static_type_ids() { } template -void runtime::augment_classes() { +void compiler::augment_classes() { + using namespace detail; + // scope { ++trace << "Static class info:\n"; @@ -393,18 +430,18 @@ void runtime::augment_classes() { // The standard does not guarantee that there is exactly one // type_info object per class. However, it guarantees that the // type_index for a class has a unique value. - for (auto& cr : Policy::catalog.classes) { + for (auto& cr : Policy::classes) { if constexpr (trace_enabled) { { indent YOMM2_GENSYM(trace); - ++trace << type_name(cr.ti) << ": " - << type_range{cr.first_base, cr.last_base}; + ++trace << type_name(cr.type) << ": " + << range{cr.first_base, cr.last_base}; ++trace << "\n"; } } - auto& rtc = class_map[Policy::type_index(cr.ti)]; + auto& rtc = class_map[Policy::type_index(cr.type)]; if (rtc == nullptr) { rtc = &classes.emplace_back(); @@ -417,18 +454,19 @@ void runtime::augment_classes() { // unordered_set because, again, this situation is highly // unlikely, and, were it to occur, the number of distinct ti*s // would probably be small. - if (std::find(rtc->type_ids.begin(), rtc->type_ids.end(), cr.ti) == + if (std::find( + rtc->type_ids.begin(), rtc->type_ids.end(), cr.type) == rtc->type_ids.end()) { - rtc->type_ids.push_back(cr.ti); + rtc->type_ids.push_back(cr.type); } } } - // All known classes now have exactly one associated rt_class* in the + // All known classes now have exactly one associated class_* in the // map. Collect the bases. - for (auto& cr : Policy::catalog.classes) { - auto& rtc = class_map[Policy::type_index(cr.ti)]; + for (auto& cr : Policy::classes) { + auto& rtc = class_map[Policy::type_index(cr.type)]; for (auto base_iter = cr.first_base; base_iter != cr.last_base; ++base_iter) { @@ -529,7 +567,7 @@ void runtime::augment_classes() { } template -void runtime::calculate_compatible_classes(rt_class& cls) { +void compiler::calculate_compatible_classes(class_& cls) { if (!cls.compatible_classes.empty()) { return; } @@ -550,21 +588,21 @@ void runtime::calculate_compatible_classes(rt_class& cls) { } template -void runtime::augment_methods() { +void compiler::augment_methods() { using namespace policy; + using namespace detail; - methods.resize(Policy::catalog.methods.size()); + methods.resize(Policy::methods.size()); ++trace << "Methods:\n"; indent YOMM2_GENSYM(trace); - auto meth_iter = methods.rbegin(); - // reverse the registration order reversed by 'chain'. + auto meth_iter = methods.begin(); - for (auto& meth_info : Policy::catalog.methods) { + for (auto& meth_info : Policy::methods) { if constexpr (trace_enabled) { ++trace << meth_info.name << " " - << type_range{meth_info.vp_begin, meth_info.vp_end} << "\n"; + << range{meth_info.vp_begin, meth_info.vp_end} << "\n"; } indent YOMM2_GENSYM(trace); @@ -573,9 +611,9 @@ void runtime::augment_methods() { meth_iter->vp.reserve(meth_info.arity()); size_t param_index = 0; - for (auto ti : type_range{meth_info.vp_begin, meth_info.vp_end}) { - auto rt_class = class_map[Policy::type_index(ti)]; - if (!rt_class) { + for (auto ti : range{meth_info.vp_begin, meth_info.vp_end}) { + auto class_ = class_map[Policy::type_index(ti)]; + if (!class_) { ++trace << "unkown class " << ti << "(" << type_name(ti) << ") for parameter #" << (param_index + 1) << "\n"; unknown_class_error error; @@ -587,8 +625,8 @@ void runtime::augment_methods() { abort(); } - rt_arg param = {&*meth_iter, param_index++}; - meth_iter->vp.push_back(rt_class); + parameter param = {&*meth_iter, param_index++}; + meth_iter->vp.push_back(class_); } // initialize the function pointer in the dummy specs @@ -598,10 +636,17 @@ void runtime::augment_methods() { reinterpret_cast(meth_iter->info->not_implemented); meth_iter->specs.resize(meth_info.specs.size()); - auto spec_iter = meth_iter->specs.rbegin(); - // reverse the reversed order from 'chain' + auto spec_iter = meth_iter->specs.begin(); + max_specs = 0; for (auto& definition_info : meth_info.specs) { + spec_iter->method_index = meth_iter - methods.begin(); + spec_iter->group_index = spec_iter - meth_iter->specs.begin(); + + if (spec_iter->group_index >= max_specs) { + max_specs = spec_iter->group_index + 1; + } + ++trace << type_name(definition_info.type) << " (" << definition_info.pf << ")\n"; spec_iter->info = &definition_info; @@ -609,10 +654,10 @@ void runtime::augment_methods() { size_t param_index = 0; for (auto type : - type_range{definition_info.vp_begin, definition_info.vp_end}) { + range{definition_info.vp_begin, definition_info.vp_end}) { indent YOMM2_GENSYM(trace); - auto rt_class = class_map[Policy::type_index(type)]; - if (!rt_class) { + auto class_ = class_map[Policy::type_index(type)]; + if (!class_) { ++trace << "error for *virtual* parameter #" << (param_index + 1) << "\n"; unknown_class_error error; @@ -626,8 +671,7 @@ void runtime::augment_methods() { } spec_iter->pf = reinterpret_cast(spec_iter->info->pf); - spec_iter->vp.push_back(rt_class); - ++param_index; + spec_iter->vp.push_back(class_); } ++spec_iter; } @@ -645,16 +689,16 @@ void runtime::augment_methods() { } template -std::vector runtime::layer_classes() { +std::vector compiler::layer_classes() { ++trace << "Layering classes...\n"; - std::vector input; + std::vector input; input.reserve(classes.size()); std::transform( classes.begin(), classes.end(), std::back_inserter(input), - [](rt_class& cls) { return &cls; }); + [](class_& cls) { return &cls; }); - std::vector layered; + std::vector layered; layered.reserve(classes.size()); for (int layer = 1; !input.empty(); ++layer) { @@ -698,7 +742,7 @@ std::vector runtime::layer_classes() { } template -void runtime::allocate_slots() { +void compiler::allocate_slots() { auto layered = layer_classes(); ++trace << "Allocating slots...\n"; @@ -739,7 +783,7 @@ void runtime::allocate_slots() { } template -void runtime::allocate_slot_down(rt_class* cls, size_t slot) { +void compiler::allocate_slot_down(class_* cls, size_t slot) { if (cls->mark == class_visit) return; @@ -766,7 +810,7 @@ void runtime::allocate_slot_down(rt_class* cls, size_t slot) { } template -void runtime::allocate_slot_up(rt_class* cls, size_t slot) { +void compiler::allocate_slot_up(class_* cls, size_t slot) { if (cls->mark == class_visit) return; @@ -792,7 +836,9 @@ void runtime::allocate_slot_up(rt_class* cls, size_t slot) { } template -void runtime::build_dispatch_tables() { +void compiler::build_dispatch_tables() { + using namespace detail; + for (auto& m : methods) { ++trace << "Building dispatch table for " << m.info->name << "\n"; indent YOMM2_GENSYM(trace); @@ -811,29 +857,29 @@ void runtime::build_dispatch_tables() { << "\n"; indent YOMM2_GENSYM(trace); - for (auto compatible_classes : vp->compatible_classes) { - ++trace << "specs applicable to " << *compatible_classes + for (auto compatible_class : vp->compatible_classes) { + ++trace << "specs applicable to " << *compatible_class << "\n"; bitvec mask; mask.resize(m.specs.size()); - size_t spec_index = 0; + size_t group_index = 0; indent YOMM2_GENSYM(trace); for (auto& spec : m.specs) { if (spec.vp[dim]->compatible_classes.find( - compatible_classes) != + compatible_class) != spec.vp[dim]->compatible_classes.end()) { ++trace << type_name(spec.info->type) << "\n"; - mask[spec_index] = 1; + mask[group_index] = 1; } - ++spec_index; + ++group_index; } auto& group = dim_group[mask]; - group.classes.push_back(compatible_classes); + group.classes.push_back(compatible_class); group.has_concrete_classes = group.has_concrete_classes || - !compatible_classes->is_abstract; + !compatible_class->is_abstract; ++trace << "-> mask: " << mask << "\n"; } @@ -860,15 +906,17 @@ void runtime::build_dispatch_tables() { size_t group_num = 0; for (auto& [mask, group] : groups[dim]) { for (auto cls : group.classes) { - cls->vtbl[m.slots[dim]] = group_num; + auto& entry = cls->vtbl[m.slots[dim]]; + entry.method_index = &m - &methods[0]; + entry.vp_index = dim; + entry.group_index = group_num; } if constexpr (trace_enabled) { - ++trace << "group " << dim << "/" << group_num << " mask " - << mask << "\n"; + ++trace << group_num << " mask " << mask << "\n"; indent YOMM2_GENSYM(trace); - for (auto cls : type_range{ - group.classes.begin(), group.classes.end()}) { - ++trace << cls->type_ids[0] << "\n"; + for (auto cls : + range{group.classes.begin(), group.classes.end()}) { + ++trace << type_name(cls->type_ids[0]) << "\n"; } } ++group_num; @@ -879,50 +927,53 @@ void runtime::build_dispatch_tables() { ++trace << "assigning specs\n"; bitvec all(m.specs.size()); all = ~all; - build_dispatch_table(m, dims - 1, groups.end() - 1, all, false); + build_dispatch_table(m, dims - 1, groups.end() - 1, all, true); if (m.arity() > 1) { indent YOMM2_GENSYM(trace); - m.stats.cells = 1; + m.report.cells = 1; ++trace << "dispatch table rank: "; const char* prefix = ""; + for (const auto& dim_groups : groups) { - m.stats.cells *= dim_groups.size(); + m.report.cells *= dim_groups.size(); trace << prefix << dim_groups.size(); prefix = " x "; } - m.stats.concrete_cells = 1; + m.report.concrete_cells = 1; prefix = ", concrete only: "; + for (const auto& dim_groups : groups) { auto cells = std::count_if( dim_groups.begin(), dim_groups.end(), [](const auto& group) { return group.second.has_concrete_classes; }); - m.stats.concrete_cells *= cells; + m.report.concrete_cells *= cells; trace << prefix << cells; prefix = " x "; } + trace << "\n"; } - print(m.stats); - metrics.accumulate(m.stats); + print(m.report); + accumulate(m.report, report); ++trace << "assigning next\n"; - std::vector specs; + std::vector specs; std::transform( m.specs.begin(), m.specs.end(), std::back_inserter(specs), - [](const rt_spec& spec) { return &spec; }); + [](const definition& spec) { return &spec; }); for (auto& spec : m.specs) { indent YOMM2_GENSYM(trace); ++trace << type_name(spec.info->type) << ":\n"; - std::vector candidates; + std::vector candidates; std::copy_if( specs.begin(), specs.end(), std::back_inserter(candidates), - [spec](const rt_spec* other) { + [spec](const definition* other) { return is_base(other, &spec); }); @@ -932,7 +983,8 @@ void runtime::build_dispatch_tables() { for (auto& candidate : candidates) { indent YOMM2_GENSYM(trace); - ++trace << type_name(candidate->info->type) << "\n"; + ++trace << "#" << candidate->group_index + << type_name(candidate->info->type) << "\n"; } } @@ -942,7 +994,9 @@ void runtime::build_dispatch_tables() { if (nexts.size() == 1) { const definition_info* next_info = nexts.front()->info; next = next_info->pf; - ++trace << "-> " << type_name(next_info->type) << "\n"; + ++trace << "-> " + << "#" << nexts.front()->group_index + << type_name(next_info->type) << "\n"; } else if (nexts.empty()) { ++trace << "-> none\n"; next = m.info->not_implemented; @@ -960,9 +1014,11 @@ void runtime::build_dispatch_tables() { } template -void runtime::build_dispatch_table( - rt_method& m, size_t dim, std::vector::const_iterator group_iter, +void compiler::build_dispatch_table( + method& m, size_t dim, std::vector::const_iterator group_iter, const bitvec& candidates, bool concrete) { + using namespace detail; + indent YOMM2_GENSYM(trace); size_t group_index = 0; @@ -973,14 +1029,13 @@ void runtime::build_dispatch_table( ++trace << "group " << dim << "/" << group_index << " mask " << mask << "\n"; indent YOMM2_GENSYM(trace); - for (auto cls : - type_range{group.classes.begin(), group.classes.end()}) { - ++trace << cls->type_ids[0] << "\n"; + for (auto cls : range{group.classes.begin(), group.classes.end()}) { + ++trace << type_name(cls->type_ids[0]) << "\n"; } } if (dim == 0) { - std::vector applicable; + std::vector applicable; size_t i = 0; for (const auto& spec : m.specs) { @@ -995,7 +1050,8 @@ void runtime::build_dispatch_table( indent YOMM2_GENSYM(trace); for (auto& app : applicable) { - ++trace << type_name(app->info->type) << "\n"; + ++trace << "#" << app->group_index << " " + << type_name(app->info->type) << "\n"; } } @@ -1005,22 +1061,24 @@ void runtime::build_dispatch_table( indent YOMM2_GENSYM(trace); ++trace << "ambiguous\n"; m.dispatch_table.push_back(&m.ambiguous); - ++m.stats.ambiguous; + ++m.report.ambiguous; if (concrete) { - ++m.stats.concrete_ambiguous; + ++m.report.concrete_ambiguous; } } else if (specs.empty()) { indent YOMM2_GENSYM(trace); ++trace << "not implemented\n"; m.dispatch_table.push_back(&m.not_implemented); - ++m.stats.not_implemented; - if (concrete) { - ++m.stats.concrete_not_implemented; + ++m.report.not_implemented; + if (concrete && group.has_concrete_classes) { + ++m.report.concrete_not_implemented; } } else { - m.dispatch_table.push_back(specs[0]); - ++trace << "-> " << type_name(specs[0]->info->type) - << " pf = " << specs[0]->info->pf << "\n"; + auto spec = specs[0]; + m.dispatch_table.push_back(spec); + ++trace << "-> #" << spec->group_index << " " + << type_name(spec->info->type) + << " pf = " << spec->info->pf << "\n"; } } else { build_dispatch_table( @@ -1031,17 +1089,18 @@ void runtime::build_dispatch_table( } } -inline void dispatch_stats_t::accumulate(const dispatch_stats_t& other) { - cells += other.cells; - concrete_cells += other.concrete_cells; - not_implemented += other.not_implemented; - concrete_not_implemented += other.concrete_not_implemented; - ambiguous += other.ambiguous; - concrete_ambiguous += other.concrete_ambiguous; +inline void generic_compiler::accumulate( + const update_method_report& partial, update_report& total) { + total.cells += partial.cells; + total.concrete_cells += partial.concrete_cells; + total.not_implemented += partial.not_implemented != 0; + total.concrete_not_implemented += partial.concrete_not_implemented != 0; + total.ambiguous += partial.ambiguous != 0; + total.concrete_ambiguous += partial.concrete_ambiguous != 0; } template -void runtime::install_gv() { +void compiler::install_gv() { using namespace policy; for (size_t pass = 0; pass != 2; ++pass) { @@ -1049,7 +1108,7 @@ void runtime::install_gv() { if constexpr (trace_enabled) { if (pass) { - ++trace << "Initializing dispatch tables at " + ++trace << "Initializing multi-method dispatch tables at " << Policy::dispatch_data.data() << "\n"; } } @@ -1057,21 +1116,15 @@ void runtime::install_gv() { for (auto& m : methods) { if (m.info->arity() == 1) { // Uni-methods just need an index in the method table. - *m.info->slots_strides_p = m.slots[0]; + m.info->slots_strides_ptr[0] = m.slots[0]; continue; } // multi-methods only - auto slot_iter = m.slots.begin(); - auto stride_iter = m.strides.begin(); - auto offsets_iter = m.info->slots_strides_p; - *offsets_iter++ = *slot_iter++; - - while (slot_iter != m.slots.end()) { - *offsets_iter++ = *slot_iter++; - *offsets_iter++ = *stride_iter++; - } + auto strides_iter = std::copy( + m.slots.begin(), m.slots.end(), m.info->slots_strides_ptr); + std::copy(m.strides.begin(), m.strides.end(), strides_iter); if constexpr (trace_enabled) { if (pass) { @@ -1079,6 +1132,11 @@ void runtime::install_gv() { << Policy::dispatch_data.data() + Policy::dispatch_data.size() << " dispatch table for " << m.info->name << "\n"; + indent YOMM2_GENSYM(trace); + ++trace + << range( + m.dispatch_table.begin(), m.dispatch_table.end()) + << "\n"; } } @@ -1090,6 +1148,15 @@ void runtime::install_gv() { [](auto spec) { return spec->pf; }); } + if constexpr (trace_enabled) { + if (pass) { + ++trace << "Initializing v-tables at " + << (Policy::dispatch_data.data() + + Policy::dispatch_data.size()) + << "\n"; + } + } + for (auto& cls : classes) { if (cls.first_used_slot == -1) { // corner case: no methods for this class @@ -1103,14 +1170,19 @@ void runtime::install_gv() { if constexpr (trace_enabled) { if (pass) { ++trace << rflush(4, Policy::dispatch_data.size()) << " " - << *cls.static_vptr << " vtbl for " << cls << "\n"; + << *cls.static_vptr << " vtbl for " << cls + << " slots [0/" << cls.first_used_slot << "/" + << cls.vtbl.size() << "]\n"; + indent YOMM2_GENSYM(trace); + ++trace << range(cls.vtbl.begin(), cls.vtbl.end()) << "\n"; } } if (cls.first_used_slot != -1) { - std::copy( + std::transform( cls.vtbl.begin() + cls.first_used_slot, cls.vtbl.end(), - std::back_inserter(Policy::dispatch_data)); + std::back_inserter(Policy::dispatch_data), + [](auto entry) { return entry.group_index; }); } } } @@ -1125,7 +1197,7 @@ void runtime::install_gv() { } template -void runtime::optimize() { +void compiler::optimize() { ++trace << "Optimizing\n"; for (auto& m : methods) { @@ -1135,13 +1207,14 @@ void runtime::optimize() { if (m.arity() == 1) { for (auto cls : m.vp[0]->compatible_classes) { - auto pf = m.dispatch_table[(*cls->static_vptr)[slot]]->pf; + auto spec = m.dispatch_table[(*cls->static_vptr)[slot]]; if constexpr (trace_enabled) { - ++trace << *cls << " vtbl[" << slot << "] = " << pf - << " (function)" - << "\n"; + ++trace << *cls << " vtbl[" << slot + << "] = " << spec->method_index << "/" + << spec->group_index << " function " + << (void*)spec->pf << "\n"; } - (*cls->static_vptr)[slot] = pf; + (*cls->static_vptr)[slot] = spec->pf; } } else { for (auto cls : m.vp[0]->compatible_classes) { @@ -1160,12 +1233,12 @@ void runtime::optimize() { } template -std::vector -runtime::best(std::vector& candidates) { - std::vector best; +std::vector +compiler::best(std::vector& candidates) { + std::vector best; for (auto spec : candidates) { - const rt_spec* candidate = spec; + const definition* candidate = spec; for (auto iter = best.begin(); iter != best.end();) { if (is_more_specific(spec, *iter)) { @@ -1187,7 +1260,8 @@ runtime::best(std::vector& candidates) { } template -bool runtime::is_more_specific(const rt_spec* a, const rt_spec* b) { +bool compiler::is_more_specific( + const definition* a, const definition* b) { bool result = false; auto a_iter = a->vp.begin(), a_last = a->vp.end(), b_iter = b->vp.begin(); @@ -1209,7 +1283,7 @@ bool runtime::is_more_specific(const rt_spec* a, const rt_spec* b) { } template -bool runtime::is_base(const rt_spec* a, const rt_spec* b) { +bool compiler::is_base(const definition* a, const definition* b) { bool result = false; auto a_iter = a->vp.begin(), a_last = a->vp.end(), b_iter = b->vp.begin(); @@ -1229,29 +1303,26 @@ bool runtime::is_base(const rt_spec* a, const rt_spec* b) { } template -void runtime::print(const dispatch_stats_t& stats) { +void compiler::print(const update_method_report& report) const { ++trace; - if (stats.cells) { + + if (report.cells) { // only for multi-methods, uni-methods don't have dispatch tables - ++trace << stats.cells << " dispatch table cells, "; - } - trace << stats.not_implemented << " not implemented, "; - trace << stats.ambiguous << " ambiguities, concrete only: "; - if (stats.cells) { - trace << stats.concrete_cells << ", "; + ++trace << report.cells << " dispatch table cells, "; } - trace << stats.concrete_not_implemented << ", "; - trace << stats.concrete_ambiguous << "\n"; -} -} // namespace detail + trace << report.not_implemented << " not implemented, "; + trace << report.ambiguous << " ambiguities, concrete only: "; -template -void update() { - detail::runtime rt; - rt.update(); + if (report.cells) { + trace << report.concrete_cells << ", "; + } + + trace << report.concrete_not_implemented << ", "; + trace << report.concrete_ambiguous << "\n"; } +} // namespace detail } // namespace yomm2 } // namespace yorel diff --git a/include/yorel/yomm2/detail/forward.hpp b/include/yorel/yomm2/detail/forward.hpp new file mode 100644 index 00000000..92dc32b7 --- /dev/null +++ b/include/yorel/yomm2/detail/forward.hpp @@ -0,0 +1,20 @@ +// Copyright (c) 2018-2024 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef YOREL_YOMM2_DETAIL_FORWARD_INCLUDED +#define YOREL_YOMM2_DETAIL_FORWARD_INCLUDED + +namespace yorel { +namespace yomm2 { +namespace detail { + +template +struct compiler; + +} +} // namespace yomm2 +} // namespace yorel + +#endif diff --git a/include/yorel/yomm2/generator.hpp b/include/yorel/yomm2/generator.hpp new file mode 100644 index 00000000..231039b6 --- /dev/null +++ b/include/yorel/yomm2/generator.hpp @@ -0,0 +1,604 @@ +// Copyright (c) 2018-2024 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef YOREL_YOMM2_GENERATOR_GENERATE_INCLUDED +#define YOREL_YOMM2_GENERATOR_GENERATE_INCLUDED + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace yorel { +namespace yomm2 { + +class generator { + public: + void add_forward_declaration(const std::type_info& type); + template + void add_forward_declaration(); + void add_forward_declaration(std::string_view name); + template + void add_forward_declarations(); + void write_forward_declarations(std::ostream& os) const; + template + const generator& write_static_slots(std::ostream& os) const; + + template + static void create_dispatch_data( + const Compiler& compiler, std::vector& slots, + std::vector& tables); + template + static void + encode_dispatch_data(const Compiler& compiler, std::ostream& os); + template + static void decode_dispatch_data(Data& data); + + static constexpr size_t method_bits = sizeof(uint16_t) * 4; + static constexpr std::uintptr_t spec_mask = (1 << method_bits) - 1; + static constexpr std::uintptr_t stop_bit = 1 << (sizeof(uint16_t) * 8 - 1); + static constexpr std::uintptr_t index_bit = 1 << (sizeof(uint16_t) * 8 - 2); + + private: + void write_static_slots( + const detail::method_info& method, std::ostream& os) const; + + static uint16_t encode_group( + const detail::generic_compiler::method* method, + const detail::generic_compiler::vtbl_entry& entry); + + static std::unordered_set keywords; + std::set names; +}; + +namespace detail { + +inline bool starts_with(std::string_view name, const char* prefix) { + // Assume that prefix is not an empty string. + + for (auto c : name) { + if (c != *prefix) { + return false; + } + + ++prefix; + + if (!*prefix) { + return true; + } + } + + return false; +} + +} // namespace detail + +// clang-format off +inline std::unordered_set generator::keywords = { + "void", "bool", "char", "int", "float", + "double", "short", "long", "signed", "unsigned", + "class", "struct", "enum", +}; +// clang-format on + +inline void generator::add_forward_declaration(std::string_view type) { + using namespace detail; + + std::regex name_regex(R"((\w+(?:::\w+)*)( *<)?)"); + + auto iter = std::regex_iterator(type.begin(), type.end(), name_regex); + auto words_end = std::sregex_iterator(); + + for (decltype(iter) last; iter != last; ++iter) { + if ((*iter)[2].matched) { + continue; + } + + auto match = (*iter)[1]; + + if (!match.matched) { + continue; + } + + std::string_view name(&*match.first, match.length()); + + if (!std::isalpha(*match.first)) { + continue; + } + + if (keywords.find(name) != keywords.end()) { + continue; + } + + if (starts_with(name, "std::") || starts_with(name, "yorel::")) { + continue; + } + + names.emplace(name); + } +} + +inline void generator::add_forward_declaration(const std::type_info& type) { + add_forward_declaration(boost::core::demangle(type.name())); +} + +template +inline void generator::add_forward_declaration() { + add_forward_declaration(typeid(T)); +} + +template +void generator::add_forward_declarations() { + for (auto& method : Policy::methods) { + add_forward_declaration( + *reinterpret_cast(method.method_type)); + } +} +inline void generator::write_forward_declarations(std::ostream& os) const { + const std::string file_scope; + auto prev_ns_iter = file_scope.begin(); + auto prev_ns_last = file_scope.begin(); + + for (auto& name : names) { + auto name_iter = name.begin(); + auto ns_last = name_iter; + + while (prev_ns_iter != prev_ns_last) { + if (name_iter == name.end() || *prev_ns_iter != *name_iter) { + while (prev_ns_iter != prev_ns_last) { + if (*prev_ns_iter == ':') { + os << "}\n"; + ++prev_ns_iter; + } + ++prev_ns_iter; + } + + while (name_iter != name.begin() && name_iter[-1] != ':') { + --name_iter; + } + + ns_last = name_iter; + + break; + } + + ++prev_ns_iter; + ++name_iter; + } + + prev_ns_iter = name.begin(); + prev_ns_last = name_iter; + + while (true) { + auto scope_iter = std::find(name_iter, name.end(), ':'); + + if (scope_iter == name.end()) { + os << "class " + << std::string_view(&*name_iter, scope_iter - name_iter) + << ";\n"; + break; + } else { + os << "namespace " + << std::string_view(&*name_iter, scope_iter - name_iter) + << " {\n"; + name_iter = scope_iter + 2; + prev_ns_last = name_iter; + } + } + } + + while (prev_ns_iter != prev_ns_last) { + if (*prev_ns_iter == ':') { + os << "}\n"; + ++prev_ns_iter; + } + + ++prev_ns_iter; + } +} + +template +const generator& generator::write_static_slots(std::ostream& os) const { + using namespace detail; + + if constexpr (is_policy) { + for (auto& method : T::methods) { + write_static_slots(method, os); + } + } else { + static_assert(is_method); + write_static_slots(T::fn, os); + } + + return *this; +} + +void generator::write_static_slots( + const detail::method_info& method, std::ostream& os) const { + using namespace detail; + + auto method_name = boost::core::demangle( + reinterpret_cast(method.method_type)->name()); + os << "template<> struct yorel::yomm2::detail::static_offsets<" + << method_name << "> {static constexpr size_t slots[] = {"; + + os << method.slots_strides_ptr[0]; + + if (method.arity() > 1) { + for (size_t i = 1; i < method.arity(); i++) { + os << ", " << method.slots_strides_ptr[i * 2 - 1]; + } + + os << "}; static constexpr size_t strides[] = {"; + auto comma = ""; + + for (size_t i = 1; i < method.arity(); i++) { + os << comma << method.slots_strides_ptr[i * 2]; + comma = ", "; + } + } + + os << "}; };\n"; +} + +uint16_t generator::encode_group( + const detail::generic_compiler::method* method, + const detail::generic_compiler::vtbl_entry& entry) { + auto spec = method->dispatch_table[entry.group_index]; + + if (method->arity() == 1) { + auto spec = method->dispatch_table[entry.group_index]; + + if (spec == &method->not_implemented) { + return method->specs.size(); + } else if (spec == &method->ambiguous) { + return method->specs.size() + 1; + } else { + return entry.group_index; + } + } else { + return entry.group_index; + } +} + +template +void generator::encode_dispatch_data( + const Compiler& compiler, std::ostream& os) { + const char* indent = " "; + using namespace yorel::yomm2::detail; + + std::vector slots; + std::vector tables; + // create_dispatch_data(compiler, slots, tables); + + os << std::hex << std::showbase; + + auto slots_and_strides_size = std::accumulate( + compiler.methods.begin(), compiler.methods.end(), 0, + [](auto sum, auto& method) { return sum + 2 * method.arity() - 1; }); + auto dispatch_tables_size = std::accumulate( + compiler.methods.begin(), compiler.methods.end(), 0, + [](auto sum, auto& method) { + return sum + method.dispatch_table.size(); + }); + + size_t encode_vtbl_size = 0, decode_vtbl_size = 0; + + for (auto& cls : compiler.classes) { + size_t encode_size = 0; + + for (auto& entry : + range(cls.vtbl.begin() + cls.first_used_slot, cls.vtbl.end())) { + if (entry.vp_index > 1) { + // It's a multi-method, and not the first virtual parameter. + // Encode the index, it will be decoded as is. + ++encode_size; + } else { + // It's a uni-method, or the first virtual parameter of a + // multi-method. Encode the method index and the spec index; + encode_size += 2; + } + } + + encode_vtbl_size += encode_size; + decode_vtbl_size += cls.vtbl.size() - cls.first_used_slot; + } + + char prelude_format[] = R"( +auto generated() { + constexpr auto stop_bit = yorel::yomm2::generator::stop_bit; + constexpr auto method_bits = yorel::yomm2::generator::method_bits; + + static union { + struct { + uint16_t headroom[%d]; + uint16_t slots[%d]; + uint16_t dispatch[%d]; + uint16_t vtbl[%d]; + } packed; + std::uintptr_t decode[%d]; + } init = { { {}, { +)"; + + const auto total_decode_size = dispatch_tables_size + decode_vtbl_size; + const auto total_encode_size = + slots_and_strides_size + dispatch_tables_size + encode_vtbl_size; + + const auto headroom = total_decode_size * sizeof(uintptr_t) - + total_encode_size * sizeof(uint16_t); + char prelude[sizeof(prelude_format) + 5 * 6]; + std::snprintf( + prelude, sizeof(prelude), prelude_format, headroom / sizeof(uint16_t), + slots_and_strides_size, dispatch_tables_size, encode_vtbl_size, + total_decode_size); + os << prelude; + + std::vector methods; + methods.resize(compiler.methods.size()); + std::transform( + compiler.methods.begin(), compiler.methods.end(), methods.begin(), + [](auto& method) { return &method; }); + + for (auto& method : methods) { + os << indent << "// " + << boost::core::demangle(method->info->name.data()) << "\n"; + os << indent; + auto strides_iter = std::copy( + method->slots.begin(), method->slots.end(), + std::ostream_iterator(os, ", ")); + std::copy( + method->strides.begin(), method->strides.end(), + std::ostream_iterator(os, ", ")); + os << "\n"; + } + + os << " }, {\n"; + + for (auto& method : + range(compiler.methods.begin(), compiler.methods.end())) { + if (method.arity() < 2) { + continue; + } + + os << indent << "// " << boost::core::demangle(method.info->name.data()) + << "\n"; + os << indent; + auto dt_iter = std::transform( + method.dispatch_table.begin(), method.dispatch_table.end() - 1, + std::ostream_iterator(os, ", "), + [&method](auto entry) { return entry->group_index; }); + auto& last = method.dispatch_table.back(); + *dt_iter = (uint16_t)last->group_index | stop_bit; + os << "\n"; + } + + os << " }, {\n"; + + std::vector> vtbls(compiler.classes.size()); + + for (auto& cls : range(compiler.classes.begin(), compiler.classes.end())) { + os << indent << "// " + << boost::core::demangle( + reinterpret_cast(cls.type_ids[0]) + ->name()) + << "\n"; + + os << indent << cls.first_used_slot << ", "; + + for (auto& entry : cls.vtbl) { + os << " "; + auto stop = &entry == &cls.vtbl.back() ? stop_bit : 0; + + if (entry.vp_index > 0) { + // It's a multi-method, and not the first virtual parameter. + // Encode the index, it will be decoded as is. + os << (entry.group_index | index_bit | stop); + } else { + // It's a uni-method, or the first virtual parameter of a + // multi-method. Encode the method index and the spec index; + os << entry.method_index << ", " << (entry.group_index | stop); + } + + os << ", "; + } + + os << "\n"; + } + + os << R"( } } }; + + yorel::yomm2::generator::decode_dispatch_data(init); + + return init.decode; +} +)"; +} + +template +void generator::decode_dispatch_data(Data& init) { + using namespace yorel::yomm2::detail; + + constexpr auto pointer_size = sizeof(std::uintptr_t*); + + trace_type trace; + using indent = typename trace_type::indent; + + trace << "Decoding dispatch data for "; + Policy::type_name(Policy::template static_type(), trace); + trace << "\n"; + + auto method_count = 0, multi_method_count = 0; + + for (auto& method : Policy::methods) { + ++method_count; + + if (method.arity() >= 2) { + ++multi_method_count; + } + } + + ++trace << method_count << " methods, " << multi_method_count + << " multi-methods\n"; + + // First copy the slots and strides to the static arrays in methods. Also + // build an array of arrays of pointer to method definitions. Methods and + // definitions are in reverse order, because of how 'chain' works. While + // building the array of array of defintions, we put them back in the order + // in which the compiler saw them. + auto packed_slots_iter = init.packed.slots; + auto methods = (method_info**)alloca(method_count * pointer_size); + auto methods_iter = methods; + auto method_defs = (definition_info***)alloca(method_count * pointer_size); + auto method_defs_iter = method_defs; + auto dispatch_tables = + (std::uintptr_t**)alloca(method_count * pointer_size); + auto multi_method_to_method = + (size_t*)alloca(multi_method_count * sizeof(size_t)); + auto multi_method_to_method_iter = multi_method_to_method; + auto method_index = 0; + + for (auto& method : Policy::methods) { + ++trace << "method " << method.name << "\n"; + indent _(trace); + + *methods_iter++ = &method; + + ++trace << "specializations:\n"; + + for (auto& spec : method.specs) { + indent _(trace); + ++trace << spec.pf << " "; + Policy::type_name(spec.type, trace); + trace << "\n"; + } + + auto slots_strides_count = 2 * method.arity() - 1; + + // copy slots and strides into the method's static + ++trace << "installing " << slots_strides_count + << " slots and strides\n"; + std::copy_n( + packed_slots_iter, slots_strides_count, method.slots_strides_ptr); + packed_slots_iter += slots_strides_count; + + auto specs = + (definition_info**)alloca(method.specs.size() * pointer_size); + std::transform( + method.specs.begin(), method.specs.end(), specs, + [](auto& spec) { return &spec; }); + *method_defs_iter++ = specs; + ++trace << "specs index: " << specs << "\n"; + + if (method.arity() >= 2) { + ++trace << "m-method " + << (multi_method_to_method_iter - multi_method_to_method) + << " is method " << method_index << "\n"; + *multi_method_to_method_iter++ = method_index; + } + + ++method_index; + } + + // Build a table of pointers to dispatch tables, for multi-methods only. + + // decode the dispatch tables for multi-methods, and keep track of them in + // an array. We will use it when we fill the vtables. The packed dispatch + // tables are in compiler order. + + auto packed_iter = init.packed.dispatch; + auto decode_iter = init.decode; + + ++trace << "decoding multi-method dispatch tables\n"; + + for (auto i = 0; i < multi_method_count; ++i) { + assert((char*)decode_iter < (char*)packed_iter); + indent _(trace); + + dispatch_tables[i] = decode_iter; + ++trace << "multi-method " << i << " dispatch table at " << decode_iter + << "\n"; + + indent __(trace); + ++trace << "specs:"; + + auto defs = method_defs[multi_method_to_method[i]]; + + do { + auto spec_index = *packed_iter & ~stop_bit; + trace << " " << spec_index; + *decode_iter++ = (uintptr_t)defs[spec_index]->pf; + } while (!(*packed_iter++ & stop_bit)); + + trace << "\n"; + } + + ++trace << "decoding v-tables\n"; + + auto vtbl_iter = init.packed.vtbl; + + for (auto& cls : Policy::classes) { + if (*cls.static_vptr != nullptr) { + continue; + } + + indent _1(trace); + ++trace << "class "; + Policy::type_name(cls.type, trace); + trace << "\n"; + + indent _2(trace); + + auto first_slot = *vtbl_iter++; + ++trace << "first slot: " << first_slot << "\n"; + + *cls.static_vptr = decode_iter - first_slot; + + do { + auto entry = *vtbl_iter & ~stop_bit; + + if (entry & index_bit) { + auto index = *vtbl_iter & ~index_bit; + ++trace << "multi-method index " << index << "\n"; + *decode_iter++ = index; + } else { + auto method_index = entry; + auto group_index = *++vtbl_iter & ~stop_bit; + auto method = methods[method_index]; + + if (method->arity() == 1) { + ++trace << "uni-method " << method_index << " group " + << group_index << "\n"; + *decode_iter++ = + (std::uintptr_t)method_defs[method_index][group_index] + ->pf; + } else { + ++trace << "multi-method " << method_index << " group " + << group_index << "\n"; + *decode_iter++ = (std::uintptr_t)( + dispatch_tables[method_index] + group_index); + } + } + } while (!(*vtbl_iter++ & stop_bit)); + } + + using namespace policy; + + if constexpr (has_facet) { + Policy::publish_vptrs(Policy::classes.begin(), Policy::classes.end()); + } +} + +} // namespace yomm2 +} // namespace yorel + +#endif diff --git a/include/yorel/yomm2/policy.hpp b/include/yorel/yomm2/policy.hpp index 01b34afc..432d57e9 100644 --- a/include/yorel/yomm2/policy.hpp +++ b/include/yorel/yomm2/policy.hpp @@ -120,7 +120,7 @@ using method_call_error_handler = namespace policy { struct abstract_policy {}; -struct facet {}; + struct error_handler {}; struct runtime_checks {}; struct indirect_vptr {}; @@ -146,11 +146,6 @@ struct release_shared; namespace yorel { namespace yomm2 { -struct catalog { - detail::static_chain classes; - detail::static_chain methods; -}; - namespace policy { template @@ -164,16 +159,23 @@ template template std::uintptr_t* method_tables::static_vptr; +using class_catalog = detail::static_chain; +using method_catalog = detail::static_chain; + struct domain {}; template struct yOMM2_API_gcc basic_domain : domain, method_tables { - static struct catalog catalog; + static class_catalog classes; + static method_catalog methods; static std::vector dispatch_data; }; template -catalog basic_domain::catalog; +class_catalog basic_domain::classes; + +template +method_catalog basic_domain::methods; template std::vector basic_domain::dispatch_data; @@ -295,7 +297,6 @@ struct yOMM2_API_gcc vptr_vector : virtual external_vptr { template static void publish_vptrs(ForwardIterator first, ForwardIterator last) { using namespace policy; - using detail::pair_first_iterator; size_t size; @@ -413,6 +414,11 @@ bool basic_trace_output::trace_enabled([]() { template struct yOMM2_API_gcc fast_perfect_hash : virtual type_hash { + struct report { + size_t method_table_size, dispatch_table_size; + size_t hash_search_attempts; + }; + static type_id hash_mult; static std::size_t hash_shift; static std::size_t hash_length; @@ -613,7 +619,7 @@ struct yOMM2_API_gcc vectored_error : virtual error_handler { auto comma = ""; for (auto ti : - type_range{error->types, error->types + error->arity}) { + range{error->types, error->types + error->arity}) { Policy::error_stream << comma; Policy::type_name(ti, Policy::error_stream); comma = ", "; @@ -676,7 +682,7 @@ struct yOMM2_API_gcc backward_compatible_error_handler << " for " << error.method_name << "("; auto comma = ""; - for (auto ti : detail::type_range{ti_ptrs, ti_ptrs + arity}) { + for (auto ti : detail::range{ti_ptrs, ti_ptrs + arity}) { Policy::error_stream << comma; Policy::type_name(ti, Policy::error_stream); comma = ", "; @@ -752,6 +758,48 @@ using default_static = policy::debug; } // namespace policy +namespace detail { + +struct update_report : update_method_report {}; + +template +struct has_report_aux : std::false_type {}; + +template +struct has_report_aux> + : std::true_type {}; + +template +constexpr bool has_report = has_report_aux::value; + +template +struct aggregate_reports; + +template +struct aggregate_reports< + types, types, + std::void_t> { + using type = typename aggregate_reports< + types, types>::type; +}; + +template +struct aggregate_reports, types, Void> { + using type = typename aggregate_reports< + types, types>::type; +}; + +template +struct aggregate_reports, types<>, Void> { + struct type : Reports... {}; +}; + +template +using report_type = typename aggregate_reports< + types, typename Policy::facets>::type; + +} // namespace detail + } // namespace yomm2 } // namespace yorel diff --git a/src/yomm2.cpp b/src/yomm2.cpp index b3c39354..70df019b 100644 --- a/src/yomm2.cpp +++ b/src/yomm2.cpp @@ -26,7 +26,8 @@ namespace policy { template class yOMM2_API_msc basic_domain; template class yOMM2_API_msc vptr_vector; template class yOMM2_API_msc basic_indirect_vptr; -template class yOMM2_API_msc vectored_error>; +template class yOMM2_API_msc vectored_error< + debug_shared, backward_compatible_error_handler>; template class yOMM2_API_msc backward_compatible_error_handler; template class yOMM2_API_msc fast_perfect_hash; template class yOMM2_API_msc checked_perfect_hash; @@ -39,10 +40,12 @@ template class yOMM2_API_msc basic_policy< } // namespace policy -template void yOMM2_API_gcc yOMM2_API_msc update(); +template auto yOMM2_API_gcc yOMM2_API_msc update() + -> detail::compiler; -yOMM2_API_gcc yOMM2_API_msc void update() { - update(); +yOMM2_API_gcc yOMM2_API_msc auto update() + -> detail::compiler { + return update(); } yOMM2_API_gcc yOMM2_API_msc error_handler_type diff --git a/tests/benchmarks.cpp b/tests/benchmarks.cpp index 6803405d..de8f5aae 100644 --- a/tests/benchmarks.cpp +++ b/tests/benchmarks.cpp @@ -25,7 +25,7 @@ int main() { #include #include -#include +#include #include #include "benchmarks_parameters.hpp" @@ -44,6 +44,10 @@ enum { NH = YOMM2_BENCHMARK_HIERARCHIES }; enum { NH = 100 }; #endif +#define mb() asm volatile("mfence":::"memory") +#define rmb() asm volatile("lfence":::"memory") +#define wmb() asm volatile("sfence" ::: "memory") + const std::string yomm2_ = "yomm2_"; auto OBJECTS() { diff --git a/tests/benchmarks_parameters.hpp b/tests/benchmarks_parameters.hpp index 69176768..4c2a2efa 100644 --- a/tests/benchmarks_parameters.hpp +++ b/tests/benchmarks_parameters.hpp @@ -1 +1,5 @@ +#ifdef _WIN32 +#define YOMM2_BENCHMARK_HIERARCHIES 20 +#else #define YOMM2_BENCHMARK_HIERARCHIES 100 +#endif diff --git a/tests/blackbox.cpp b/tests/blackbox.cpp index d610f6db..3199c163 100644 --- a/tests/blackbox.cpp +++ b/tests/blackbox.cpp @@ -19,14 +19,17 @@ using yorel::yomm2::detail::types; namespace states { +using test_policy = test_policy_<__COUNTER__>; using std::string; struct Animal { Animal(const Animal&) = delete; Animal() : name("wrong") { } + virtual ~Animal() { } + std::string name; }; @@ -42,11 +45,9 @@ struct Cat : virtual Animal { } }; -YOMM2_CLASS(Animal); -YOMM2_CLASS(Dog, Animal); -YOMM2_CLASS(Cat, Animal); +YOMM2_CLASSES(Animal, Dog, Cat, test_policy); -YOMM2_DECLARE(string, name, (virtual_)); +YOMM2_DECLARE(string, name, (virtual_), test_policy); YOMM2_DEFINE(string, name, (const Dog& dog)) { return "dog " + dog.name; @@ -57,7 +58,7 @@ YOMM2_DEFINE(string, name, (const Cat& cat)) { } BOOST_AUTO_TEST_CASE(initializing) { - update(); + update(); const Animal& dog = Dog("spot"); BOOST_TEST("dog spot" == name(dog)); const Animal& cat = Cat("felix"); @@ -169,7 +170,7 @@ YOMM2_DEFINE(Subtype, zero_ptr, (diagonal_matrix * m)) { } BOOST_AUTO_TEST_CASE(simple) { - update(); + auto report = update(); { const matrix& dense = dense_matrix(); @@ -377,3 +378,86 @@ BOOST_AUTO_TEST_CASE(across_namespaces) { } } // namespace across_namespaces + +namespace report { + +using test_policy = test_policy_<__COUNTER__>; + +struct Animal { + virtual void foo() = 0; +}; + +struct Dog : Animal { + void foo() override { + } +}; + +struct Cat : Animal { + void foo() override { + } +}; + +struct kick_; +struct pet_; +struct meet_; + +template +void fn(Class&...) { +} + +YOMM2_CLASSES(Animal, Dog, Cat, test_policy); + +BOOST_AUTO_TEST_CASE(update_report) { + using kick = method), test_policy>; + using pet = method), test_policy>; + using meet = + method, virtual_), test_policy>; + + auto report = update().report; + BOOST_TEST(report.not_implemented == 3); + BOOST_TEST(report.concrete_not_implemented == 3); + BOOST_TEST(report.ambiguous == 0); + BOOST_TEST(report.concrete_ambiguous == 0); + // 'meet' dispatch table is one cell, containing 'not_implemented' + BOOST_TEST(report.cells == 1); + BOOST_TEST(report.concrete_cells == 1); + + YOMM2_STATIC(kick::add_function>); + report = update().report; + BOOST_TEST(report.not_implemented == 2); + BOOST_TEST(report.concrete_not_implemented == 2); + + YOMM2_STATIC(pet::add_function>); + YOMM2_STATIC(pet::add_function>); + report = update().report; + BOOST_TEST(report.not_implemented == 2); + BOOST_TEST(report.concrete_not_implemented == 1); + + // create ambiguity + YOMM2_STATIC(meet::add_function>); + YOMM2_STATIC(meet::add_function>); + report = update().report; + BOOST_TEST(report.cells == 4); + BOOST_TEST(report.concrete_cells == 4); + BOOST_TEST(report.ambiguous == 1); + BOOST_TEST(report.concrete_ambiguous == 1); + + YOMM2_STATIC(meet::add_function>); + report = update().report; + BOOST_TEST(report.cells == 6); + BOOST_TEST(report.concrete_cells == 4); + BOOST_TEST(report.ambiguous == 1); + BOOST_TEST(report.concrete_ambiguous == 1); + + // shadow ambiguity + YOMM2_STATIC(meet::add_function>); + YOMM2_STATIC(meet::add_function>); + YOMM2_STATIC(meet::add_function>); + report = update().report; + BOOST_TEST(report.cells == 9); + BOOST_TEST(report.concrete_cells == 4); + BOOST_TEST(report.ambiguous == 0); + BOOST_TEST(report.concrete_ambiguous == 0); +} + +} // namespace report diff --git a/tests/compiler.cpp b/tests/compiler.cpp index dfef24ef..89e0bd02 100644 --- a/tests/compiler.cpp +++ b/tests/compiler.cpp @@ -2,18 +2,23 @@ #include #include -#include +#include #include "test_helpers.hpp" -#define BOOST_TEST_MODULE runtime +#define BOOST_TEST_MODULE compiler #include using namespace yorel::yomm2; using namespace yorel::yomm2::detail; -std::ostream& operator<<(std::ostream& os, const rt_class* cls) { - return os << reinterpret_cast(cls)->name(); +using class_ = generic_compiler::class_; +using cc_method = generic_compiler::method; +using definition = generic_compiler::definition; + +std::ostream& operator<<(std::ostream& os, const class_* cls) { + return os + << reinterpret_cast(cls->type_ids[0])->name(); } std::string empty = "{}"; @@ -45,7 +50,7 @@ auto str(T... args) { template auto sstr(Ts... args) { - std::vector vec{args...}; + std::vector vec{args...}; std::sort(vec.begin(), vec.end()); return str(vec); } @@ -72,9 +77,9 @@ std::ostream& operator<<(std::ostream& os, const std::vector& vec) { return os; } -template -auto get_class(const Runtime& rt) { - return rt.class_map.at(typeid(T)); +template +auto get_class(const Compiler& comp) { + return comp.class_map.at(typeid(T)); } namespace ns_use_classes { @@ -103,15 +108,16 @@ YOMM2_STATIC(use_classes); using policies = std::tuple; BOOST_AUTO_TEST_CASE_TEMPLATE(test_use_classes, Key, policies) { - runtime rt; - rt.update(); + compiler comp; + comp.compile(); + comp.install_global_tables(); - auto animal = get_class(rt); - auto herbivore = get_class(rt); - auto carnivore = get_class(rt); - auto cow = get_class(rt); - auto wolf = get_class(rt); - auto human = get_class(rt); + auto animal = get_class(comp); + auto herbivore = get_class(comp); + auto carnivore = get_class(comp); + auto cow = get_class(comp); + auto wolf = get_class(comp); + auto human = get_class(comp); BOOST_TEST(animal->direct_bases.empty()); BOOST_TEST(sstr(animal->direct_derived) == sstr(herbivore, carnivore)); @@ -192,76 +198,76 @@ YOMM2_DEFINE( BOOST_AUTO_TEST_CASE(runtime_test) { - runtime rt; + compiler comp; - rt.augment_classes(); + comp.augment_classes(); - auto role = get_class(rt); - auto employee = get_class(rt); - auto manager = get_class(rt); - auto founder = get_class(rt); - auto expense = get_class(rt); - auto public_ = get_class(rt); - auto bus = get_class(rt); - auto metro = get_class(rt); - auto taxi = get_class(rt); - auto jet = get_class(rt); + auto role = get_class(comp); + auto employee = get_class(comp); + auto manager = get_class(comp); + auto founder = get_class(comp); + auto expense = get_class(comp); + auto public_ = get_class(comp); + auto bus = get_class(comp); + auto metro = get_class(comp); + auto taxi = get_class(comp); + auto jet = get_class(comp); BOOST_TEST(sstr(role->direct_bases) == empty); BOOST_TEST(sstr(employee->direct_bases) == sstr(role)); { - std::vector expected = {employee}; + std::vector expected = {employee}; BOOST_TEST(manager->direct_bases == expected); } { - std::vector expected = {role}; + std::vector expected = {role}; BOOST_TEST(founder->direct_bases == expected); } { - std::vector expected = {expense}; + std::vector expected = {expense}; BOOST_TEST(public_->direct_bases == expected); } { - std::vector expected = {public_}; + std::vector expected = {public_}; BOOST_TEST(bus->direct_bases == expected); } { - std::vector expected = {public_}; + std::vector expected = {public_}; BOOST_TEST(metro->direct_bases == expected); } { - std::vector expected = {expense}; + std::vector expected = {expense}; BOOST_TEST(taxi->direct_bases == expected); } { - std::vector expected = {expense}; + std::vector expected = {expense}; BOOST_TEST(jet->direct_bases == expected); } { - std::vector expected = {employee, founder}; + std::vector expected = {employee, founder}; BOOST_TEST(sstr(role->direct_derived) == sstr(expected)); } { - std::vector expected = {manager}; + std::vector expected = {manager}; BOOST_TEST(sstr(employee->direct_derived) == sstr(expected)); } { - std::vector expected = {public_, taxi, jet}; + std::vector expected = {public_, taxi, jet}; BOOST_TEST(sstr(expense->direct_derived) == sstr(expected)); } { - std::vector expected = {bus, metro}; + std::vector expected = {bus, metro}; BOOST_TEST(sstr(public_->direct_derived) == sstr(expected)); } @@ -279,34 +285,34 @@ BOOST_AUTO_TEST_CASE(runtime_test) { sstr(expense, public_, taxi, jet, bus, metro)); BOOST_TEST(sstr(public_->compatible_classes) == sstr(public_, bus, metro)); - rt.augment_methods(); + comp.augment_methods(); - BOOST_TEST_REQUIRE(rt.methods.size() == 2); - auto method_iter = rt.methods.begin(); - rt_method& pay_method = *method_iter++; - BOOST_TEST_REQUIRE(pay_method.vp.size() == 1); - rt_method& approve_method = *method_iter++; + BOOST_TEST_REQUIRE(comp.methods.size() == 2); + auto method_iter = comp.methods.begin(); + cc_method& approve_method = *method_iter++; BOOST_TEST_REQUIRE(approve_method.vp.size() == 2); + cc_method& pay_method = *method_iter++; + BOOST_TEST_REQUIRE(pay_method.vp.size() == 1); { - std::vector expected = {employee}; + std::vector expected = {employee}; BOOST_TEST_INFO("result = " + sstr(pay_method.vp)); BOOST_TEST_INFO("expected = " + sstr(expected)); BOOST_TEST(pay_method.vp == expected); } { - std::vector expected = {role, expense}; + std::vector expected = {role, expense}; BOOST_TEST_INFO("result = " << sstr(approve_method.vp)); BOOST_TEST_INFO("expected = " << sstr(expected)); BOOST_TEST(approve_method.vp == expected); } { - auto c_iter = test_policy::catalog.methods.begin(); - auto r_iter = rt.methods.rbegin(); + auto c_iter = test_policy::methods.begin(); + auto r_iter = comp.methods.begin(); - for (int i = 0; i < rt.methods.size(); ++i) { + for (int i = 0; i < comp.methods.size(); ++i) { BOOST_TEST_INFO("i = " << i); auto& c_meth = *c_iter++; auto& r_meth = *r_iter++; @@ -338,7 +344,7 @@ BOOST_AUTO_TEST_CASE(runtime_test) { BOOST_TEST(expense->used_by_vp[0].method == &approve_method); BOOST_TEST(expense->used_by_vp[0].param == 1); - rt.allocate_slots(); + comp.allocate_slots(); { const std::vector expected = {1}; @@ -363,39 +369,39 @@ BOOST_AUTO_TEST_CASE(runtime_test) { BOOST_TEST_REQUIRE(jet->vtbl.size() == 1); auto pay_method_iter = pay_method.specs.begin(); - auto pay_Employee = pay_method_iter++; auto pay_Manager = pay_method_iter++; + auto pay_Employee = pay_method_iter++; auto spec_iter = approve_method.specs.begin(); - auto approve_Role_Expense = spec_iter++; - auto approve_Employee_public = spec_iter++; - auto approve_Manager_Taxi = spec_iter++; auto approve_Founder_Expense = spec_iter++; + auto approve_Manager_Taxi = spec_iter++; + auto approve_Employee_public = spec_iter++; + auto approve_Role_Expense = spec_iter++; { - BOOST_TEST(runtime::is_more_specific( + BOOST_TEST(compiler::is_more_specific( &*approve_Founder_Expense, &*approve_Role_Expense)); - BOOST_TEST(runtime::is_more_specific( + BOOST_TEST(compiler::is_more_specific( &*approve_Manager_Taxi, &*approve_Role_Expense)); - BOOST_TEST(!runtime::is_more_specific( + BOOST_TEST(!compiler::is_more_specific( &*approve_Role_Expense, &*approve_Role_Expense)); { - std::vector expected = {&*approve_Manager_Taxi}; - std::vector specs = { + std::vector expected = {&*approve_Manager_Taxi}; + std::vector specs = { &*approve_Role_Expense, &*approve_Manager_Taxi}; - BOOST_TEST(expected == runtime::best(specs)); + BOOST_TEST(expected == compiler::best(specs)); } } { - BOOST_TEST(runtime::is_base( + BOOST_TEST(compiler::is_base( &*approve_Role_Expense, &*approve_Founder_Expense)); - BOOST_TEST(!runtime::is_base( + BOOST_TEST(!compiler::is_base( &*approve_Role_Expense, &*approve_Role_Expense)); } - rt.build_dispatch_tables(); + comp.build_dispatch_tables(); { BOOST_TEST_REQUIRE(pay_method.dispatch_table.size() == 2); @@ -403,83 +409,12 @@ BOOST_AUTO_TEST_CASE(runtime_test) { BOOST_TEST(pay_method.dispatch_table[1]->pf == pay_Manager->pf); } - { - // expected dispatch table here: - // https://www.codeproject.com/Articles/859492/Open-Multi-Methods-for-Cplusplus-Part-Inside-Yomm - BOOST_TEST_REQUIRE(approve_method.dispatch_table.size() == 12); - BOOST_TEST_REQUIRE(approve_method.strides.size() == 1); - BOOST_TEST_REQUIRE(approve_method.strides[0] == 4); - - auto dp_iter = approve_method.dispatch_table.begin(); - BOOST_TEST((*dp_iter++)->pf == approve_Role_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Role_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Role_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Founder_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Role_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Employee_public->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Employee_public->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Founder_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Role_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Role_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Manager_Taxi->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Founder_Expense->pf); - } - - { - const std::vector expected = {0}; - BOOST_TEST(expected == role->vtbl); - } - - { - const std::vector expected = {1, 0}; - BOOST_TEST(expected == employee->vtbl); - } - - { - const std::vector expected = {2, 1}; - BOOST_TEST(expected == manager->vtbl); - } - - { - const std::vector expected = {3}; - BOOST_TEST(expected == founder->vtbl); - } - - { - const std::vector expected = {0}; - BOOST_TEST(expected == expense->vtbl); - } - - { - const std::vector expected = {1}; - BOOST_TEST(expected == public_->vtbl); - } - - { - const std::vector expected = {1}; - BOOST_TEST(expected == bus->vtbl); - } - - { - const std::vector expected = {1}; - BOOST_TEST(expected == metro->vtbl); - } - - { - const std::vector expected = {2}; - BOOST_TEST(expected == taxi->vtbl); - } - - { - const std::vector expected = {0}; - BOOST_TEST(expected == jet->vtbl); - } BOOST_TEST_REQUIRE(pay_Employee->info->next != nullptr); BOOST_TEST_REQUIRE(pay_Manager->info->next != nullptr); BOOST_TEST(*pay_Manager->info->next == pay_Employee->info->pf); - rt.install_gv(); + comp.install_gv(); { // pay @@ -535,7 +470,7 @@ BOOST_AUTO_TEST_CASE(runtime_test) { // // Plane // BOOST_TEST(gv_iter++->i == 0); // approve/1 - rt.optimize(); + comp.optimize(); // // Role // BOOST_TEST(opt_iter++->pw == 0 + approve_dispatch_table); // @@ -680,17 +615,17 @@ using test_policy = test_policy_<__COUNTER__>; YOMM2_STATIC(use_classes); BOOST_AUTO_TEST_CASE(test_use_classes_mi) { - std::vector actual, expected; + std::vector actual, expected; - runtime rt; - rt.augment_classes(); + compiler comp; + comp.augment_classes(); - auto a = get_class(rt); - auto b = get_class(rt); - auto ab = get_class(rt); - auto c = get_class(rt); - auto d = get_class(rt); - auto e = get_class(rt); + auto a = get_class(comp); + auto b = get_class(comp); + auto ab = get_class(comp); + auto c = get_class(comp); + auto d = get_class(comp); + auto e = get_class(comp); // ----------------------------------------------------------------------- // A @@ -737,17 +672,18 @@ auto& m_c = method), test_policy>::fn; auto& m_d = method), test_policy>::fn; BOOST_AUTO_TEST_CASE(test_allocate_slots_mi) { - runtime rt; - rt.augment_classes(); - rt.augment_methods(); - rt.allocate_slots(); + compiler comp; + comp.augment_classes(); + comp.augment_methods(); + comp.allocate_slots(); - auto m_iter = rt.methods.begin(); - auto m_a = m_iter++; - auto m_b = m_iter++; - auto m_ab = m_iter++; - auto m_c = m_iter++; + auto m_iter = comp.methods.begin(); auto m_d = m_iter++; + auto m_c = m_iter++; + auto m_ab = m_iter++; + auto m_b = m_iter++; + auto m_a = m_iter++; + // lattice: // A B diff --git a/tests/rdtsc-benchmark.cpp b/tests/rdtsc-benchmark.cpp new file mode 100644 index 00000000..6611fbdc --- /dev/null +++ b/tests/rdtsc-benchmark.cpp @@ -0,0 +1,393 @@ +// Copyright (c) 2018-2024 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include +#include +#include + +using std::cout; +using std::setw; +using namespace yorel::yomm2; +using namespace policy; + +struct intrusive : default_policy::rebind::remove< + external_vptr>::remove { + template + static auto dynamic_vptr(const Class& arg) { + return arg.yomm2_vptr; + } +}; + +struct std_unordered_map + : default_policy::rebind::replace< + external_vptr, vptr_map>::remove {}; + +#if __has_include() + +#include +#define FLAT_MAP_AVAILABLE + +struct flat_std_unordered_map + : default_policy::rebind::replace< + external_vptr, + vptr_map< + flat_std_unordered_map, + boost::unordered_flat_map>>:: + remove {}; + +#endif + +namespace stat { + +struct Animal { + virtual ~Animal() { + } + + virtual void pet_vf() = 0; + + const std::uintptr_t* yomm2_vptr = 0; +}; + +struct Cat : Animal { + Cat() { + yomm2_vptr = intrusive::static_vptr; + } + + void pet_vf() override{/*purr*/}; +}; + +struct YOMM2_SYMBOL(pet_ref); +struct YOMM2_SYMBOL(pet_vp); +struct YOMM2_SYMBOL(pet_iptr); + +} // namespace stat + +namespace dyn { + +struct Animal { + virtual ~Animal() { + } + + virtual void pet_vf() = 0; + + const std::uintptr_t* yomm2_vptr = 0; +}; + +struct Cat : Animal { + Cat() { + yomm2_vptr = intrusive::static_vptr; + } + + void pet_vf() override{/*purr*/}; +}; + +} // namespace dyn + +template +struct use_policy { + use_policy() { + YOMM2_STATIC(use_classes< + dyn::Animal, dyn::Cat, stat::Animal, stat::Cat, Policy>); + update(); + } +}; + +std::vector, stat::Animal&, + virtual_ptr)>> + benchmarks; + +struct register_benchmark { + explicit register_benchmark( + std::string name, + unsigned (*fun)( + dyn::Animal&, virtual_ptr, stat::Animal&, + virtual_ptr)) { + benchmarks.emplace_back(name, fun); + } +}; + +#define BENCHMARK(NAME, EXPR) \ + __attribute__((noinline)) unsigned NAME( \ + dyn::Animal& dyn_ref, virtual_ptr dyn_vp, \ + stat::Animal& stat_ref, virtual_ptr stat_vp) { \ + unsigned int dummy; \ + _mm_sfence(); \ + _mm_lfence(); \ + auto start = __rdtscp(&dummy); \ + EXPR; \ + auto end = __rdtscp(&dummy); \ + _mm_sfence(); \ + _mm_lfence(); \ + return end - start; \ + } \ + register_benchmark YOMM2_GENSYM(#NAME, NAME) + +// ----------------------------------------------------------------------------- +// ovh + +BENCHMARK(ovh, {}); + +// ----------------------------------------------------------------------------- +// virtual function + +BENCHMARK(vf, dyn_ref.pet_vf()); + +// ----------------------------------------------------------------------------- +// others + +namespace dyn { + +// ----------------------------------------------------------------------------- +// method, argument passed by reference + +declare_method(void, pet_ref, (virtual_)); + +// Implement 'pet_ref' for Cats. +define_method(void, pet_ref, (Cat & Cat)) { + // purr +} + +BENCHMARK(ref, pet_ref(dyn_ref)); + +// ----------------------------------------------------------------------------- +// method, argument passed by virtual_ptr + +declare_method(void, pet_vp, (virtual_ptr)); + +// Implement 'pet_vp' for Cats. +define_method(void, pet_vp, (virtual_ptr cat)) { + // purr +} + +BENCHMARK(vp, pet_vp(dyn_vp)); + +// ref and vp +YOMM2_STATIC(use_policy); + +// ----------------------------------------------------------------------------- +// intrusive + +declare_method(void, pet_iptr, (virtual_), intrusive); + +// Implement 'pet_iptr' for Cats. +define_method(void, pet_iptr, (Cat & Cat)) { + // purr +} + +YOMM2_STATIC(use_policy); +BENCHMARK(iptr, pet_iptr(dyn_ref)); + +// ----------------------------------------------------------------------------- +// std::std_unordered_map + +declare_method(void, pet_sum, (virtual_), std_unordered_map); + +// Implement 'pet_sum' for Cats. +define_method(void, pet_sum, (Cat & Cat)) { + // purr +} + +YOMM2_STATIC(use_policy); +BENCHMARK(sum, pet_sum(dyn_ref)); + +// ----------------------------------------------------------------------------- +// boost::flat_std_unordered_map + +#ifdef FLAT_MAP_AVAILABLE + +declare_method(void, pet_fum, (virtual_), flat_std_unordered_map); + +// Implement 'pet_fum' for Cats. +define_method(void, pet_fum, (Cat & Cat)) { + // purr +} + +YOMM2_STATIC(use_policy); +BENCHMARK(fum, pet_fum(dyn_ref)); + +#endif + +} // namespace dyn + +namespace stat { + +// ----------------------------------------------------------------------------- +// method, argument passed by reference + +declare_method(void, pet_ref, (virtual_)); + +// Implement 'pet_ref' for Cats. +define_method(void, pet_ref, (Cat & Cat)) { + // purr +} + +BENCHMARK(stat_ref, pet_ref(stat_ref)); + +// ----------------------------------------------------------------------------- +// method, argument passed by virtual_ptr + +declare_method(void, pet_vp, (virtual_ptr)); + +// Implement 'pet_vp' for Cats. +define_method(void, pet_vp, (virtual_ptr cat)) { + // purr +} + +BENCHMARK(stat_vp, pet_vp(stat_vp)); + +// ref and vp +YOMM2_STATIC(use_policy); + +// ----------------------------------------------------------------------------- +// intrusive + +declare_method(void, pet_iptr, (virtual_), intrusive); + +// Implement 'pet_iptr' for Cats. +define_method(void, pet_iptr, (Cat & Cat)) { + // purr +} + +YOMM2_STATIC(use_policy); +BENCHMARK(stat_iptr, pet_iptr(stat_ref)); + +// ----------------------------------------------------------------------------- +// std::std_unordered_map + +declare_method(void, pet_sum, (virtual_), std_unordered_map); + +// Implement 'pet_sum' for Cats. +define_method(void, pet_sum, (Cat & Cat)) { + // purr +} + +YOMM2_STATIC(use_policy); +BENCHMARK(stat_sum, pet_sum(stat_ref)); + +// ----------------------------------------------------------------------------- +// boost::flat_std_unordered_map + +#ifdef FLAT_MAP_AVAILABLE + +declare_method(void, pet_fum, (virtual_), flat_std_unordered_map); + +// Implement 'pet_fum' for Cats. +define_method(void, pet_fum, (Cat & Cat)) { + // purr +} + +YOMM2_STATIC(use_policy); +BENCHMARK(stat_fum, pet_fum(stat_ref)); + +#endif + +} // namespace stat + +// ----------------------------------------------------------------------------- +// driver + +__attribute__((noinline)) void flush_cache() { + std::vector v1, v2; + + for (int n = 1024 * 1024; n--;) { + v1.push_back(n); + v2.push_back(n); + } + + for (int i = 0; i < v1.size(); ++i) { + v1[i] += v2[i]; + } +} + +int main(int argc, char** argv) { + dyn::Cat dyn_ref; + auto dyn_vp = virtual_ptr(dyn_ref); + stat::Cat stat_ref; + auto stat_vp = virtual_ptr(stat_ref); + + std::string what = "all"; + bool flush = true; + int count = 10; + + auto arg = argv + 1; + + if (*arg) { + what = *arg++; + } + + // if (what == "generate") { + // boost::mp11::mp_for_each>([](auto Policy) { + // { + // compiler comp; + // comp.compile(); + // comp.write_static_slots(std::cout); + // } + // }); + + // return 0; + // } + + if (*arg) { + count = std::stoi(*arg++); + } + + if (*arg) { + flush = !strcmp(*arg++, "flush"); + } + + if (what == "all") { + for (auto& benchmark : benchmarks) { + cout << setw(10) << benchmark.first; + } + cout << "\n"; + + if (!flush) { + for (auto& benchmark : benchmarks) { + benchmark.second(dyn_ref, dyn_vp, stat_ref, stat_vp); + } + } + + for (auto i = count; i--;) { + for (auto& benchmark : benchmarks) { + if (flush) { + flush_cache(); + } + + cout << setw(10) + << benchmark.second(dyn_ref, dyn_vp, stat_ref, stat_vp); + } + cout << "\n"; + } + } else { + auto benchmark = + std::find_if(benchmarks.begin(), benchmarks.end(), [what](auto bm) { + return bm.first == what; + }); + + if (benchmark == benchmarks.end()) { + std::cerr << "unknown benchmark " << what << "\n"; + exit(1); + } + + if (flush) { + flush_cache(); + } else { + benchmark->second(dyn_ref, dyn_vp, stat_ref, stat_vp); + } + + cout << setw(10) + << benchmark->second(dyn_ref, dyn_vp, stat_ref, stat_vp); + } + + return 0; +} diff --git a/tests/run-rdtsc-benchmark b/tests/run-rdtsc-benchmark new file mode 100755 index 00000000..e4d9403d --- /dev/null +++ b/tests/run-rdtsc-benchmark @@ -0,0 +1,17 @@ +#!/bin/bash + +# Copyright (c) 2018-2024 Jean-Louis Leroy +# Distributed under the Boost Software License, Version 1.0. +# See accompanying file LICENSE_1_0.txt +# or copy at http://www.boost.org/LICENSE_1_0.txt) + +count=$1 +cache_state=$2 +prog=${YOMM2_BENCHMARK-$(dirname $0)/../build/tests/rdtsc-benchmark} + +for i in $(seq $count); do + for dispatch in 'ovh' 'vf' 'ref' 'vp' 'iptr' 'sum' 'fum' 'stat_ref' 'stat_vp' 'stat_iptr' 'stat_sum' 'stat_fum'; do + $prog $dispatch $cache_state + done + echo +done diff --git a/tests/runtime.cpp b/tests/runtime.cpp deleted file mode 100644 index 6c8f4ce7..00000000 --- a/tests/runtime.cpp +++ /dev/null @@ -1,797 +0,0 @@ -#include -#include - -#include - -#include "test_helpers.hpp" - -#define BOOST_TEST_MODULE compiler -#include - -using namespace yorel::yomm2; -using namespace yorel::yomm2::detail; - -std::ostream& operator<<(std::ostream& os, const rt_class* cls) { - return os << reinterpret_cast(cls)->name(); -} - -std::string empty = "{}"; - -template typename Container, typename T> -auto str(const Container& container) { - std::ostringstream os; - os << "{"; - const char* sep = ""; - - for (const auto& item : container) { - os << sep << item; - sep = ", "; - } - - os << "}"; - - return os.str(); -} - -template -auto str(T... args) { - std::ostringstream os; - os << "{"; - const char* sep = ""; - ((os << sep, os << args, sep = ", "), ...); - return os.str(); -} - -template -auto sstr(Ts... args) { - std::vector vec{args...}; - std::sort(vec.begin(), vec.end()); - return str(vec); -} - -template -auto sstr(const std::unordered_set& container) { - return sstr(std::vector(container.begin(), container.end())); -} - -template -bool contains(const C& coll, const T& value) { - return std::find(coll.begin(), coll.end(), value) != coll.end(); -} - -template -std::ostream& operator<<(std::ostream& os, const std::vector& vec) { - os << "{ "; - const char* sep = ""; - for (auto& val : vec) { - os << sep << val; - sep = ", "; - } - os << " }"; - return os; -} - -template -auto get_class(const Runtime& rt) { - return rt.class_map.at(typeid(T)); -} - -namespace ns_use_classes { - -struct Animal { - virtual ~Animal() { - } -}; - -struct Herbivore : virtual Animal {}; -struct Carnivore : virtual Animal {}; -struct Cow : Herbivore {}; -struct Wolf : Carnivore {}; -struct Human : Carnivore, Herbivore {}; - -using whole_hierarchy = test_policy_<__COUNTER__>; -using incremental = test_policy_<__COUNTER__>; - -use_classes - YOMM2_GENSYM; - -YOMM2_STATIC(use_classes); -YOMM2_STATIC(use_classes); -YOMM2_STATIC(use_classes); - -using policies = std::tuple; - -BOOST_AUTO_TEST_CASE_TEMPLATE(test_use_classes, Key, policies) { - runtime rt; - rt.update(); - - auto animal = get_class(rt); - auto herbivore = get_class(rt); - auto carnivore = get_class(rt); - auto cow = get_class(rt); - auto wolf = get_class(rt); - auto human = get_class(rt); - - BOOST_TEST(animal->direct_bases.empty()); - BOOST_TEST(sstr(animal->direct_derived) == sstr(herbivore, carnivore)); - BOOST_TEST(sstr(herbivore->direct_bases) == sstr(animal)); - BOOST_TEST(sstr(herbivore->direct_derived) == sstr(cow, human)); - BOOST_TEST(sstr(carnivore->direct_bases) == sstr(animal)); - BOOST_TEST(sstr(carnivore->direct_derived) == sstr(wolf, human)); - BOOST_TEST(sstr(cow->direct_bases) == sstr(herbivore)); - BOOST_TEST(cow->direct_derived.empty()); - BOOST_TEST(sstr(wolf->direct_bases) == sstr(carnivore)); - BOOST_TEST(wolf->direct_derived.empty()); - BOOST_TEST(sstr(human->direct_bases) == sstr(carnivore, herbivore)); - BOOST_TEST(human->direct_derived.empty()); -} -} // namespace ns_use_classes - -namespace rolex { - -struct Role { - virtual ~Role() { - } -}; - -struct Employee : Role {}; -struct Manager : Employee {}; -struct Founder : Role {}; - -struct Expense { - virtual ~Expense() { - } -}; - -struct Public : Expense {}; -struct Bus : Public {}; -struct Metro : Public {}; -struct Taxi : Expense {}; -struct Jet : Expense {}; - -using test_policy = test_policy_<__COUNTER__>; -// any type from this namespace would work. - -use_classes - YOMM2_GENSYM; - -YOMM2_STATIC(use_classes); - -YOMM2_DECLARE(double, pay, (virtual_), test_policy); - -YOMM2_DECLARE( - bool, approve, (virtual_, virtual_, double), - test_policy); - -YOMM2_DEFINE(double, pay, (const Employee&)) { - return 3000; -} - -YOMM2_DEFINE(double, pay, (const Manager& exec)) { - return next(exec) + 2000; -} - -YOMM2_DEFINE(bool, approve, (const Role& r, const Expense& e, double amount)) { - return false; -} - -YOMM2_DEFINE( - bool, approve, (const Employee& r, const Public& e, double amount)) { - return true; -} - -YOMM2_DEFINE(bool, approve, (const Manager& r, const Taxi& e, double amount)) { - return true; -} - -YOMM2_DEFINE( - bool, approve, (const Founder& r, const Expense& e, double amount)) { - return true; -} - -BOOST_AUTO_TEST_CASE(runtime_test) { - - runtime rt; - - rt.augment_classes(); - - auto role = get_class(rt); - auto employee = get_class(rt); - auto manager = get_class(rt); - auto founder = get_class(rt); - auto expense = get_class(rt); - auto public_ = get_class(rt); - auto bus = get_class(rt); - auto metro = get_class(rt); - auto taxi = get_class(rt); - auto jet = get_class(rt); - - BOOST_TEST(sstr(role->direct_bases) == empty); - BOOST_TEST(sstr(employee->direct_bases) == sstr(role)); - - { - std::vector expected = {employee}; - BOOST_TEST(manager->direct_bases == expected); - } - - { - std::vector expected = {role}; - BOOST_TEST(founder->direct_bases == expected); - } - - { - std::vector expected = {expense}; - BOOST_TEST(public_->direct_bases == expected); - } - - { - std::vector expected = {public_}; - BOOST_TEST(bus->direct_bases == expected); - } - - { - std::vector expected = {public_}; - BOOST_TEST(metro->direct_bases == expected); - } - - { - std::vector expected = {expense}; - BOOST_TEST(taxi->direct_bases == expected); - } - - { - std::vector expected = {expense}; - BOOST_TEST(jet->direct_bases == expected); - } - - { - std::vector expected = {employee, founder}; - BOOST_TEST(sstr(role->direct_derived) == sstr(expected)); - } - - { - std::vector expected = {manager}; - BOOST_TEST(sstr(employee->direct_derived) == sstr(expected)); - } - - { - std::vector expected = {public_, taxi, jet}; - BOOST_TEST(sstr(expense->direct_derived) == sstr(expected)); - } - - { - std::vector expected = {bus, metro}; - BOOST_TEST(sstr(public_->direct_derived) == sstr(expected)); - } - - BOOST_TEST(bus->direct_derived.size() == 0); - BOOST_TEST(metro->direct_derived.size() == 0); - BOOST_TEST(taxi->direct_derived.size() == 0); - BOOST_TEST(jet->direct_derived.size() == 0); - - BOOST_TEST( - sstr(role->compatible_classes) == - sstr(role, employee, founder, manager)); - BOOST_TEST(sstr(founder->compatible_classes) == sstr(founder)); - BOOST_TEST( - sstr(expense->compatible_classes) == - sstr(expense, public_, taxi, jet, bus, metro)); - BOOST_TEST(sstr(public_->compatible_classes) == sstr(public_, bus, metro)); - - rt.augment_methods(); - - BOOST_TEST_REQUIRE(rt.methods.size() == 2); - auto method_iter = rt.methods.begin(); - rt_method& pay_method = *method_iter++; - BOOST_TEST_REQUIRE(pay_method.vp.size() == 1); - rt_method& approve_method = *method_iter++; - BOOST_TEST_REQUIRE(approve_method.vp.size() == 2); - - { - std::vector expected = {employee}; - BOOST_TEST_INFO("result = " + sstr(pay_method.vp)); - BOOST_TEST_INFO("expected = " + sstr(expected)); - BOOST_TEST(pay_method.vp == expected); - } - - { - std::vector expected = {role, expense}; - BOOST_TEST_INFO("result = " << sstr(approve_method.vp)); - BOOST_TEST_INFO("expected = " << sstr(expected)); - BOOST_TEST(approve_method.vp == expected); - } - - { - auto c_iter = test_policy::catalog.methods.begin(); - auto r_iter = rt.methods.rbegin(); - - for (int i = 0; i < rt.methods.size(); ++i) { - BOOST_TEST_INFO("i = " << i); - auto& c_meth = *c_iter++; - auto& r_meth = *r_iter++; - BOOST_TEST_REQUIRE(r_meth.specs.size() == c_meth.specs.size()); - - auto c_spec_iter = c_meth.specs.begin(); - auto r_spec_iter = r_meth.specs.begin(); - - for (int j = 0; j < r_meth.specs.size(); ++j) { - BOOST_TEST_INFO("i = " << i); - BOOST_TEST_INFO("j = " << j); - auto& c_spec = *c_spec_iter++; - auto& r_spec = *r_spec_iter++; - BOOST_TEST_REQUIRE( - r_spec.vp.size() == c_spec.vp_end - c_spec.vp_begin); - } - } - } - - BOOST_TEST_REQUIRE(role->used_by_vp.size() == 1); - BOOST_TEST(role->used_by_vp[0].method == &approve_method); - BOOST_TEST(role->used_by_vp[0].param == 0); - - BOOST_TEST_REQUIRE(employee->used_by_vp.size() == 1); - BOOST_TEST(employee->used_by_vp[0].method == &pay_method); - BOOST_TEST(employee->used_by_vp[0].param == 0); - - BOOST_TEST_REQUIRE(expense->used_by_vp.size() == 1); - BOOST_TEST(expense->used_by_vp[0].method == &approve_method); - BOOST_TEST(expense->used_by_vp[0].param == 1); - - rt.allocate_slots(); - - { - const std::vector expected = {1}; - BOOST_TEST(expected == pay_method.slots); - } - - { - const std::vector expected = {0, 0}; - BOOST_TEST(expected == approve_method.slots); - } - - BOOST_TEST_REQUIRE(role->vtbl.size() == 1); - BOOST_TEST_REQUIRE(employee->vtbl.size() == 2); - BOOST_TEST_REQUIRE(manager->vtbl.size() == 2); - BOOST_TEST_REQUIRE(founder->vtbl.size() == 1); - - BOOST_TEST_REQUIRE(expense->vtbl.size() == 1); - BOOST_TEST_REQUIRE(public_->vtbl.size() == 1); - BOOST_TEST_REQUIRE(bus->vtbl.size() == 1); - BOOST_TEST_REQUIRE(metro->vtbl.size() == 1); - BOOST_TEST_REQUIRE(taxi->vtbl.size() == 1); - BOOST_TEST_REQUIRE(jet->vtbl.size() == 1); - - auto pay_method_iter = pay_method.specs.begin(); - auto pay_Employee = pay_method_iter++; - auto pay_Manager = pay_method_iter++; - - auto spec_iter = approve_method.specs.begin(); - auto approve_Role_Expense = spec_iter++; - auto approve_Employee_public = spec_iter++; - auto approve_Manager_Taxi = spec_iter++; - auto approve_Founder_Expense = spec_iter++; - - { - BOOST_TEST(runtime::is_more_specific( - &*approve_Founder_Expense, &*approve_Role_Expense)); - BOOST_TEST(runtime::is_more_specific( - &*approve_Manager_Taxi, &*approve_Role_Expense)); - BOOST_TEST(!runtime::is_more_specific( - &*approve_Role_Expense, &*approve_Role_Expense)); - - { - std::vector expected = {&*approve_Manager_Taxi}; - std::vector specs = { - &*approve_Role_Expense, &*approve_Manager_Taxi}; - BOOST_TEST(expected == runtime::best(specs)); - } - } - - { - BOOST_TEST(runtime::is_base( - &*approve_Role_Expense, &*approve_Founder_Expense)); - BOOST_TEST(!runtime::is_base( - &*approve_Role_Expense, &*approve_Role_Expense)); - } - - rt.build_dispatch_tables(); - - { - BOOST_TEST_REQUIRE(pay_method.dispatch_table.size() == 2); - BOOST_TEST(pay_method.dispatch_table[0]->pf == pay_Employee->pf); - BOOST_TEST(pay_method.dispatch_table[1]->pf == pay_Manager->pf); - } - - { - // expected dispatch table here: - // https://www.codeproject.com/Articles/859492/Open-Multi-Methods-for-Cplusplus-Part-Inside-Yomm - BOOST_TEST_REQUIRE(approve_method.dispatch_table.size() == 12); - BOOST_TEST_REQUIRE(approve_method.strides.size() == 1); - BOOST_TEST_REQUIRE(approve_method.strides[0] == 4); - - auto dp_iter = approve_method.dispatch_table.begin(); - BOOST_TEST((*dp_iter++)->pf == approve_Role_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Role_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Role_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Founder_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Role_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Employee_public->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Employee_public->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Founder_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Role_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Role_Expense->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Manager_Taxi->pf); - BOOST_TEST((*dp_iter++)->pf == approve_Founder_Expense->pf); - } - - { - const std::vector expected = {0}; - BOOST_TEST(expected == role->vtbl); - } - - { - const std::vector expected = {1, 0}; - BOOST_TEST(expected == employee->vtbl); - } - - { - const std::vector expected = {2, 1}; - BOOST_TEST(expected == manager->vtbl); - } - - { - const std::vector expected = {3}; - BOOST_TEST(expected == founder->vtbl); - } - - { - const std::vector expected = {0}; - BOOST_TEST(expected == expense->vtbl); - } - - { - const std::vector expected = {1}; - BOOST_TEST(expected == public_->vtbl); - } - - { - const std::vector expected = {1}; - BOOST_TEST(expected == bus->vtbl); - } - - { - const std::vector expected = {1}; - BOOST_TEST(expected == metro->vtbl); - } - - { - const std::vector expected = {2}; - BOOST_TEST(expected == taxi->vtbl); - } - - { - const std::vector expected = {0}; - BOOST_TEST(expected == jet->vtbl); - } - - BOOST_TEST_REQUIRE(pay_Employee->info->next != nullptr); - BOOST_TEST_REQUIRE(pay_Manager->info->next != nullptr); - BOOST_TEST(*pay_Manager->info->next == pay_Employee->info->pf); - - rt.install_gv(); - - { - // pay - BOOST_TEST_REQUIRE( - test_policy::dispatch_data.size() == - +12 // approve: 3 slots and 12 cells for dispatch table - + 12); // 3 vtbl of 2 cells for Roles + 6 vtbl of 1 cells for - // Expenses - BOOST_TEST_REQUIRE( - test_policy::vptrs.size() <= - (1 - << (std::numeric_limits::digits - - test_policy::hash_shift))); -#ifndef NDEBUG - BOOST_TEST_REQUIRE( - test_policy::control.size() == test_policy::vptrs.size()); -#endif - - auto gv_iter = test_policy::dispatch_data.data(); - // no slots nor fun* for 1-method - - // approve - // 12 fun* - auto approve_dispatch_table = gv_iter; - BOOST_TEST(std::equal( - approve_method.dispatch_table.begin(), - approve_method.dispatch_table.end(), gv_iter, - [](auto a, auto b) { return a->pf == b; })); - gv_iter += approve_method.dispatch_table.size(); - - // auto opt_iter = gv_iter; - - // // Role - // BOOST_TEST(gv_iter++->i == 0); // approve/0 - // // Employee - // BOOST_TEST(gv_iter++->i == 1); // approve/0 - // BOOST_TEST(gv_iter++->i == 0); // pay - // // Manager - // BOOST_TEST(gv_iter++->i == 2); // approve/0 - // BOOST_TEST(gv_iter++->i == 1); // pay - // // owner - // BOOST_TEST(gv_iter++->i == 3); // approve/0 - // // Expense - // BOOST_TEST(gv_iter++->i == 0); // approve/1 - // // Public - // BOOST_TEST(gv_iter++->i == 1); // approve/1 - // // Bus - // BOOST_TEST(gv_iter++->i == 1); // approve/1 - // // Metro - // BOOST_TEST(gv_iter++->i == 1); // approve/1 - // // Taxi - // BOOST_TEST(gv_iter++->i == 2); // approve/1 - // // Plane - // BOOST_TEST(gv_iter++->i == 0); // approve/1 - - rt.optimize(); - - // // Role - // BOOST_TEST(opt_iter++->pw == 0 + approve_dispatch_table); // - // approve/0 - // // Employee - // BOOST_TEST(opt_iter++->pw == 1 + approve_dispatch_table); // - // approve/0 BOOST_TEST(opt_iter++->pf == pay_Employee->info->pf); // - // pay - // // Manager - // BOOST_TEST(opt_iter++->pw == 2 + approve_dispatch_table); // - // approve/0 BOOST_TEST(opt_iter++->pf == pay_Manager->info->pf); // pay - // // owner - // BOOST_TEST(opt_iter++->pw == 3 + approve_dispatch_table); // - // approve/0 - // // Expense - // BOOST_TEST(opt_iter++->i == 0); // approve/1 - // // Public - // BOOST_TEST(opt_iter++->i == 1); // approve/1 - // // Bus - // BOOST_TEST(opt_iter++->i == 1); // approve/1 - // // Metro - // BOOST_TEST(opt_iter++->i == 1); // approve/1 - // // Taxi - // BOOST_TEST(opt_iter++->i == 2); // approve/1 - // // Plane - // BOOST_TEST(opt_iter++->i == 0); // approve/1 - - // BOOST_TEST(vptr(test_policy::context, &typeid(Role)) == role->vptr); - // BOOST_TEST( - // vptr(test_policy::context, &typeid(Employee)) == employee->vptr); - // BOOST_TEST( - // vptr(test_policy::context, &typeid(Manager)) == manager->vptr); - // BOOST_TEST( - // vptr(test_policy::context, &typeid(Founder)) == founder->vptr); - - // BOOST_TEST( - // vptr(test_policy::context, &typeid(Expense)) == expense->vptr); - // BOOST_TEST( - // vptr(test_policy::context, &typeid(Public)) == public_->vptr); - // BOOST_TEST(vptr(test_policy::context, &typeid(Bus)) == bus->vptr); - // BOOST_TEST(vptr(test_policy::context, &typeid(Metro)) == - // metro->vptr); BOOST_TEST(vptr(test_policy::context, &typeid(Taxi)) == - // taxi->vptr); BOOST_TEST(vptr(test_policy::context, &typeid(Jet)) == - // jet->vptr); - - { - const Role& role = Role(); - const Employee& employee = Employee(); - const Employee& manager = Manager(); - const Role& founder = Founder(); - - const Expense& expense = Expense(); - const Expense& public_transport = Public(); - const Expense& bus = Bus(); - const Expense& metro = Metro(); - const Expense& taxi = Taxi(); - const Expense& jet = Jet(); - - const auto& pay_method = - decltype(yOMM2_SELECTOR(pay)(Employee()))::fn; - BOOST_TEST(pay_method.arity == 1); - BOOST_TEST(pay_method.resolve(employee) == pay_Employee->info->pf); - BOOST_TEST(&typeid(manager) == &typeid(Manager)); - BOOST_TEST(pay_method.resolve(manager) == pay_Manager->info->pf); - - using approve_method = - decltype(yOMM2_SELECTOR(approve)(Role(), Expense(), 0.)); - BOOST_TEST(approve_method::fn.arity == 2); - - BOOST_TEST( - (approve_method::fn.resolve(role, expense, 0.)) == - approve_Role_Expense->info->pf); - - { - std::vector Roles = { - &role, &employee, &manager, &founder}; - - std::vector Expenses = { - &expense, &public_transport, &bus, &metro, &taxi, &jet}; - - int i = 0; - - for (auto r : Roles) { - int j = 0; - for (auto e : Expenses) { - BOOST_TEST_INFO("i = " << i << " j = " << j); - auto expected = typeid(*r) == typeid(Founder) - ? approve_Founder_Expense - : typeid(*r) == typeid(Manager) - ? (typeid(*e) == typeid(Taxi) ? approve_Manager_Taxi - : dynamic_cast(e) - ? approve_Employee_public - : approve_Role_Expense) - : typeid(*r) == typeid(Employee) && - dynamic_cast(e) - ? approve_Employee_public - : approve_Role_Expense; - BOOST_TEST( - (approve_method::fn.resolve(*r, *e, 0.)) == - expected->info->pf); - ++j; - } - ++i; - } - } - - BOOST_TEST(pay(employee) == 3000); - BOOST_TEST(pay(manager) == 5000); - } - } -} -} // namespace rolex - -namespace multiple_inheritance { - -// A B -// \ / \ -// AB D -// | | -// C E - -struct A { - virtual ~A() { - } -}; - -struct B { - virtual ~B() { - } -}; - -struct AB : A, B {}; - -struct C : AB {}; - -struct D : B {}; - -struct E : D {}; - -using test_policy = test_policy_<__COUNTER__>; - -YOMM2_STATIC(use_classes); - -BOOST_AUTO_TEST_CASE(test_use_classes_mi) { - std::vector actual, expected; - - runtime rt; - rt.augment_classes(); - - auto a = get_class(rt); - auto b = get_class(rt); - auto ab = get_class(rt); - auto c = get_class(rt); - auto d = get_class(rt); - auto e = get_class(rt); - - // ----------------------------------------------------------------------- - // A - BOOST_REQUIRE_EQUAL(sstr(a->direct_bases), empty); - BOOST_REQUIRE_EQUAL(sstr(a->direct_derived), sstr(ab)); - BOOST_REQUIRE_EQUAL(sstr(a->compatible_classes), sstr(a, ab, c)); - - // ----------------------------------------------------------------------- - // B - BOOST_REQUIRE_EQUAL(sstr(b->direct_bases), empty); - BOOST_REQUIRE_EQUAL(sstr(b->direct_derived), sstr(ab, d)); - BOOST_REQUIRE_EQUAL(sstr(b->compatible_classes), sstr(b, ab, c, d, e)); - - // ----------------------------------------------------------------------- - // AB - BOOST_REQUIRE_EQUAL(sstr(ab->direct_bases), sstr(a, b)); - BOOST_REQUIRE_EQUAL(sstr(ab->direct_derived), sstr(c)); - BOOST_REQUIRE_EQUAL(sstr(ab->compatible_classes), sstr(ab, c)); - - // ----------------------------------------------------------------------- - // C - BOOST_REQUIRE_EQUAL(sstr(c->direct_bases), sstr(ab)); - BOOST_REQUIRE_EQUAL(sstr(c->direct_derived), empty); - BOOST_REQUIRE_EQUAL(sstr(c->compatible_classes), sstr(c)); - - // ----------------------------------------------------------------------- - // D - BOOST_REQUIRE_EQUAL(sstr(d->direct_bases), sstr(b)); - BOOST_REQUIRE_EQUAL(sstr(d->direct_derived), sstr(e)); - BOOST_REQUIRE_EQUAL(sstr(d->compatible_classes), sstr(d, e)); - - // ----------------------------------------------------------------------- - // E - BOOST_REQUIRE_EQUAL(sstr(e->direct_bases), sstr(d)); - BOOST_REQUIRE_EQUAL(sstr(e->direct_derived), empty); - BOOST_REQUIRE_EQUAL(sstr(e->compatible_classes), sstr(e)); -} - -struct key; -auto& m_a = method), test_policy>::fn; -auto& m_b = method), test_policy>::fn; -auto& m_ab = method, virtual_), test_policy>::fn; -auto& m_c = method), test_policy>::fn; -auto& m_d = method), test_policy>::fn; - -BOOST_AUTO_TEST_CASE(test_allocate_slots_mi) { - runtime rt; - rt.augment_classes(); - rt.augment_methods(); - rt.allocate_slots(); - - auto m_iter = rt.methods.begin(); - auto m_a = m_iter++; - auto m_b = m_iter++; - auto m_ab = m_iter++; - auto m_c = m_iter++; - auto m_d = m_iter++; - - // lattice: - // A B - // \ / \ - // AB D - // | | - // C E - - // 1-methods use one slot: - BOOST_TEST(m_a->slots.size() == 1); - BOOST_TEST(m_b->slots.size() == 1); - BOOST_TEST(m_c->slots.size() == 1); - BOOST_TEST(m_d->slots.size() == 1); - - // 2-method uses two slots - BOOST_TEST(m_ab->slots.size() == 2); - // two *different* slots - BOOST_TEST(m_ab->slots[0] != m_ab->slots[1]); - - // m_c and m_d use the same slot - BOOST_TEST(str(m_c->slots) == str(m_d->slots)); - - // check that no two methods methods (except m_d) use a same slot - - decltype(m_a) methods[] = {m_a, m_b, m_ab, m_c}; - - for (auto m1 : methods) { - std::vector in_use(5); // in total methods use 5 slots - - for (auto s1 : m1->slots) { - BOOST_REQUIRE(s1 <= 4); - in_use[s1] = true; - } - - for (auto m2 : methods) { - if (m1 == m2) { - continue; - } - - for (auto s2 : m2->slots) { - BOOST_REQUIRE(s2 <= 4); - BOOST_TEST(!in_use[s2]); - } - } - } -} - -} // namespace multiple_inheritance diff --git a/tests/test_core.cpp b/tests/test_core.cpp index e38af1fe..02ce63c3 100644 --- a/tests/test_core.cpp +++ b/tests/test_core.cpp @@ -360,3 +360,33 @@ using meet = method, virtual_)>; static_assert(has_static_offsets::value); } + +namespace test_report { + +struct report {}; + +struct facet1 { + struct report {}; +}; + +struct facet2 { +}; + +struct facet3 { + struct report {}; +}; + +static_assert( + std::is_base_of_v< + report, typename aggregate_reports< + types, types>::type>); +static_assert( + std::is_base_of_v< + facet1::report, typename aggregate_reports< + types, types>::type>); +static_assert( + std::is_base_of_v< + facet3::report, typename aggregate_reports< + types, types>::type>); + +} diff --git a/tests/test_generator.cpp b/tests/test_generator.cpp new file mode 100644 index 00000000..ffc3f44b --- /dev/null +++ b/tests/test_generator.cpp @@ -0,0 +1,230 @@ +// Copyright (c) 2018-2024 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include +#include + +#include +#include + +#include "test_helpers.hpp" + +#define BOOST_TEST_MAIN +#define BOOST_TEST_MODULE generator +#include +#include + +using namespace yorel::yomm2; + +struct foo {}; + +template +struct baz {}; + +namespace ns1 { + +struct foo {}; + +namespace ns11 { +struct foo {}; +struct bar {}; +} // namespace ns11 + +struct bar {}; +} // namespace ns1 + +namespace ns2 { +struct foo {}; + +namespace ns21 { +struct foo {}; + +} // namespace ns21 +} // namespace ns2 + +namespace ns1_longer { +struct foo {}; +} // namespace ns1_longer + +struct generic_forward_declaration_data { + std::string expected; + const std::type_info** const first_type; + const std::type_info** const last_type; +}; + +std::ostream& +operator<<(std::ostream& os, const generic_forward_declaration_data& data) { + const char* sep = ""; + + for (auto& type : detail::range{data.first_type, data.last_type}) { + os << sep << boost::core::demangle(type->name()); + sep = ", "; + } + return os; +} + +template +generic_forward_declaration_data fd(std::string expected) { + static const std::type_info* types[] = {&typeid(Ts)...}; + return generic_forward_declaration_data( + {expected, types, types + sizeof...(Ts)}); +} + +namespace bdata = boost::unit_test::data; + +auto fd_dataset = bdata::make( + fd("\n"), fd("\nclass foo;\n"), + fd( + R"( +namespace ns1 { +class foo; +} +)"), + fd( + R"( +namespace ns1 { +class foo; +} +namespace ns2 { +class foo; +} +)"), + fd( + R"( +namespace ns1 { +class foo; +} +namespace ns1_longer { +class foo; +} +)"), + fd&>, std::ostream)>>( + R"( +class foo; +)"), + fd( + R"( +namespace ns1 { +namespace ns11 { +class bar; +class foo; +} +} +)"), + fd( + R"( +namespace ns1 { +namespace ns11 { +class foo; +} +} +namespace ns2 { +namespace ns21 { +class foo; +} +} +)")); + +BOOST_DATA_TEST_CASE(test_generator_write_forward_declarations, fd_dataset) { + std::ostringstream os; + generator gen; + + for (auto& type : detail::range{sample.first_type, sample.last_type}) { + gen.add_forward_declaration(*type); + } + + os << "\n"; + gen.write_forward_declarations(os); + BOOST_TEST(os.str() == sample.expected); +} + +BOOST_AUTO_TEST_CASE(test_generator_write_forward_declarations_) { + using namespace detail; + + { + std::ostringstream os; + generator gen; + gen.write_forward_declarations(os); + BOOST_TEST(os.str().empty()); + } + + { + std::ostringstream os; + os << "\n"; + generator gen; + gen.add_forward_declaration(); + gen.add_forward_declaration(); + gen.add_forward_declaration(); + + gen.write_forward_declarations(os); + std::string_view expected = R"( +namespace ns1 { +class foo; +namespace ns11 { +class bar; +class foo; +} +} +)"; + BOOST_TEST(os.str() == expected); + } +} + +struct baz_key; + +void baz1_def(foo&, int) { +} +void baz2_def(foo&, foo&) { +} + +BOOST_AUTO_TEST_CASE(test_generate_offsets) { + { + using policy = test_policy_<1>; + YOMM2_STATIC(use_classes); + + using baz1 = method, int), policy>; + YOMM2_STATIC(baz1::add_function); + + using baz2 = + method, virtual_), policy>; + YOMM2_STATIC(baz2::add_function); + + update(); + { + std::ostringstream os; + os << "\n"; + generator gen; + gen.write_static_slots(os); + std::string_view expected = R"( +template<> struct yorel::yomm2::detail::static_offsets, int), test_policy_<1>>> {static constexpr size_t slots[] = {2}; }; +)"; + auto actual = + std::regex_replace(os.str(), std::basic_regex("> +>"), ">>"); + // On some compilers, 'demangle' adds spaces between closing angle + // brackets. + BOOST_TEST(actual == expected); + } + + { + std::ostringstream os; + os << "\n"; + generator gen; + gen.write_forward_declarations(os); + gen.write_static_slots(os); + std::string_view expected = R"( +template<> struct yorel::yomm2::detail::static_offsets, yorel::yomm2::virtual_), test_policy_<1>>> {static constexpr size_t slots[] = {0, 1}; static constexpr size_t strides[] = {1}; }; +template<> struct yorel::yomm2::detail::static_offsets, int), test_policy_<1>>> {static constexpr size_t slots[] = {2}; }; +)"; + auto actual = + std::regex_replace(os.str(), std::basic_regex("> +>"), ">>"); + // On some compilers, 'demangle' adds spaces between closing angle + // brackets. + BOOST_TEST(actual == expected); + } + } +} diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 00000000..840681d7 --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1,14 @@ +{ + "default-registry": { + "kind": "git", + "baseline": "afa12e7292fb47771e619675d7915645fe0adb1b", + "repository": "https://github.com/microsoft/vcpkg" + }, + "registries": [ + { + "kind": "artifact", + "location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip", + "name": "microsoft" + } + ] +} diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 00000000..d6b2c306 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,13 @@ +{ + "name": "yomm2", + "version": "1.5.3", + "dependencies": [ + "benchmark", + "boost-core", + "boost-dynamic-bitset", + "boost-mp11", + "boost-preprocessor", + "boost-test", + "vcpkg-tool-ninja" + ] +} From edbaa3147183caab3acd2974020ea2fb8143e41e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Thu, 25 Jul 2024 21:02:54 -0400 Subject: [PATCH 2/6] cmake --- CMakeLists.txt | 99 ++++++++++++--------- CMakePresets.json | 94 +++++++++++++++++++ cmake/BoostConfigVersion.cmake.in | 10 --- cmake/Boost_download.cmake.in | 89 ------------------ cmake/YOMM2Config.cmake.in | 11 --- cmake/benchmark_download.cmake.in | 19 ---- cmake/boost_headers-config-version.cmake.in | 12 --- cmake/boost_headers-config.cmake.in | 36 -------- cmake/download_package.cmake | 68 -------------- examples/CMakeLists.txt | 13 ++- examples/static-slots/CMakeLists.txt | 32 +++++++ src/CMakeLists.txt | 6 -- tests/CMakeLists.txt | 15 ++-- 13 files changed, 203 insertions(+), 301 deletions(-) create mode 100644 CMakePresets.json delete mode 100644 cmake/BoostConfigVersion.cmake.in delete mode 100644 cmake/Boost_download.cmake.in delete mode 100644 cmake/YOMM2Config.cmake.in delete mode 100644 cmake/benchmark_download.cmake.in delete mode 100644 cmake/boost_headers-config-version.cmake.in delete mode 100644 cmake/boost_headers-config.cmake.in delete mode 100644 cmake/download_package.cmake create mode 100644 examples/static-slots/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index c0bfde3d..f1ca73d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ cmake_minimum_required(VERSION 3.20) cmake_policy(SET CMP0057 NEW) + project(YOMM2 LANGUAGES CXX VERSION 1.5.2) if(NOT CMAKE_CXX_STANDARD) @@ -12,23 +13,49 @@ if(NOT CMAKE_CXX_STANDARD) endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) -option(YOMM2_DOWNLOAD_DEPENDENCIES "Set to ON to build missing dependencies from source" OFF) -include(cmake/download_package.cmake) +option(YOMM2_SHARED "Build yomm2 as a shared library" OFF) +option(YOMM2_ENABLE_EXAMPLES "Set to ON to build examples" OFF) +option(YOMM2_DEBUG_MACROS "Set to ON to debug macros" OFF) +option(YOMM2_ENABLE_DOC "Set to ON to generate tutorials and reference" OFF) +option(YOMM2_ENABLE_TESTS "Set to ON to build tests" OFF) +option(YOMM2_ENABLE_BENCHMARKS "Set to ON to enable benchmarks" OFF) +option( + YOMM2_CHECK_ABI_COMPATIBILITY + "Build shared library and examples in different modes" OFF) -# Find Boost dependency -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") -find_package(Boost 1.74 QUIET) -if(NOT TARGET Boost::headers) - if (YOMM2_DOWNLOAD_DEPENDENCIES) - download_package(Boost INSTALL_WITH_YOMM) +include(CMakeDependentOption) +CMAKE_DEPENDENT_OPTION(YOMM2_ENABLE_BENCHMARKS + "Set to ON to build benchmarks" OFF + "YOMM2_ENABLE_TESTS" OFF +) + +macro(assign_bool VAR) + if(${ARGN}) + set(${VAR} ON) else() - message(FATAL_ERROR "Boost was not found on your system. Set YOMM2_DOWNLOAD_DEPENDENCIES to ON to download and build from the sources.") + set(${VAR} OFF) endif() +endmacro() + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(COMPILER_IS_CLANG ON) endif() -message(STATUS "Using Boost libraries from ${Boost_INCLUDE_DIRS}") -if(NOT ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") AND (CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")) - add_compile_options(-save-temps -fverbose-asm -masm=intel) +if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set(VARIANT_IS_DEBUG ON) +endif() + +if (VARIANT_IS_DEBUG) + if(COMPILER_IS_CLANG) + add_compile_options(-fno-limit-debug-info) + endif() + if(CMAKE_COMPILER_IS_GNUCXX OR COMPILER_IS_CLANG) + add_compile_definitions(_GLIBCXX_DEBUG) + endif() +else() + if(CMAKE_COMPILER_IS_GNUCXX OR COMPILER_IS_CLANG) + add_compile_options(-save-temps -masm=intel) + endif() endif() if(MSVC) @@ -36,31 +63,32 @@ if(MSVC) add_compile_options(/EHsc /FAs) endif() -option(YOMM2_DEBUG_MACROS "Set to ON to debug macros" OFF) if(${YOMM2_DEBUG_MACROS}) message(STATUS "Macro debugging enabled") set(CMAKE_CXX_COMPILER_LAUNCHER ${CMAKE_SOURCE_DIR}/dev/ppfc) endif() -add_subdirectory(src) +# set(YOMM2_REQUIRED_BOOST_LIBRARIES core mp11 preprocessor dynamic_bitset) -option(YOMM2_ENABLE_TESTS "Set to ON to build tests" ON) -include(CMakeDependentOption) -CMAKE_DEPENDENT_OPTION(YOMM2_ENABLE_BENCHMARKS - "Set to ON to build benchmarks" OFF - "YOMM2_ENABLE_TESTS" OFF -) if(${YOMM2_ENABLE_TESTS}) message(STATUS "Tests enabled") + # set(YOMM2_REQUIRED_BOOST_LIBRARIES ${YOMM2_REQUIRED_BOOST_LIBRARIES} unit_test_framework) +endif() + +# Find Boost dependencies +# find_package(Boost REQUIRED COMPONENTS ${YOMM2_REQUIRED_BOOST_LIBRARIES}) +find_package(Boost REQUIRED) + +add_subdirectory(src) + +if(${YOMM2_ENABLE_EXAMPLES}) + message(STATUS "Examples enabled") + add_subdirectory(examples) +endif() + +if(${YOMM2_ENABLE_TESTS}) if(${YOMM2_ENABLE_BENCHMARKS}) - find_package(benchmark QUIET) - if(NOT ${benchmark_FOUND}) - if (YOMM2_DOWNLOAD_DEPENDENCIES) - download_package(benchmark) - else() - message(FATAL_ERROR "benchmarks was not found on your system. Set YOMM2_DOWNLOAD_DEPENDENCIES to ON to download and build from the sources.") - endif() - endif() + find_package(benchmark REQUIRED) message(STATUS "Benchmarks enabled") endif() include(CTest) @@ -69,15 +97,6 @@ if(${YOMM2_ENABLE_TESTS}) add_subdirectory(ce) endif() -option(YOMM2_ENABLE_EXAMPLES "Set to ON to build examples" ON) -if(${YOMM2_ENABLE_EXAMPLES}) - message(STATUS "Examples enabled") - add_subdirectory(examples) -endif() - -option(YOMM2_ENABLE_DOC "Set to ON to generate tutorials and reference" OFF) -option(YOMM2_ENABLE_BENCHMARKS "Set to ON to enable benchmarks" OFF) - set(readme_md "${CMAKE_SOURCE_DIR}/README.md") set(readme_cpp "${CMAKE_SOURCE_DIR}/examples/README.cpp") @@ -93,6 +112,7 @@ endif() if(YOMM2_ENABLE_TESTS OR YOMM2_ENABLE_DOC) add_subdirectory(docs.in) + add_custom_target(doc DEPENDS README_md README reference build_and_make_tutorials) endif() ## Install instruction @@ -109,11 +129,6 @@ install(EXPORT YOMM2Targets NAMESPACE YOMM2:: DESTINATION lib/cmake/YOMM2 ) -# Configure package config (tells using code about dependencies) -configure_package_config_file( - cmake/YOMM2Config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/YOMM2Config.cmake - INSTALL_DESTINATION lib/cmake/YOMM2 -) # Copy config files to install directory install(FILES "${CMAKE_CURRENT_BINARY_DIR}/YOMM2Config.cmake" diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 00000000..f3b3bdc1 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,94 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "binaryDir": "${sourceDir}/build/${presetName}", + "generator": "Ninja", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}", + "YOMM2_ENABLE_EXAMPLES": "ON", + "YOMM2_ENABLE_TESTS": "ON", + "YOMM2_ENABLE_BENCHMARKS": "ON" + } + }, + { + "name": "shared", + "hidden": true, + "cacheVariables": { + "YOMM2_SHARED": "ON" + } + }, + { + "name": "debug", + "inherits": "base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "release", + "inherits": "base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "ci-debug", + "inherits": "base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "ci-release", + "inherits": "base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "clang16-debug", + "inherits": "debug", + "cacheVariables": { + "CMAKE_CXX_COMPILER": "clang++-16" + } + }, + { + "name": "clang16-release", + "inherits": "release", + "cacheVariables": { + "CMAKE_CXX_COMPILER": "clang++-16" + } + }, + { + "name": "clang16-debug-doc", + "inherits": "clang16-debug", + "cacheVariables": { + "YOMM2_ENABLE_DOC": "ON" + } + }, + { + "name": "clang16-debug-shared", + "inherits": [ + "clang16-debug", + "shared" + ] + } + ], + "buildPresets": [ + { + "name": "doc-debug", + "configurePreset": "clang16-debug-doc", + "targets": [ + "doc", + "README_md", + "README", + "reference", + "build_and_make_tutorials" + ] + } + ] +} diff --git a/cmake/BoostConfigVersion.cmake.in b/cmake/BoostConfigVersion.cmake.in deleted file mode 100644 index 2e28e1e1..00000000 --- a/cmake/BoostConfigVersion.cmake.in +++ /dev/null @@ -1,10 +0,0 @@ -set(PACKAGE_VERSION @BOOST_VERSION@) - -if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) - set(PACKAGE_VERSION_COMPATIBLE FALSE) -else() - set(PACKAGE_VERSION_COMPATIBLE TRUE) - if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) - set(PACKAGE_VERSION_EXACT TRUE) - endif() -endif() diff --git a/cmake/Boost_download.cmake.in b/cmake/Boost_download.cmake.in deleted file mode 100644 index ffd8ae45..00000000 --- a/cmake/Boost_download.cmake.in +++ /dev/null @@ -1,89 +0,0 @@ -cmake_minimum_required(VERSION 3.5) -include(ExternalProject) -project(Boost) -set(BOOST_VERSION 1.74.0) - -list(APPEND BOOST_SUBMODULES - "libs/assert" - "libs/config" - "libs/container_hash" - "libs/core" - "libs/detail" - "libs/dynamic_bitset" - "libs/functional" - "libs/integer" - "libs/move" - "libs/mp11" - "libs/preprocessor" - "libs/static_assert" - "libs/type_traits" - "libs/throw_exception" -) - list(APPEND BOOST_SUBMODULES - "libs/algorithm" - "libs/bind" - "libs/exception" - "libs/function" - "libs/io" - "libs/iterator" - "libs/mpl" - "libs/numeric/conversion" - "libs/range" - "libs/smart_ptr" - "libs/test" - "libs/type_index" - "libs/utility" - ) - -ExternalProject_Add(${PROJECT_NAME} - GIT_REPOSITORY https://github.com/boostorg/boost.git - GIT_TAG boost-${BOOST_VERSION} - GIT_SUBMODULES tools/cmake;tools/boost_install;${BOOST_SUBMODULES} - @ADDITIONAL_GIT_SETTINGS@ - SOURCE_DIR "@CMAKE_BINARY_DIR@/dependencies/sources/${PROJECT_NAME}" - BINARY_DIR "@CMAKE_CURRENT_BINARY_DIR@/${PROJECT_NAME}_build" - UPDATE_COMMAND "" - INSTALL_DIR @DEPENDENCY_INSTALL_PREFIX@ - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= - -DCMAKE_BUILD_TYPE=Release - -DBOOST_ENABLE_CMAKE=ON - -DBUILD_TESTING=OFF -) - -ExternalProject_Add_Step(${PROJECT_NAME} install_config - DEPENDEES install - COMMAND ${CMAKE_COMMAND} -E make_directory - /lib/cmake/Boost-${BOOST_VERSION} - COMMAND ${CMAKE_COMMAND} -E copy - /tools/boost_install/BoostConfig.cmake - /lib/cmake/Boost-${BOOST_VERSION} -) - -# Boost install broken for some CMake versions - do manually! -foreach(boost_module ${BOOST_SUBMODULES}) - STRING(REGEX REPLACE "/" "_" clean_boost_module ${boost_module}) - ExternalProject_Add_Step(${PROJECT_NAME} "${clean_boost_module}" - DEPENDEES install - COMMAND ${CMAKE_COMMAND} -E copy_directory - /${boost_module}/include - /include - ) -endforeach(boost_module) - -configure_file( - @DL_SCRIPT_DIR@/BoostConfigVersion.cmake.in - @DEPENDENCY_INSTALL_PREFIX@/lib/cmake/Boost-${BOOST_VERSION}/BoostConfigVersion.cmake - @ONLY -) - -configure_file( - @DL_SCRIPT_DIR@/boost_headers-config-version.cmake.in - @DEPENDENCY_INSTALL_PREFIX@/lib/cmake/boost_headers-${BOOST_VERSION}/boost_headers-config-version.cmake - @ONLY -) - -configure_file( - @DL_SCRIPT_DIR@/boost_headers-config.cmake.in - @DEPENDENCY_INSTALL_PREFIX@/lib/cmake/boost_headers-${BOOST_VERSION}/boost_headers-config.cmake - @ONLY -) diff --git a/cmake/YOMM2Config.cmake.in b/cmake/YOMM2Config.cmake.in deleted file mode 100644 index 8d6d702b..00000000 --- a/cmake/YOMM2Config.cmake.in +++ /dev/null @@ -1,11 +0,0 @@ -@PACKAGE_INIT@ - -include(CMakeFindDependencyMacro) - -# Tell library users about the Boost dependency -find_dependency(Boost 1.74 REQUIRED) - -# Add the targets file -include("${CMAKE_CURRENT_LIST_DIR}/YOMM2Targets.cmake") - -check_required_components(YOMM2) diff --git a/cmake/benchmark_download.cmake.in b/cmake/benchmark_download.cmake.in deleted file mode 100644 index 842e4a92..00000000 --- a/cmake/benchmark_download.cmake.in +++ /dev/null @@ -1,19 +0,0 @@ -cmake_minimum_required(VERSION 3.5) -project(benchmark) -include(ExternalProject) - -ExternalProject_Add(${PROJECT_NAME} - GIT_REPOSITORY https://github.com/google/benchmark.git - GIT_TAG v1.7.0 - GIT_PROGRESS True - SOURCE_DIR "@CMAKE_BINARY_DIR@/dependencies/${PROJECT_NAME}" - BINARY_DIR "@CMAKE_CURRENT_BINARY_DIR@/${PROJECT_NAME}_build" - INSTALL_DIR @DEPENDENCY_INSTALL_PREFIX@ - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= - -DCMAKE_BUILD_TYPE=Release - -DBENCHMARK_ENABLE_TESTING=OFF - -DBENCHMARK_ENABLE_GTEST_TESTS=OFF - -DCMAKE_CXX_FLAGS=-fPIE -) - - diff --git a/cmake/boost_headers-config-version.cmake.in b/cmake/boost_headers-config-version.cmake.in deleted file mode 100644 index 3872238e..00000000 --- a/cmake/boost_headers-config-version.cmake.in +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Boost @BOOST_VERSION@ - -set(PACKAGE_VERSION @BOOST_VERSION@) - -if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) - set(PACKAGE_VERSION_COMPATIBLE FALSE) -else() - set(PACKAGE_VERSION_COMPATIBLE TRUE) - if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) - set(PACKAGE_VERSION_EXACT TRUE) - endif() -endif() diff --git a/cmake/boost_headers-config.cmake.in b/cmake/boost_headers-config.cmake.in deleted file mode 100644 index e6cb24ea..00000000 --- a/cmake/boost_headers-config.cmake.in +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Boost @BOOST_VERSION@ - -if(TARGET Boost::headers) - return() -endif() - -if(Boost_VERBOSE OR Boost_DEBUG) - message(STATUS "Found boost_headers ${boost_headers_VERSION} at ${boost_headers_DIR}") -endif() - -# Compute the include and library directories relative to this file. - -get_filename_component(_BOOST_CMAKEDIR "${CMAKE_CURRENT_LIST_DIR}/../" REALPATH) - -# If the computed and the original directories are symlink-equivalent, use original -if(EXISTS "/build/boost/src/stagedir/lib/cmake") - get_filename_component(_BOOST_CMAKEDIR_ORIGINAL "/build/boost/src/stagedir/lib/cmake" REALPATH) - if(_BOOST_CMAKEDIR STREQUAL _BOOST_CMAKEDIR_ORIGINAL) - set(_BOOST_CMAKEDIR "/build/boost/src/stagedir/lib/cmake") - endif() - unset(_BOOST_CMAKEDIR_ORIGINAL) -endif() - -get_filename_component(_BOOST_INCLUDEDIR "${_BOOST_CMAKEDIR}/../../include/" ABSOLUTE) - -add_library(Boost::headers INTERFACE IMPORTED) - -set_target_properties(Boost::headers PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${_BOOST_INCLUDEDIR}" - INTERFACE_COMPILE_DEFINITIONS "BOOST_ALL_NO_LIB" -) - -unset(_BOOST_INCLUDEDIR) -unset(_BOOST_CMAKEDIR) - -mark_as_advanced(boost_headers_DIR) diff --git a/cmake/download_package.cmake b/cmake/download_package.cmake deleted file mode 100644 index 2875b641..00000000 --- a/cmake/download_package.cmake +++ /dev/null @@ -1,68 +0,0 @@ -macro(download_package PACKAGE) - set(options INSTALL_WITH_YOMM) - set(oneValueArgs DL_SCRIPT_DIR) - cmake_parse_arguments(ARGS - "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} - ) - set(DEPENDENCY_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/dependencies/${PACKAGE}) - message(STATUS "Package \"${PACKAGE}\" not found in system.") - message(STATUS - "Downloading dependency \"${PACKAGE}\" and building from source." - ) - # Use below settings for git downloads if available - if(${CMAKE_VERSION} VERSION_GREATER 3.6) - list(APPEND ADDITIONAL_GIT_SETTINGS "GIT_SHALLOW True") - endif() - if(${CMAKE_VERSION} VERSION_GREATER 3.8) - list(APPEND ADDITIONAL_GIT_SETTINGS - "GIT_PROGRESS True GIT_CONFIG advice.detachedHead=false" - ) - endif() - # Prepare download instructions for dependency - message(STATUS ${ARGS_DL_SCRIPT_DIR}) - if("${ARGS_DL_SCRIPT_DIR}" STREQUAL "") - set(DL_SCRIPT_DIR ${CMAKE_SOURCE_DIR}/cmake) - else() - set(DL_SCRIPT_DIR ${ARGS_DL_SCRIPT_DIR}) - endif() - configure_file( - ${DL_SCRIPT_DIR}/${PACKAGE}_download.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE}-download/CMakeLists.txt - @ONLY - ) - - # Download dependency - execute_process( - COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . - RESULT_VARIABLE result - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE}-download - OUTPUT_QUIET - ) - if(result) - message(FATAL_ERROR "Download of dependency ${PACKAGE} failed: ${result}") - endif() - - # Build dependency - execute_process( - COMMAND ${CMAKE_COMMAND} --build . - RESULT_VARIABLE result - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE}-download - ) - if(result) - message(FATAL_ERROR "Build of dependency ${PACKAGE} failed: ${result}") - endif() - - # Restrict search path and use regular find_package to add dependency - find_package(${PACKAGE} - REQUIRED NO_DEFAULT_PATH PATHS "${DEPENDENCY_INSTALL_PREFIX}" - ) - - # Install the built package alongside YOMM if so desired, by copying the - # install made in the build tree - if(${ARGS_INSTALL_WITH_YOMM}) - install( - DIRECTORY ${DEPENDENCY_INSTALL_PREFIX}/ - DESTINATION ${CMAKE_INSTALL_PREFIX} - ) - endif() -endmacro() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 398ab73a..6318dab7 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -38,7 +38,8 @@ add_test(NAME asteroids COMMAND asteroids) add_subdirectory(containers) add_test(NAME containers COMMAND containers) -if(NOT MSVC AND NOT APPLE) +if (NOT (WIN32 OR APPLE)) + message(STATUS "Building dlopen example.") add_executable(dl_main dl_main.cpp) add_library(dl_shared SHARED dl_shared.cpp) get_target_property(YOMM2_INCLUDE_DIRS YOMM2::yomm2 INTERFACE_INCLUDE_DIRECTORIES) @@ -48,4 +49,12 @@ if(NOT MSVC AND NOT APPLE) target_link_libraries(dl_main YOMM2::yomm2 dl) target_link_libraries(dl_shared YOMM2::yomm2) add_test(NAME dlopen COMMAND dl_main) - endif() +endif() + +if (NOT (MSVC AND YOMM2_SHARED)) + # Running this example with a Windows DLL is too much of a hassle, because we + # would need to add the path to the directory containing yomm2.dll to PATH. + # Anyway, if it works with static linking, it is very unlikely that it fails + # with the runtime in a DLL. + add_subdirectory(static-slots) +endif() diff --git a/examples/static-slots/CMakeLists.txt b/examples/static-slots/CMakeLists.txt new file mode 100644 index 00000000..aae6a606 --- /dev/null +++ b/examples/static-slots/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (c) 2018-2024 Jean-Louis Leroy +# Distributed under the Boost Software License, Version 1.0. +# See accompanying file LICENSE_1_0.txt +# or copy at http://www.boost.org/LICENSE_1_0.txt) + +add_executable(generate-static-slots generate-static-slots.cpp animals.cpp) +target_include_directories(generate-static-slots PRIVATE .) +target_link_libraries(generate-static-slots YOMM2::yomm2) + +set(GENERATED_FILES "${CMAKE_CURRENT_BINARY_DIR}/slots.hpp;${CMAKE_CURRENT_BINARY_DIR}/tables.hpp") + +add_custom_command( + OUTPUT ${GENERATED_FILES} + COMMAND generate-static-slots ${GENERATED_FILES} + DEPENDS generate-static-slots animals.cpp +) +add_custom_target( + animals_yomm2_slots_hpp + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/slots.hpp ${CMAKE_CURRENT_BINARY_DIR}/tables.hpp) + +add_library(animals_yomm2_slots INTERFACE) +add_dependencies(animals_yomm2_slots animals_yomm2_slots_hpp) +target_include_directories(animals_yomm2_slots INTERFACE ${CMAKE_CURRENT_BINARY_DIR}) + +add_executable(static-slots-app app.cpp animals.cpp) +target_include_directories(static-slots-app PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_link_libraries( + static-slots-app PRIVATE animals_yomm2_slots YOMM2::yomm2) +add_test(NAME static-slots-app COMMAND static-slots-app) + +# For convenience: all targets related to static slots generation. +add_custom_target(static-slots DEPENDS static-slots-app test_generator) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index df692e17..0f526c30 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,11 +3,6 @@ # See accompanying file LICENSE_1_0.txt # or copy at http://www.boost.org/LICENSE_1_0.txt) -option(YOMM2_SHARED "Build yomm2 as a shared library" OFF) -option( - YOMM2_CHECK_ABI_COMPATIBILITY - "Build shared library and examples in different modes" OFF) - if(YOMM2_SHARED) message(STATUS "Building a shared library") add_library(yomm2 SHARED) @@ -28,7 +23,6 @@ if(YOMM2_SHARED) message(STATUS "type: ${CMAKE_BUILD_TYPE} FLAGS: ${CMAKE_CXX_FLAGS_RELEASE}") if(CMAKE_BUILD_TYPE STREQUAL "Debug") set_target_properties(yomm2 PROPERTIES COMPILE_FLAGS "${CMAKE_CXX_FLAGS_RELEASE}" ) - #set_property(TARGET yomm2 PROPERTY COMPILE_FLAGS "${CMAKE_CXX_FLAGS_RELEASE}") endif() endif() else() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9911fec8..38abfff4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -45,15 +45,14 @@ add_executable(pointer_to_method pointer_to_method.cpp) target_link_libraries(pointer_to_method YOMM2::yomm2 ${CMAKE_THREAD_LIBS_INIT}) add_test(NAME pointer_to_method COMMAND pointer_to_method) -if(${YOMM2_ENABLE_BENCHMARKS}) +if(YOMM2_ENABLE_BENCHMARKS AND NOT (WIN32 OR APPLE)) add_executable(benchmarks benchmarks.cpp) - target_link_libraries(benchmarks YOMM2::yomm2 benchmark::benchmark) + target_link_libraries( + benchmarks YOMM2::yomm2 benchmark::benchmark ${CMAKE_THREAD_LIBS_INIT}) + add_executable(rdtsc-benchmark rdtsc-benchmark.cpp) + target_link_libraries(rdtsc-benchmark YOMM2::yomm2 ${CMAKE_THREAD_LIBS_INIT}) endif() -# add_executable(intrusive intrusive.cpp) -# target_link_libraries(intrusive YOMM2::yomm2 ${CMAKE_THREAD_LIBS_INIT}) -# add_test(NAME intrusive COMMAND intrusive) - add_executable(test_virtual_ptr_basic test_virtual_ptr_basic.cpp) target_link_libraries(test_virtual_ptr_basic YOMM2::yomm2 ${CMAKE_THREAD_LIBS_INIT}) add_test(NAME test_virtual_ptr_basic COMMAND test_virtual_ptr_basic) @@ -76,3 +75,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU set_target_properties(test_custom_rtti PROPERTIES COMPILE_FLAGS "-fno-rtti") endif() add_test(NAME test_custom_rtti COMMAND test_custom_rtti) + +add_executable(test_generator test_generator.cpp) +target_link_libraries(test_generator YOMM2::yomm2 ${CMAKE_THREAD_LIBS_INIT}) +add_test(NAME test_generator COMMAND test_generator) From f251d01a93e154db8fa14682a76e4a458a9785aa Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 27 Jul 2024 11:35:57 -0400 Subject: [PATCH 3/6] wrap up generator --- CMakePresets.json | 1 + docs.in/README.md | 1 + docs.in/reference/generator.md | 47 +++++-- docs/README.md | 1 + docs/reference/generator.md | 117 +++++++++++++++++ examples/CMakeLists.txt | 2 +- .../CMakeLists.txt | 22 ++-- .../{static-slots => generator}/README.md | 0 .../{static-slots => generator}/animals.cpp | 0 examples/generator/animals.hpp | 50 +++++++ .../generate.cpp} | 9 +- examples/generator/generator_example.cpp | 74 +++++++++++ examples/static-slots/animals.hpp | 33 ----- examples/static-slots/app.cpp | 123 ------------------ include/yorel/yomm2/core.hpp | 34 ++--- include/yorel/yomm2/generator.hpp | 45 +++---- include/yorel/yomm2/macros.hpp | 6 +- include/yorel/yomm2/policy.hpp | 12 ++ tests/rdtsc-benchmark.cpp | 2 +- tests/test_generator.cpp | 4 +- 20 files changed, 348 insertions(+), 235 deletions(-) create mode 100644 docs/reference/generator.md rename examples/{static-slots => generator}/CMakeLists.txt (50%) rename examples/{static-slots => generator}/README.md (100%) rename examples/{static-slots => generator}/animals.cpp (100%) create mode 100644 examples/generator/animals.hpp rename examples/{static-slots/generate-static-slots.cpp => generator/generate.cpp} (63%) create mode 100644 examples/generator/generator_example.cpp delete mode 100644 examples/static-slots/animals.hpp delete mode 100644 examples/static-slots/app.cpp diff --git a/CMakePresets.json b/CMakePresets.json index f3b3bdc1..b08e3176 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -83,6 +83,7 @@ "name": "doc-debug", "configurePreset": "clang16-debug-doc", "targets": [ + "all", "doc", "README_md", "README", diff --git a/docs.in/README.md b/docs.in/README.md index d42edba2..2696a467 100644 --- a/docs.in/README.md +++ b/docs.in/README.md @@ -169,6 +169,7 @@ for the shared runtime to be used. | ->error_handler_type | type | handler function | | ->error_type | variant | object passed to error handler | | ->friend_method | macro | make a method in a container, or the entire container, a friend | +| ->generator | class | generate compile-time offsets, pre-calculate dispatch data | | ->hash_search_error | class | failure to find a hash function for registered classes | | ->make_virtual_shared | function template | create an object and return a `virtual_shared_ptr` | | ->method | class template | implement a method | diff --git a/docs.in/reference/generator.md b/docs.in/reference/generator.md index d9b8900e..b84d43be 100644 --- a/docs.in/reference/generator.md +++ b/docs.in/reference/generator.md @@ -5,8 +5,9 @@ headers: yorel/yomm2/generator.hpp class generator; ``` -This mechanism generates header files that can be included in a project to speed -up method dispatch. +This mechanism generates C++ source files that can be included in a project to +speed up method dispatch, and to make dispatch data construction significantly +less resource intensive. Like virtual functions, methods occupy slots in v-tables associated to classes. Unlike virtual functions, the slots cannot be determined by looking at a single @@ -15,7 +16,7 @@ are known. By default, method dispatch reads the slots from variables set by ->`update`. The additional reads put open methods at a disadvantage, compared to ordinary virtual functions. -`write_static_slots` generates C++ code that enables method dispatch to use +`write_static_offsets` generates C++ code that enables method dispatch to use "static" slots - i.e. slots known at compile time. Static slots should be made visible (typically by means including the generated code) before these methods are called. A program may contain a mixture of methods that use static slots, @@ -28,7 +29,7 @@ the number of virtual parameters in a method. A 1-method call via a same as a virtual function call, but one fewer memory read. See the example for assembly listings. -The code generated by `write_static_slots` requires that types used by the +The code generated by `write_static_offsets` requires that types used by the method (parameter types, return type and method key) to be known. This is easy when using static slots for specific methods - by including the generated static offsets for the method just before the method declaration; it is more @@ -36,19 +37,24 @@ challenging when using static offsets for an entire program. `write_forward_declarations` attempts to generate suitable forward declarations, but it has limitations. See its documentation. +`encode_dispatch_data` initializes the dispatch tables for a policy, using a +compact representation of the data produced by ->`update`. It merely copies +integers and it does not allocate memory from the heap. + ## Member functions | Name | Description | | --------------------------------------------------------- | --------------------------------------------------- | -| [write_static_slots](#write_static_slots) | write static slots for a method or a policy | +| [write_static_offsets](#write_static_offsets) | write static slots for a method or a policy | | [add_forward_declaration](#add_forward_declaration) | register types for forward declaration generation | | [write_forward_declarations](#write_forward_declarations) | write forward declarations for the registered types | +| [encode_dispatch_data](#write_forward_declarations) | write data and code to initialize dispatch tables | -## write_static_slots +## write_static_offsets ```c++ -template void write_static_slots(std::ostream& os) const; (1) -template void write_static_slots(std::ostream& os) const; (2) +template void write_static_offsets(std::ostream& os) const; (1) +template void write_static_offsets(std::ostream& os) const; (2) ``` Add the method to the policy's method list. @@ -83,6 +89,29 @@ void write_forward_declarations(std::ostream& os) const; Write forward declarations for all the types extracted by `add_forward_declaration(s)` to `os`. +## encode_dispatch_data + +```c++ +template +static void encode_dispatch_data( + const Compiler& compiler, std::ostream& os); (1) +template +static void encode_dispatch_data( + const Compiler& compiler, const std::string& policy, std::ostream& os); (2) +``` + +Write code to initialize the dispatch data for a policy to `os`. +`compiler` is the object returned by the ->`update` function. + +(1) targets the default policy. (2) targets the specified policy. + +Ther generated code consists of a data structure, named `yomm2_dispatch_data`, +and a function call. It is suitable for inclusionh in a function body - e.g. +`main`. It is assumed that `` has been included, and +that the policy is visible. + ## Example -See the [static-slots](https://github.com/jll63/yomm2/tree/master/examples/static-slots) example. +See the +[generator](https://github.com/jll63/yomm2/tree/master/examples/generator) +example. diff --git a/docs/README.md b/docs/README.md index e5958413..e7a52830 100644 --- a/docs/README.md +++ b/docs/README.md @@ -169,6 +169,7 @@ for the shared runtime to be used. | [error_handler_type](/yomm2/reference/error.html) | type | handler function | | [error_type](/yomm2/reference/error.html) | variant | object passed to error handler | | [friend_method](/yomm2/reference/friend_method.html) | macro | make a method in a container, or the entire container, a friend | +| [generator](/yomm2/reference/generator.html) | class | generate compile-time offsets, pre-calculate dispatch data | | [hash_search_error](/yomm2/reference/error.html) | class | failure to find a hash function for registered classes | | [make_virtual_shared](/yomm2/reference/virtual_ptr.html) | function template | create an object and return a `virtual_shared_ptr` | | [method](/yomm2/reference/method.html) | class template | implement a method | diff --git a/docs/reference/generator.md b/docs/reference/generator.md new file mode 100644 index 00000000..68e4179b --- /dev/null +++ b/docs/reference/generator.md @@ -0,0 +1,117 @@ +yorel::yomm2::generator
+defined in
+ +```c++ +class generator; +``` + +This mechanism generates C++ source files that can be included in a project to +speed up method dispatch, and to make dispatch data construction significantly +less resource intensive. + +Like virtual functions, methods occupy slots in v-tables associated to classes. +Unlike virtual functions, the slots cannot be determined by looking at a single +translation unit; the entire program has to be examined before the slots +are known. By default, method dispatch reads the slots from variables set by +[`update`](/yomm2/reference/update.html). The additional reads put open methods at a disadvantage, compared to +ordinary virtual functions. + +`write_static_offsets` generates C++ code that enables method dispatch to use +"static" slots - i.e. slots known at compile time. Static slots should be made +visible (typically by means including the generated code) before these methods +are called. A program may contain a mixture of methods that use static slots, +and methods that do not. However, this should be consistent across translation +units; failing to ensure this is a ODR violation. + +Using static slots shaves off 2*N-1 memory reads from a method call, where N is +the number of virtual parameters in a method. A 1-method call via a +[`virtual_ptr`](/yomm2/reference/virtual_ptr.html), using static offsets, takes 2 instructions on a x64 CPU, the +same as a virtual function call, but one fewer memory read. See the example for +assembly listings. + +The code generated by `write_static_offsets` requires that types used by the +method (parameter types, return type and method key) to be known. This is easy +when using static slots for specific methods - by including the generated static +offsets for the method just before the method declaration; it is more +challenging when using static offsets for an entire program. +`write_forward_declarations` attempts to generate suitable forward declarations, +but it has limitations. See its documentation. + +`encode_dispatch_data` initializes the dispatch tables for a policy, using a +compact representation of the data produced by [`update`](/yomm2/reference/update.html). It merely copies +integers and it does not allocate memory from the heap. + +## Member functions + +| Name | Description | +| --------------------------------------------------------- | --------------------------------------------------- | +| [write_static_offsets](#write_static_offsets) | write static slots for a method or a policy | +| [add_forward_declaration](#add_forward_declaration) | register types for forward declaration generation | +| [write_forward_declarations](#write_forward_declarations) | write forward declarations for the registered types | +| [encode_dispatch_data](#write_forward_declarations) | write data and code to initialize dispatch tables | + +## write_static_offsets + +```c++ +template void write_static_offsets(std::ostream& os) const; (1) +template void write_static_offsets(std::ostream& os) const; (2) +``` +Add the method to the policy's method list. + +1) Write static slots for a single method to `os`. +2) Write static slots for all the methods in a policy to `os`. + +## add_forward_declaration + +```c++ +void add_forward_declaration(std::string_view decl); (1) +void add_forward_declaration(const std::type_info& type); (2) +template void add_forward_declaration(); (3) +template void add_forward_declarations(); (4) +``` +1) Add `decl` to the list of declarations. +2) Add a declaration for `type` to the list of declarations. +3) Equivalent to `add_forward_declaration(typeid(T))`. +4) Add declarations for the return, parameter and key types used by all the + methods in `Policy`. + +(2), (3) and (4) use `boost::demangle` to extract type names. The result is not +guaranteed, as it depends on the availability and the output of a ABI specific +demangling mechanism. Note that no attempt is made at extracting templates, +because it is impossible to guess the tewmplate parameter list. + +## write_forward_declarations + +```c++ +void write_forward_declarations(std::ostream& os) const; +``` + +Write forward declarations for all the types extracted by +`add_forward_declaration(s)` to `os`. + +## encode_dispatch_data + +```c++ +template +static void encode_dispatch_data( + const Compiler& compiler, std::ostream& os); (1) +template +static void encode_dispatch_data( + const Compiler& compiler, const std::string& policy, std::ostream& os); (2) +``` + +Write code to initialize the dispatch data for a policy to `os`. +`compiler` is the object returned by the [`update`](/yomm2/reference/update.html) function. + +(1) targets the default policy. (2) targets the specified policy. + +Ther generated code consists of a data structure, named `yomm2_dispatch_data`, +and a function call. It is suitable for inclusionh in a function body - e.g. +`main`. It is assumed that `` has been included, and +that the policy is visible. + +## Example + +See the +[generator](https://github.com/jll63/yomm2/tree/master/examples/generator) +example. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 6318dab7..4c2688d8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -56,5 +56,5 @@ if (NOT (MSVC AND YOMM2_SHARED)) # would need to add the path to the directory containing yomm2.dll to PATH. # Anyway, if it works with static linking, it is very unlikely that it fails # with the runtime in a DLL. - add_subdirectory(static-slots) + add_subdirectory(generator) endif() diff --git a/examples/static-slots/CMakeLists.txt b/examples/generator/CMakeLists.txt similarity index 50% rename from examples/static-slots/CMakeLists.txt rename to examples/generator/CMakeLists.txt index aae6a606..64492d2c 100644 --- a/examples/static-slots/CMakeLists.txt +++ b/examples/generator/CMakeLists.txt @@ -3,16 +3,17 @@ # See accompanying file LICENSE_1_0.txt # or copy at http://www.boost.org/LICENSE_1_0.txt) -add_executable(generate-static-slots generate-static-slots.cpp animals.cpp) -target_include_directories(generate-static-slots PRIVATE .) -target_link_libraries(generate-static-slots YOMM2::yomm2) +add_executable(generate generate.cpp animals.cpp) +target_include_directories(generate PRIVATE .) +target_link_libraries(generate YOMM2::yomm2) set(GENERATED_FILES "${CMAKE_CURRENT_BINARY_DIR}/slots.hpp;${CMAKE_CURRENT_BINARY_DIR}/tables.hpp") add_custom_command( OUTPUT ${GENERATED_FILES} - COMMAND generate-static-slots ${GENERATED_FILES} - DEPENDS generate-static-slots animals.cpp + COMMAND generate ${GENERATED_FILES} + cat "${CMAKE_CURRENT_BINARY_DIR}/slots.hpp" + DEPENDS generate animals.cpp ) add_custom_target( animals_yomm2_slots_hpp @@ -22,11 +23,8 @@ add_library(animals_yomm2_slots INTERFACE) add_dependencies(animals_yomm2_slots animals_yomm2_slots_hpp) target_include_directories(animals_yomm2_slots INTERFACE ${CMAKE_CURRENT_BINARY_DIR}) -add_executable(static-slots-app app.cpp animals.cpp) -target_include_directories(static-slots-app PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +add_executable(generator_example generator_example.cpp animals.cpp) +target_include_directories(generator_example PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries( - static-slots-app PRIVATE animals_yomm2_slots YOMM2::yomm2) -add_test(NAME static-slots-app COMMAND static-slots-app) - -# For convenience: all targets related to static slots generation. -add_custom_target(static-slots DEPENDS static-slots-app test_generator) + generator_example PRIVATE animals_yomm2_slots YOMM2::yomm2) +add_test(NAME generator_example COMMAND generator_example) diff --git a/examples/static-slots/README.md b/examples/generator/README.md similarity index 100% rename from examples/static-slots/README.md rename to examples/generator/README.md diff --git a/examples/static-slots/animals.cpp b/examples/generator/animals.cpp similarity index 100% rename from examples/static-slots/animals.cpp rename to examples/generator/animals.cpp diff --git a/examples/generator/animals.hpp b/examples/generator/animals.hpp new file mode 100644 index 00000000..ccd0561e --- /dev/null +++ b/examples/generator/animals.hpp @@ -0,0 +1,50 @@ +#ifndef ANIMALS_HPP +#define ANIMALS_HPP + +#include + +#include + +// Override the default policy with a minimal policy: +// * no external vptr table, because we will only use 'final' +// * therefore, no need for a hash function +// We use only the 'std_rtti' facet, which is needed by the generator. If this +// was not acceptable, we could build the 'generate' program with RTTI enabled, +// and the application without RTTI. This would require building the 'animals' +// classes twice, but the debug build could be used for the generation. +struct animals_policy : yorel::yomm2::policy::basic_policy< + animals_policy, yorel::yomm2::policy::std_rtti> {}; + +#define YOMM2_DEFAULT_POLICY animals_policy + +#include + +using yorel::yomm2::virtual_ptr; + +struct Animal { + virtual ~Animal(); + virtual void kick() = 0; +}; + +struct Dog : Animal { + virtual void kick() { + } +}; + +struct Cat : Animal { + virtual void kick() { + } +}; + +declare_method(void, kick, (virtual_ptr)); +declare_method(void, pet, (virtual_ptr)); +declare_method( + void, meet, (virtual_ptr, virtual_ptr)); +declare_method( + void, mate, (virtual_ptr, virtual_ptr)); + +#if __has_include("slots.hpp") +#include "slots.hpp" +#endif + +#endif diff --git a/examples/static-slots/generate-static-slots.cpp b/examples/generator/generate.cpp similarity index 63% rename from examples/static-slots/generate-static-slots.cpp rename to examples/generator/generate.cpp index 27fc345a..0d0e7a78 100644 --- a/examples/static-slots/generate-static-slots.cpp +++ b/examples/generator/generate.cpp @@ -7,19 +7,18 @@ int main(int argc, char* argv[]) { using namespace yorel::yomm2; - detail::compiler comp; - comp.update(); + auto compiler = update(); generator generator; std::ofstream slots(argv[1]); generator - .write_static_slots))>( + .write_static_offsets))>( slots) - .write_static_slots, virtual_ptr))>(slots); std::ofstream tables(argv[2]); - generator.encode_dispatch_data(comp, tables); + generator.encode_dispatch_data(compiler, tables); return 0; } diff --git a/examples/generator/generator_example.cpp b/examples/generator/generator_example.cpp new file mode 100644 index 00000000..df18c484 --- /dev/null +++ b/examples/generator/generator_example.cpp @@ -0,0 +1,74 @@ +#include "animals.hpp" +#include + +#include + +// https://godbolt.org/z/rf1bjb544 + +void call_vf(Animal& a) { + // yardstick + a.kick(); + // mov rax, qword ptr [rdi] + // jmp qword ptr [rax + 16] # TAILCALL + +} + +void call_kick(virtual_ptr animal) { + // using dynamic offsets + kick(animal); + // mov rax, qword ptr [rip + method)>::slots_strides] + // mov rax, qword ptr [rsi + 8*rax] + // jmp rax # TAILCALL + +} + +void call_pet(virtual_ptr animal) { + // using static offsets + pet(animal); + // mov rax, qword ptr [rsi + 32] + // jmp rax # TAILCALL + +} + +void call_meet(virtual_ptr a1, virtual_ptr a2) { + // using dynamic offsets + meet(a1, a2); + // mov rax, qword ptr [rip + method, virtual_ptr)>::slots_strides] + // mov rax, qword ptr [rsi + 8*rax] + // mov r8, qword ptr [rip + method, virtual_ptr)>::slots_strides+8] + // mov r8, qword ptr [rcx + 8*r8] + // imul r8, qword ptr [rip + method, virtual_ptr)>::slots_strides+16] + // mov rax, qword ptr [rax + 8*r8] + // jmp rax # TAILCALL +} + +void call_mate(virtual_ptr a1, virtual_ptr a2) { + // using static offsets + mate(a1, a2); + // mov rax, qword ptr [rsi] + // mov r8, qword ptr [rcx + 8] + // lea r8, [r8 + 2*r8] + // mov rax, qword ptr [rax + 8*r8] + // jmp rax # TAILCALL +} + +int main() { + using namespace yorel::yomm2; + + #include "tables.hpp" + + Cat felix; + auto cat = virtual_ptr::final(felix); + Dog snoopy; + virtual_ptr dog = virtual_ptr::final(snoopy); + + // // our yardstick: an ordinary virtual function call + // felix.kick(); + + call_kick(cat); + call_pet(dog); + call_meet(cat, cat); + call_mate(dog, dog); + + return 0; +} diff --git a/examples/static-slots/animals.hpp b/examples/static-slots/animals.hpp deleted file mode 100644 index 4ea931ce..00000000 --- a/examples/static-slots/animals.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef ANIMALS_HPP -#define ANIMALS_HPP - -#include -#include - -using yorel::yomm2::virtual_ptr; - -struct Animal { - virtual ~Animal(); - virtual void kick() = 0; -}; - -struct Dog : Animal { - virtual void kick() { - } -}; - -struct Cat : Animal { - virtual void kick() { - } -}; - -declare_method(void, kick, (virtual_ptr)); -declare_method(void, pet, (virtual_ptr)); -declare_method(void, meet, (virtual_ptr, virtual_ptr)); -declare_method(void, mate, (virtual_ptr, virtual_ptr)); - -#if __has_include("slots.hpp") -#include "slots.hpp" -#endif - -#endif diff --git a/examples/static-slots/app.cpp b/examples/static-slots/app.cpp deleted file mode 100644 index 55273492..00000000 --- a/examples/static-slots/app.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "animals.hpp" -#include - -#include - -// https://godbolt.org/z/rf1bjb544 - -void call_vf(Animal& a) { - // yardstick - a.kick(); -} - -void call_kick(virtual_ptr animal) { - // using dynamic offsets - kick(animal); -} - -void call_pet(virtual_ptr animal) { - // using static offsets - pet(animal); -} - -void call_meet(virtual_ptr a1, virtual_ptr a2) { - // using dynamic offsets - meet(a1, a2); -} - -void call_mate(virtual_ptr a1, virtual_ptr a2) { - // using static offsets - mate(a1, a2); -} - -#include "tables.hpp" - -template -auto collect_slots() { - std::vector slots_strides; - - for (auto& method : Policy::methods) { - std::copy_n( - method.slots_strides_ptr, method.arity(), - std::back_inserter(slots_strides)); - } - - return slots_strides; -} - -int main() { - using namespace yorel::yomm2; - - #ifdef CHECK - - auto compiler = update(); - - std::vector expected_slots_strides = - collect_slots(); - auto expected_dispatch_data = default_policy::dispatch_data; - - for (auto& cls : default_policy::classes) { - *cls.static_vptr = nullptr; - } - - #endif - - auto unpacked = generated(); - - #ifdef CHECK - - auto actual_slots_strides = collect_slots(); - - if (actual_slots_strides.size() != expected_slots_strides.size()) { - std::cerr << "slot count mismatch: " << actual_slots_strides.size() - << " != " << expected_slots_strides.size() << "\n"; - } - -#undef min - auto n = - std::min(actual_slots_strides.size(), expected_slots_strides.size()); - - for (size_t i = 0; i < n; ++i) { - if (actual_slots_strides[i] != expected_slots_strides[i]) { - std::cerr << "slot " << i << " mismatch: " << std::hex - << actual_slots_strides[i] - << " != " << expected_slots_strides[i] << "\n"; - } - } - - auto expected_size = expected_dispatch_data.size(); - auto actual_size = default_policy::dispatch_data.size(); - n = std::min( - default_policy::dispatch_data.size(), expected_dispatch_data.size()); - - if (actual_size != expected_size) { - std::cerr << "dispatch_data mismatch: " << actual_size - << " != " << expected_size << "\n"; - } - - // for (size_t i = 0; i < n; ++i) { - // if (default_policy::dispatch_data[i] != unpacked[i]) { - // std::cerr << "dispatch_data " << i << " : " << std::hex - // << default_policy::dispatch_data[i] - // << " != " << unpacked[i] << "\n"; - // } - // } - - #endif - - Animal&& felix = Cat(); - virtual_ptr cat = felix; - Animal&& snoopy = Dog(); - virtual_ptr dog = snoopy; - - // // our yardstick: an ordinary virtual function call - // felix.kick(); - - kick(cat); - pet(dog); - meet(cat, cat); - mate(dog, dog); - call_mate(dog, dog); - - return 0; -} diff --git a/include/yorel/yomm2/core.hpp b/include/yorel/yomm2/core.hpp index 6561cbdc..2dae31ed 100644 --- a/include/yorel/yomm2/core.hpp +++ b/include/yorel/yomm2/core.hpp @@ -14,18 +14,8 @@ namespace yorel { namespace yomm2 { -#ifdef YOMM2_DEFAULT_POLICY -using default_policy = YOMM2_DEFAULT_POLICY; -#else -#if defined(YOMM2_SHARED) -#ifdef NDEBUG -using default_policy = policy::release_shared; -#else -using default_policy = policy::debug_shared; -#endif -#else -using default_policy = policy::default_static; -#endif +#ifndef YOMM2_DEFAULT_POLICY +#define YOMM2_DEFAULT_POLICY ::yorel::yomm2::default_policy #endif namespace detail { @@ -33,7 +23,7 @@ namespace detail { template using get_policy = std::conditional_t< is_policy>>, - boost::mp11::mp_back>, default_policy>; + boost::mp11::mp_back>, YOMM2_DEFAULT_POLICY>; template using remove_policy = std::conditional_t< @@ -43,13 +33,13 @@ using remove_policy = std::conditional_t< template using virtual_ptr_policy = std::conditional_t< sizeof...(Ts) == 2, boost::mp11::mp_first>, - default_policy>; + YOMM2_DEFAULT_POLICY>; } // namespace detail // ----------------------------------------------------------------------------- // Method -template +template struct method; template @@ -215,7 +205,7 @@ using use_classes = typename detail::use_classes_aux< // ----------------------------------------------------------------------------- // virtual_ptr -template +template class virtual_ptr { template friend class virtual_ptr; @@ -402,12 +392,12 @@ class virtual_ptr { }; template -virtual_ptr(Class&) -> virtual_ptr; +virtual_ptr(Class&) -> virtual_ptr; -template +template using virtual_shared_ptr = virtual_ptr, Policy>; -template +template inline auto make_virtual_shared() { return virtual_shared_ptr::final( std::make_shared>()); @@ -696,19 +686,19 @@ set_method_call_error_handler(method_call_error_handler handler); #if defined(__GXX_RTTI) || defined(_HAS_STATIC_RTTI) -template +template auto update() -> detail::compiler; inline error_handler_type set_error_handler(error_handler_type handler) { auto p = &default_policy::error; - auto prev = default_policy::error; + auto prev= default_policy::error; default_policy::error = handler; return prev; } inline method_call_error_handler set_method_call_error_handler(method_call_error_handler handler) { - auto prev = default_policy::call_error; + auto prev= default_policy::call_error; default_policy::call_error = handler; return prev; } diff --git a/include/yorel/yomm2/generator.hpp b/include/yorel/yomm2/generator.hpp index 231039b6..20c68722 100644 --- a/include/yorel/yomm2/generator.hpp +++ b/include/yorel/yomm2/generator.hpp @@ -32,25 +32,23 @@ class generator { void add_forward_declarations(); void write_forward_declarations(std::ostream& os) const; template - const generator& write_static_slots(std::ostream& os) const; - - template - static void create_dispatch_data( - const Compiler& compiler, std::vector& slots, - std::vector& tables); + const generator& write_static_offsets(std::ostream& os) const; template static void encode_dispatch_data(const Compiler& compiler, std::ostream& os); + template + static void encode_dispatch_data( + const Compiler& compiler, const std::string& policy, std::ostream& os); template static void decode_dispatch_data(Data& data); + private: static constexpr size_t method_bits = sizeof(uint16_t) * 4; static constexpr std::uintptr_t spec_mask = (1 << method_bits) - 1; static constexpr std::uintptr_t stop_bit = 1 << (sizeof(uint16_t) * 8 - 1); static constexpr std::uintptr_t index_bit = 1 << (sizeof(uint16_t) * 8 - 2); - private: - void write_static_slots( + void write_static_offsets( const detail::method_info& method, std::ostream& os) const; static uint16_t encode_group( @@ -208,22 +206,22 @@ inline void generator::write_forward_declarations(std::ostream& os) const { } template -const generator& generator::write_static_slots(std::ostream& os) const { +const generator& generator::write_static_offsets(std::ostream& os) const { using namespace detail; if constexpr (is_policy) { for (auto& method : T::methods) { - write_static_slots(method, os); + write_static_offsets(method, os); } } else { static_assert(is_method); - write_static_slots(T::fn, os); + write_static_offsets(T::fn, os); } return *this; } -void generator::write_static_slots( +void generator::write_static_offsets( const detail::method_info& method, std::ostream& os) const { using namespace detail; @@ -274,6 +272,12 @@ uint16_t generator::encode_group( template void generator::encode_dispatch_data( const Compiler& compiler, std::ostream& os) { + encode_dispatch_data(compiler, "YOMM2_DEFAULT_POLICY", os); +} + +template +void generator::encode_dispatch_data( + const Compiler& compiler, const std::string& policy, std::ostream& os) { const char* indent = " "; using namespace yorel::yomm2::detail; @@ -315,10 +319,6 @@ void generator::encode_dispatch_data( } char prelude_format[] = R"( -auto generated() { - constexpr auto stop_bit = yorel::yomm2::generator::stop_bit; - constexpr auto method_bits = yorel::yomm2::generator::method_bits; - static union { struct { uint16_t headroom[%d]; @@ -327,7 +327,7 @@ auto generated() { uint16_t vtbl[%d]; } packed; std::uintptr_t decode[%d]; - } init = { { {}, { + } yomm2_dispatch_data = { { {}, { )"; const auto total_decode_size = dispatch_tables_size + decode_vtbl_size; @@ -415,13 +415,10 @@ auto generated() { os << "\n"; } - os << R"( } } }; - - yorel::yomm2::generator::decode_dispatch_data(init); - - return init.decode; -} -)"; + os << "\n } } };\n\n"; + os << " yorel::yomm2::generator::decode_dispatch_data<" + << (policy.empty() ? "YOMM2_DEFAULT_POLICY" : policy) + << ">(yomm2_dispatch_data);\n\n"; } template diff --git a/include/yorel/yomm2/macros.hpp b/include/yorel/yomm2/macros.hpp index 998bceff..edee5a09 100644 --- a/include/yorel/yomm2/macros.hpp +++ b/include/yorel/yomm2/macros.hpp @@ -65,14 +65,14 @@ #define YOMM2_DECLARE_3(R, ID, ARGS) \ yOMM2_DECLARE( \ - R, ID, ARGS, ::yorel::yomm2::default_policy, yOMM2_WHEN_NOT_STATIC) + R, ID, ARGS, YOMM2_DEFAULT_POLICY, yOMM2_WHEN_NOT_STATIC) #define YOMM2_DECLARE_4(R, ID, ARGS, POLICY) \ yOMM2_DECLARE(R, ID, ARGS, POLICY, yOMM2_WHEN_NOT_STATIC) #define YOMM2_STATIC_DECLARE_3(R, ID, ARGS) \ yOMM2_DECLARE( \ - R, ID, ARGS, ::yorel::yomm2::default_policy, yOMM2_WHEN_STATIC) + R, ID, ARGS, YOMM2_DEFAULT_POLICY, yOMM2_WHEN_STATIC) #define YOMM2_STATIC_DECLARE_4(R, ID, ARGS, POLICY) \ yOMM2_DECLARE(R, ID, ARGS, POLICY, yOMM2_WHEN_STATIC) @@ -215,7 +215,7 @@ #define YOMM2_CLASSES(...) \ static ::yorel::yomm2::detail::use_classes_macro< \ - __VA_ARGS__, ::yorel::yomm2::default_policy> \ + __VA_ARGS__, YOMM2_DEFAULT_POLICY> \ YOMM2_GENSYM; #if !BOOST_PP_VARIADICS_MSVC diff --git a/include/yorel/yomm2/policy.hpp b/include/yorel/yomm2/policy.hpp index 432d57e9..23fd3b52 100644 --- a/include/yorel/yomm2/policy.hpp +++ b/include/yorel/yomm2/policy.hpp @@ -4,7 +4,9 @@ #include #include #include +#include #include +#include #include // for default_random_en... #include #include @@ -758,6 +760,16 @@ using default_static = policy::debug; } // namespace policy +#if defined(YOMM2_SHARED) +#ifdef NDEBUG +using default_policy = policy::release_shared; +#else +using default_policy = policy::debug_shared; +#endif +#else +using default_policy = policy::default_static; +#endif + namespace detail { struct update_report : update_method_report {}; diff --git a/tests/rdtsc-benchmark.cpp b/tests/rdtsc-benchmark.cpp index 6611fbdc..4fc16687 100644 --- a/tests/rdtsc-benchmark.cpp +++ b/tests/rdtsc-benchmark.cpp @@ -330,7 +330,7 @@ int main(int argc, char** argv) { // { // compiler comp; // comp.compile(); - // comp.write_static_slots(std::cout); + // comp.write_static_offsets(std::cout); // } // }); diff --git a/tests/test_generator.cpp b/tests/test_generator.cpp index ffc3f44b..d5371c7e 100644 --- a/tests/test_generator.cpp +++ b/tests/test_generator.cpp @@ -199,7 +199,7 @@ BOOST_AUTO_TEST_CASE(test_generate_offsets) { std::ostringstream os; os << "\n"; generator gen; - gen.write_static_slots(os); + gen.write_static_offsets(os); std::string_view expected = R"( template<> struct yorel::yomm2::detail::static_offsets, int), test_policy_<1>>> {static constexpr size_t slots[] = {2}; }; )"; @@ -215,7 +215,7 @@ template<> struct yorel::yomm2::detail::static_offsets(os); + gen.write_static_offsets(os); std::string_view expected = R"( template<> struct yorel::yomm2::detail::static_offsets, yorel::yomm2::virtual_), test_policy_<1>>> {static constexpr size_t slots[] = {0, 1}; static constexpr size_t strides[] = {1}; }; template<> struct yorel::yomm2::detail::static_offsets, int), test_policy_<1>>> {static constexpr size_t slots[] = {2}; }; From 99991d688379e2f7b4b817ddc425464a749fc8d4 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 28 Jul 2024 11:02:35 -0400 Subject: [PATCH 4/6] version 1.6.0 --- CMakeLists.txt | 2 +- examples/conan/conanfile.txt | 2 +- vcpkg.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f1ca73d7..ce56733e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.20) cmake_policy(SET CMP0057 NEW) -project(YOMM2 LANGUAGES CXX VERSION 1.5.2) +project(YOMM2 LANGUAGES CXX VERSION 1.6.0) if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 17) diff --git a/examples/conan/conanfile.txt b/examples/conan/conanfile.txt index 2c22cf72..4890ef77 100644 --- a/examples/conan/conanfile.txt +++ b/examples/conan/conanfile.txt @@ -1,5 +1,5 @@ [requires] -yomm2/1.5.1 +yomm2/1.6.0 [generators] CMakeDeps CMakeToolchain diff --git a/vcpkg.json b/vcpkg.json index d6b2c306..66180664 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,6 @@ { "name": "yomm2", - "version": "1.5.3", + "version": "1.6.0", "dependencies": [ "benchmark", "boost-core", From b8d234ad9b79c0351120973259fc738ff43f9fdf Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 29 Jul 2024 16:57:07 -0400 Subject: [PATCH 5/6] Update .github/workflows/main.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fabien Péan --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5ed2389f..13be9bdc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v4 - name: Configure run: | - git clone https://github.com/microsoft/vcpkg "$HOME/vcpkg" + git clone --branch 2024.07.12 --depth 1 https://github.com/microsoft/vcpkg "$HOME/vcpkg" export VCPKG_ROOT="$HOME/vcpkg" cmake --preset ci-${{ matrix.config }} -B build -DCMAKE_CXX_STANDARD=${{ matrix.standard }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} - name: Build From 02e52d85ca791ce43f5b076cb6c9b44c69af927b Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 29 Jul 2024 16:57:19 -0400 Subject: [PATCH 6/6] Update CMakeLists.txt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fabien Péan --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce56733e..c9fc39ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ macro(assign_bool VAR) endif() endmacro() -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") +if(NOT MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(COMPILER_IS_CLANG ON) endif()