Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ on:
- master
- develop
- bugfix/**
- doc/**
- feature/**
- fix/**
- github/**
Expand Down
156 changes: 156 additions & 0 deletions dev/ce_link.py
Original file line number Diff line number Diff line change
@@ -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/<base64>` 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 <boost/" in source


def session_state(source, compiler):
libs = [{"id": "boost", "version": BOOST}] if uses_boost(source) else []
compiler = {"id": compiler, "libs": libs, "options": OPTIONS}

return {
"sessions": [
{
"id": 1,
"language": "c++",
"source": source,
"compilers": [compiler],
"executors": [{"compiler": compiler}],
}
]
}


def component(name, **state):
return {"type": "component", "componentName": name, "componentState": state}


def libs(source):
return [{"name": "boost", "ver": BOOST}] if uses_boost(source) else []


def editor(id, source):
return component("codeEditor", id=id, lang="c++", source=source)


def beside_state(base, source, compiler):
def column(id, source):
return {
"type": "column",
"content": [
editor(id, source),
component(
"compiler",
id=id,
source=id,
lang="c++",
compiler=compiler,
libs=libs(source),
options=OPTIONS,
filters=FILTERS,
),
],
}

return {"content": [{"type": "row", "content": [column(1, base), column(2, source)]}]}


def clientstate_url(state):
# CE detects the deflate on its own.
packed = zlib.compress(json.dumps(state).encode(), 9)
encoded = base64.b64encode(packed).decode()
# Escape `+`, `/` and `=`: a pair of `+` would be taken by AsciiDoc for a
# passthrough before the `link:` macro sees the URL.
encoded = urllib.parse.quote(encoded, safe="")

return "https://godbolt.org/clientstate/" + encoded


def main():
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("example", type=Path)
parser.add_argument("--tag", default="content")
parser.add_argument(
"--beside", type=Path, help="compile this example beside, for the assembly"
)
parser.add_argument("--beside-tag", help="the tag of the other example (default: --tag)")
parser.add_argument("--compiler", default=COMPILER, help="CE compiler id")
args = parser.parse_args()
source = tagged_region(args.example, args.tag)

if args.beside:
other = tagged_region(args.beside, args.beside_tag or args.tag)
state = beside_state(other, source, args.compiler)
else:
state = session_state(source, args.compiler)

print(clientstate_url(state))


if __name__ == "__main__":
main()
8 changes: 6 additions & 2 deletions doc/modules/ROOT/examples/ast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,16 @@ BOOST_OPENMETHOD_OVERRIDE(
BOOST_OPENMETHOD_CLASSES(Node, Variable, Plus, Times);
// end::class_registration[]

void demo(const Node& e) {
postfix(e, std::cout);
std::cout << " = " << e.value() << "\n"; // 2 3 + 4 * = 20
}

int main() {
boost::openmethod::initialize();
Variable a{2}, b{3}, c{4};
Plus d{a, b}; Times e{d, c};
postfix(e, std::cout);
std::cout << " = " << e.value() << "\n"; // 2 3 + 4 * = 20
demo(e);
}
// end::content[]

Expand Down
6 changes: 5 additions & 1 deletion doc/modules/ROOT/examples/ast_virtual_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ struct Times : Node {
const Node& left; const Node& right;
};

void demo(const Node& e) {
std::cout << e.value() << "\n"; // 20
}

int main() {
Variable a{2}, b{3}, c{4};
Plus d{a, b}; Times e{d, c};
std::cout << e.value() << "\n"; // 20
demo(e);
}
// end::content[]
8 changes: 6 additions & 2 deletions doc/modules/ROOT/examples/ast_virtual_function_2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,15 @@ struct Times : Node {
const Node& left; const Node& right;
};

void demo(const Node& e) {
e.postfix(std::cout);
std::cout << " = " << e.value() << "\n"; // 2 3 + 4 * = 20
}

int main() {
Variable a{2}, b{3}, c{4};
Plus d{a, b}; Times e{d, c};
e.postfix(std::cout);
std::cout << " = " << e.value() << "\n"; // 2 3 + 4 * = 20
demo(e);
}
// end::all[]

Expand Down
35 changes: 35 additions & 0 deletions doc/modules/ROOT/examples/dispatch_open_method.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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 <boost/openmethod.hpp>
#include <iostream>

struct Node {
virtual ~Node() {}
virtual int value() const = 0;
};

using boost::openmethod::virtual_ptr;

BOOST_OPENMETHOD(postfix, (virtual_ptr<const Node> 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<const Node> node, std::ostream& os) {
postfix(node, os);
}
// end::call_via_virtual_ptr[]
// end::content[]

int main() {
}
25 changes: 25 additions & 0 deletions doc/modules/ROOT/examples/dispatch_virtual_function.cpp
Original file line number Diff line number Diff line change
@@ -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 <iostream>

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() {
}
2 changes: 2 additions & 0 deletions doc/modules/ROOT/pages/basics.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions doc/modules/ROOT/pages/motivation.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
28 changes: 13 additions & 15 deletions doc/modules/ROOT/pages/performance.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,25 @@
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:

[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.
Expand All @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -86,20 +85,19 @@ If we already have a `virtual_ptr`:

[source,c++]
----
void call_via_virtual_ptr(virtual_ptr<const Node> node, std::ostream& os) {
postfix(node, os);
}
include::{examplesdir}/dispatch_open_method.cpp[tag=call_via_virtual_ptr]
----

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.

Expand Down
Loading