diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0ec928a..ce94fb8e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,7 @@ on: - master - develop - bugfix/** + - doc/** - feature/** - fix/** - github/** diff --git a/dev/ce_link.py b/dev/ce_link.py new file mode 100755 index 00000000..3a5c224b --- /dev/null +++ b/dev/ce_link.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017-2026 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) + +"""Print a Compiler Explorer link that opens a documentation example. + +The link carries the source itself, as a `/clientstate/` URL: nothing +is stored on Compiler Explorer until a reader opens it, and the link is +regenerated from the example whenever the example changes: + + dev/ce_link.py doc/modules/ROOT/examples/ast_virtual_function.cpp + +The source is the tagged region the page includes - `tag=content` by default - +so the reader gets exactly the code shown above the link. + +With `--beside`, the two examples are compiled side by side, each in its own +compiler pane, to compare the assembly: + + dev/ce_link.py doc/modules/ROOT/examples/dispatch_open_method.cpp \\ + --beside doc/modules/ROOT/examples/dispatch_virtual_function.cpp + +An example that includes a Boost header gets the Boost release that Compiler +Explorer offers in its Libraries dropdown, not the headers of this tree. +""" + +import argparse +import base64 +import json +import re +import sys +import urllib.parse +import zlib +from pathlib import Path + +COMPILER = "clang2310" # x86-64 clang 23.1.0 +OPTIONS = "-std=c++17 -O2" +BOOST = "192" # Boost 1.92.0, as Compiler Explorer names it +FILTERS = { + "intel": True, + "demangle": True, + "labels": True, + # CE takes a function whose every instruction comes from a header for + # library code: `call_via_virtual_ptr` is the inlined dispatch, all of it. + "libraryCode": False, + "directives": True, + "commentOnly": True, +} +TAG_DIRECTIVE = re.compile(r"\s*// (tag|end)::\w+\[\]\s*$") + + +def tagged_region(path, tag): + begin, end = f"// tag::{tag}[]", f"// end::{tag}[]" + lines = path.read_text().splitlines(keepends=True) + starts = [i for i, line in enumerate(lines) if line.strip() == begin] + ends = [i for i, line in enumerate(lines) if line.strip() == end] + + if len(starts) != 1 or len(ends) != 1 or starts[0] > ends[0]: + sys.exit(f"{path}: expected exactly one `{begin}` ... `{end}`") + + # Like Asciidoctor, drop the directives of the tags nested in the region. + region = lines[starts[0] + 1 : ends[0]] + + return "".join(line for line in region if not TAG_DIRECTIVE.match(line)) + + +def uses_boost(source): + return "#include +#include + +struct Node { + virtual ~Node() {} + virtual int value() const = 0; +}; + +using boost::openmethod::virtual_ptr; + +BOOST_OPENMETHOD(postfix, (virtual_ptr node, std::ostream& os), void); + +// tag::call_via_ref[] +void call_via_ref(const Node& node, std::ostream& os) { + postfix(node, os); +} +// end::call_via_ref[] + +// tag::call_via_virtual_ptr[] +void call_via_virtual_ptr(virtual_ptr node, std::ostream& os) { + postfix(node, os); +} +// end::call_via_virtual_ptr[] +// end::content[] + +int main() { +} diff --git a/doc/modules/ROOT/examples/dispatch_virtual_function.cpp b/doc/modules/ROOT/examples/dispatch_virtual_function.cpp new file mode 100644 index 00000000..9ba1f6b1 --- /dev/null +++ b/doc/modules/ROOT/examples/dispatch_virtual_function.cpp @@ -0,0 +1,25 @@ +// Copyright (c) 2017-2026 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) + +// clang-format off + +// tag::content[] +#include + +struct Node { + virtual ~Node() {} + virtual int value() const = 0; + virtual void postfix(std::ostream& os) const = 0; +}; + +// tag::call_virtual_function[] +void call_virtual_function(const Node& node, std::ostream& os) { + node.postfix(os); +} +// end::call_virtual_function[] +// end::content[] + +int main() { +} diff --git a/doc/modules/ROOT/pages/basics.adoc b/doc/modules/ROOT/pages/basics.adoc index 622a4b99..8b760187 100644 --- a/doc/modules/ROOT/pages/basics.adoc +++ b/doc/modules/ROOT/pages/basics.adoc @@ -114,6 +114,8 @@ Putting it all together: include::{examplesdir}/ast.cpp[tag=content] ---- +link:https://godbolt.org/clientstate/eNrFVn9r2zAQ%2FSqHB63SOE2TFsbixLCtgRW2pDSl%2FyxdUWw1FTiWkWXTzXiffSfZys%2B261jGHCLbT0%2F37izdSYWTsjTlIk6dHnwtHB7iveOCE9F4ntE5w1cnaDYdhFKRycAAqZJZoGAkQgbFNAa8ci5VRiP4qUHSgKLcxHmsIKdRpvsClFMwgBNvGpf4n8a1wRsqOZ1FDHobti1MlkYayMhJ%2FWildiVEzqTk2hBIpjIZQ%2B7BOnvbgcsoS7fENUQqcxo%2BgIjdKxfWEcnnD0q7pLuIbhpuBZK6609c1OOPLalZ2bHvS%2B%2B3HfJ2HdqO7Zov2HZwBvt%2F0R3tIbp2G77t8zIWIz6TVH5H2ZAZYLDPy1ikSRLxgCrMvZVMvs9Lf503PA6iDCegPxMiVW2RsHjB1IMIjx%2BSxH%2BJ0OYxV5xG%2FAfb4XKkSkYXvtbIUh7PwQzv9Vbje7069%2B8SJc1UfRiPJ9d348vh6Mvw%2BtP4nCQ45J4%2FukDWqP3VXPsQY%2BtCqtBaLXkAIsUFmAseNp60eje%2BGV5dXZwPSbWYXhKxlcXHdSuf17HpIlLo9zW15ZvS8bfyurb4kGD7e%2B3aDtHsll%2BlKbK8dc8O4dB7im0yZpc%2BdaA5dfYRiKkjPih9e30ohv76WGr688EcvRDMx8%2FvJ5PhhIzMgrLz7popcCv%2Fq%2BWkPYWQLcRGWWQ7zttlGYhMWU%2BWQO3QQDf4yJYVz%2BBT1HE8wHzvwimW%2BDMshAPontS%2B6zK6oDwmS80ncmuVnMSqL3dPWnRLF2bFKbZBcVZ6q50MwoJiV%2BnV2wErQuRYhgmbNaqPqLf8QCwSHjG5fjpwAn026J52TjQDK%2BVGp3FVd2Dl1%2BcKjXXedZ3yFjGRqPqo4bTwWw3wYNF5C61x3c0eWZApYdWsOL79U%2BnyFn%2B%2FAJCJ9%2Fs%3D[Run this example on Compiler Explorer^] + [#registering_classes_by_reflection] ## Registering Classes by Reflection diff --git a/doc/modules/ROOT/pages/motivation.adoc b/doc/modules/ROOT/pages/motivation.adoc index 895f7dd6..df6a7b0e 100644 --- a/doc/modules/ROOT/pages/motivation.adoc +++ b/doc/modules/ROOT/pages/motivation.adoc @@ -42,14 +42,20 @@ struct Times : Node { } }; +void demo(const Node& e) { + e.postfix(std::cout); // 2 3 + 4 * + std::cout << " = " << e.value() << "\n"; // = 20 +} + int main() { Variable a{2}, b{3}, c{4}; Plus d{a, b}; Times e{d, c}; - e.postfix(std::cout); // 2 3 + 4 * - std::cout << " = " << e.value() << "\n"; // = 20 + demo(e); } ---- +link:https://godbolt.org/clientstate/eNrNVMtu2zAQ%2FJUFCyRS7CS2HKCAZecT0hyKXqocaIpxCUiiwYcQQFC%2FPbsS5UhKL2kOrQzQ5OxydnZAsmFWWqt0ZdkWfjZM5fi%2FXgIreHX0%2FChxycRiwRCy2hvRAV9UJQqfS9gpbZ2RvLzPqqzCqRcOHjRGmqwC%2FGplnOcF%2FCYwiqFpp7iqHNS88BQTqMLBHlbpNKfWKocTFnpWL5F1%2BXYbil6AttNtbTrS8YMbxQ%2BFhO1E0gBH59oxZtRRmA4K3yvTtTRGEREY6bypoE6h%2FQutIyJtYbcb8XRV5408Ft7OmiAo6tkIvoBCPrsljBGjjr8ctUahiIZ42YNRCH2kVdp%2FMyQtep5h%2FebCx7rvN9HXsQ8bMTUNxlzCZRpq%2FSGaMVhkLJyWIGHuSfrek7m931Up5%2F522L8z%2BOq%2FMfjqMwZ3cnNZ6omTdMl6Lnkz6UVo7%2BJQ7QwEHXsacCrPHnV4hmVYCre3kMAGz%2BUdWreHZIUKSAAZX3JVRfH89gNvknYJh2aDo2ju2vTtYkHecAy1aTgasskxZ8jo%2BpGkE0vQuyh0eVKFNOMnlAl6QJPNekUZhTp0wSec65MLzy27xib3%2BLiuv8L1t4S1FJYvUninB7KBG1efYW6f8PcK2LHoow%3D%3D[Run this example on Compiler Explorer^] + If the classes are part of a small, self-contained program, this may be a reasonable solution. Otherwise, there are serious problems with this approach. diff --git a/doc/modules/ROOT/pages/performance.adoc b/doc/modules/ROOT/pages/performance.adoc index d044c4e4..8d5dbe20 100644 --- a/doc/modules/ROOT/pages/performance.adoc +++ b/doc/modules/ROOT/pages/performance.adoc @@ -4,14 +4,17 @@ Open-methods can be as fast as ordinary virtual member functions when compiled with optimization. +NOTE: The assembly on this page is generated by x86-64 clang 23.1.0 with +`-std=c++17 -O2`, against Boost 1.92. Clang 22 and earlier load the target of +the indirect jump into a register first: one more `mov` per call, in the +virtual function call and the method calls alike. + First, let's examine the code generated by clang for an ordinary virtual function call: [source,c++] ---- -void call_virtual_function(const Node& node, std::ostream& os) { - node.postfix(os); -} +include::{examplesdir}/dispatch_virtual_function.cpp[tag=call_virtual_function] ---- Clang compiles this function to the following assembly on the x64 architecture: @@ -19,8 +22,7 @@ Clang compiles this function to the following assembly on the x64 architecture: [source,asm] ---- mov rax, qword ptr [rdi] -mov rax, qword ptr [rax + 24] -jmp rax # TAILCALL +jmp qword ptr [rax + 24] # TAILCALL ---- llvm-mca estimates this code has a throughput of 1 cycle per dispatch. @@ -29,9 +31,7 @@ Let's look at a method call now: [source,c++] ---- -void call_via_ref(const Node& node, std::ostream& os) { - postfix(node, os); -} +include::{examplesdir}/dispatch_open_method.cpp[tag=call_via_ref] ---- This compiles to (variable names are shortened for readability): @@ -48,9 +48,8 @@ This compiles to (variable names are shortened for readability): mov rcx, qword ptr [rip + vptr_vector_vptrs] mov rdi, qword ptr [rcx + 8*rdi] mov rcx, qword ptr [rip + postfix::fn+88] - mov rcx, qword ptr [rdi + 8*rcx] mov rsi, rax - jmp rcx # TAILCALL + jmp qword ptr [rdi + 8*rcx] # TAILCALL ---- This is quite a few instructions more. Upon closer examination, we see that many @@ -86,9 +85,7 @@ If we already have a `virtual_ptr`: [source,c++] ---- -void call_via_virtual_ptr(virtual_ptr node, std::ostream& os) { - postfix(node, os); -} +include::{examplesdir}/dispatch_open_method.cpp[tag=call_via_virtual_ptr] ---- A method call compiles to: @@ -96,10 +93,11 @@ A method call compiles to: [source,asm] ---- mov rax, qword ptr [rip + postfix::fn+88] -mov rax, qword ptr [rdi + 8*rax] -jmp rax # TAILCALL +jmp qword ptr [rdi + 8*rax] # TAILCALL ---- +link:https://godbolt.org/clientstate/eNrNU1tr2zAU%2FisHDYpL3Uu8hzL38tAusJfFg%2FZtKUGx5VYgHxlJ9haM99t3ZDup03ZrO1ZoAol0vu9c9Z2GpRqdQMdi%2BN4wtyoFnZjRP1gIT2OpVlWBf4GLUqM3h6PLjBcDmolpJp02W%2FCV487jDZMZ%2FU0IUxxvO4%2B9PU%2B1ujJpF%2BKDxFRVmYBTqa0zghfnc5wjHavUwYwSQDNHoE8tjau4gl%2FeGOxC027bJTqouao8Rr1YB2dwdLLNqbXMoKREufwZWJfF8ZB0B7TddmtPfB2dQ8qVWgwhFnmFqZMag57si9kBpN8QHgccSvfwwTot2X34ObK2DeHloy5KqcSzg95M9ompb2J4k8eij5MjDyi5tP7db%2BisS9%2Bdv7J9auiMfCfHsJ9EnphL5YSxfUqSi6ITvRT1zjJRUEQl7i2KL4Wyo7tcGm5WlzQMMuZc2c5PGkEDrcWISYUW1F6CajUY27a9eTCtN9dt9DLdLjU97KEuBRbC3ens4K4sScNvL%2BxeoZWVeAtdESS%2BTRVxvBZs6UxHvEiSq%2BtF8m06%2Bzq9%2FpJ8DgZBhhCMqKf3sj7%2Fk6zDbo92Hy0IXxiRv2Yv1ivR0zaL8TDqqLzXlvpMpv%2B%2BgtFYJNE%2FrGDDcEjXPamH6p4%2B%2BRSx9p1vqP%2F%2BBlxDKSs%3D[Compare the three calls on Compiler Explorer^] + `virtual_ptr` arguments are passed through the method call, to the overrider, which can use them to make further method calls.