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
21 changes: 21 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,27 @@ compiles each generated header after the root, checks that each one *fails* on i
and runs the `ce/*.cpp` examples against the generated tree. Those examples are also ordinary
tests in the CMake build (`ce/CMakeLists.txt`), so they cannot rot silently.

### Compiler Explorer links in the documentation

**A link from a doc page uses the normal includes, not the flattened headers.** The example
includes `<boost/openmethod.hpp>` as it would locally, and the link selects Boost from CE's
Libraries list. The flattened headers are for the `ce/` examples only.

`dev/ce_link.py` generates the link from the tagged region the page includes, as a
`godbolt.org/clientstate/<base64>` URL that carries the source itself:

```bash
dev/ce_link.py doc/modules/ROOT/examples/ast.cpp
```

Its constants fix the compiler (x86-64 clang 23.1.0), the options (`-std=c++17 -O2`) and the Boost
release (1.92). The NOTE at the top of `performance.adoc` states all three, so change them together.

- **A link runs the Boost release CE offers, not this tree.** An example that uses something newer
than that release breaks its link. Compile it against that release's headers before linking it.
- **Nothing regenerates a link.** When an example changes, its link keeps the old code until
`dev/ce_link.py` is re-run and the new URL pasted into the page.

### Custom RTTI
When `<typeinfo>` is unavailable or insufficient, use static_rtti or implement custom RTTI. See `doc/modules/ROOT/examples/custom_rtti/` and policies in `include/boost/openmethod/policies/`.

Expand Down
81 changes: 62 additions & 19 deletions dev/ce_link.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@
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:
With `--column EXAMPLE:TAG`, given once per column, the link opens the
examples side by side, each source above its assembly, to compare the code
generated for several functions. A column holds the tagged region of its
example, minus every region nested in it except `TAG`, so that its assembly
shows that one function:

dev/ce_link.py doc/modules/ROOT/examples/dispatch_open_method.cpp \\
--beside doc/modules/ROOT/examples/dispatch_virtual_function.cpp
dev/ce_link.py \\
--column doc/modules/ROOT/examples/dispatch_virtual_function.cpp:virtual_function \\
--column doc/modules/ROOT/examples/dispatch_open_method.cpp:method_via_ref \\
--column doc/modules/ROOT/examples/dispatch_open_method.cpp:method_via_virtual_ptr

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.
Expand All @@ -42,15 +47,16 @@
"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.
# library code: `method_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*$")
TAG_DIRECTIVE = re.compile(r"\s*// (tag|end)::(\w+)\[\]\s*$")


def tagged_region(path, tag):
def tagged_region(path, tag, only=None):
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]
Expand All @@ -60,9 +66,31 @@ def tagged_region(path, tag):
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]]
# With `only`, also drop the regions of the nested tags other than `only`.
kept, nested, skipping = [], set(), None

return "".join(line for line in region if not TAG_DIRECTIVE.match(line))
for line in lines[starts[0] + 1 : ends[0]]:
directive = TAG_DIRECTIVE.match(line)

if directive:
kind, name = directive.groups()
nested.add(name)

if only and name != only:
skipping = name if kind == "tag" else None
elif not skipping:
kept.append(line)

if only and only not in nested:
sys.exit(f"{path}: no `// tag::{only}[]` inside `{begin}`")

source = "".join(kept)

if only:
# A dropped region leaves the blank lines that surrounded it.
source = re.sub(r"\n{3,}", "\n\n", source).rstrip() + "\n"

return source


def uses_boost(source):
Expand Down Expand Up @@ -98,7 +126,7 @@ def editor(id, source):
return component("codeEditor", id=id, lang="c++", source=source)


def beside_state(base, source, compiler):
def columns_state(sources, compiler):
def column(id, source):
return {
"type": "column",
Expand All @@ -117,7 +145,9 @@ def column(id, source):
],
}

return {"content": [{"type": "row", "content": [column(1, base), column(2, source)]}]}
columns = [column(id, source) for id, source in enumerate(sources, 1)]

return {"content": [{"type": "row", "content": columns}]}


def clientstate_url(state):
Expand All @@ -133,21 +163,34 @@ def clientstate_url(state):

def main():
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("example", type=Path)
parser.add_argument("example", type=Path, nargs="?")
parser.add_argument("--tag", default="content")
parser.add_argument(
"--beside", type=Path, help="compile this example beside, for the assembly"
"--column",
action="append",
metavar="EXAMPLE:TAG",
help="a column showing the function in the nested region TAG",
)
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)
if bool(args.example) == bool(args.column):
parser.error("give either an example or --column, not both")

if args.column:
sources = []

for spec in args.column:
path, sep, only = spec.rpartition(":")

if not sep or not path or not only:
parser.error(f"--column {spec}: expected EXAMPLE:TAG")

sources.append(tagged_region(Path(path), args.tag, only))

state = columns_state(sources, args.compiler)
else:
state = session_state(source, args.compiler)
state = session_state(tagged_region(args.example, args.tag), args.compiler)

print(clientstate_url(state))

Expand Down
4 changes: 2 additions & 2 deletions doc/modules/ROOT/examples/ast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ int main() {
}
// end::content[]

void call_via_ref(const Node& node, std::ostream& os) {
void method_via_ref(const Node& node, std::ostream& os) {
postfix(node, os);
}

void call_via_virtual_ptr(virtual_ptr<const Node> node, std::ostream& os) {
void method_via_virtual_ptr(virtual_ptr<const Node> node, std::ostream& os) {
postfix(node, os);
}
2 changes: 1 addition & 1 deletion doc/modules/ROOT/examples/ast_virtual_function_2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ int main() {
}
// end::all[]

void call_virtual_function(const Node& node, std::ostream& os) {
void virtual_function(const Node& node, std::ostream& os) {
node.postfix(os);
}
2 changes: 1 addition & 1 deletion doc/modules/ROOT/examples/custom_rtti/1/custom_rtti.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,6 @@ int main() {
std::cout << " = " << e.value() << "\n"; // 2 3 + 4 * = 20
}

void call_via_ref(const Node& node, std::ostream& os) {
void method_via_ref(const Node& node, std::ostream& os) {
postfix(node, os);
}
2 changes: 1 addition & 1 deletion doc/modules/ROOT/examples/custom_rtti/2/custom_rtti.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,6 @@ int main() {
std::cout << value(e) << "\n"; // 2 3 + 4 * = 20
}

auto call_via_ref(const Node& node) {
auto method_via_ref(const Node& node) {
return value(node);
}
12 changes: 6 additions & 6 deletions doc/modules/ROOT/examples/dispatch_open_method.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ 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) {
// tag::method_via_ref[]
void method_via_ref(const Node& node, std::ostream& os) {
postfix(node, os);
}
// end::call_via_ref[]
// end::method_via_ref[]

// tag::call_via_virtual_ptr[]
void call_via_virtual_ptr(virtual_ptr<const Node> node, std::ostream& os) {
// tag::method_via_virtual_ptr[]
void method_via_virtual_ptr(virtual_ptr<const Node> node, std::ostream& os) {
postfix(node, os);
}
// end::call_via_virtual_ptr[]
// end::method_via_virtual_ptr[]
// end::content[]

int main() {
Expand Down
6 changes: 3 additions & 3 deletions doc/modules/ROOT/examples/dispatch_virtual_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ struct Node {
virtual void postfix(std::ostream& os) const = 0;
};

// tag::call_virtual_function[]
void call_virtual_function(const Node& node, std::ostream& os) {
// tag::virtual_function[]
void virtual_function(const Node& node, std::ostream& os) {
node.postfix(os);
}
// end::call_virtual_function[]
// end::virtual_function[]
// end::content[]

int main() {
Expand Down
12 changes: 6 additions & 6 deletions doc/modules/ROOT/pages/performance.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function call:

[source,c++]
----
include::{examplesdir}/dispatch_virtual_function.cpp[tag=call_virtual_function]
include::{examplesdir}/dispatch_virtual_function.cpp[tag=virtual_function]
----

Clang compiles this function to the following assembly on the x64 architecture:
Expand All @@ -31,7 +31,7 @@ Let's look at a method call now:

[source,c++]
----
include::{examplesdir}/dispatch_open_method.cpp[tag=call_via_ref]
include::{examplesdir}/dispatch_open_method.cpp[tag=method_via_ref]
----

This compiles to (variable names are shortened for readability):
Expand Down Expand Up @@ -65,8 +65,8 @@ a single virtual argument _via_ _a_ _reference_ is between 30% and 50% slower
than calling the equivalent virtual function, with an empty body and no other
arguments. In most real programs, the overhead would be unnoticeable.

*However*, `call_via_ref` does two things: it constructs a `virtual_ptr<Node>`
from a `const Node&`, then it calls the method.
*However*, `method_via_ref` does two things: it constructs a
`virtual_ptr<Node>` from a `const Node&`, then it calls the method.

The construction of the `virtual_ptr` is the costly part. It performs a lookup
in a perfect hash table, indexed by pointers to `std::type_info`, to find the
Expand All @@ -85,7 +85,7 @@ If we already have a `virtual_ptr`:

[source,c++]
----
include::{examplesdir}/dispatch_open_method.cpp[tag=call_via_virtual_ptr]
include::{examplesdir}/dispatch_open_method.cpp[tag=method_via_virtual_ptr]
----

A method call compiles to:
Expand All @@ -96,7 +96,7 @@ mov rax, qword ptr [rip + postfix::fn+88]
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^]
link:https://godbolt.org/clientstate/eNrtU1tr2zAU%2FisHDYpL3UuchzL38tA2sJfFg%2FZtKUGx5VYgS0KSvQXj%2FfYd2U7ibN3WDLKXNYFEOt%2B56Zzvq0mqpGPSkRg%2B18QtNcMTMeoLCeFlLFWiLORv4EIr6c3h4DKlRY9mbJJxp8wWfO%2Bo83hNeIZ%2FI8QElU9txNGRd7WqNGmb4h2XqSgzBpdcWWcYLa5ncibxWKYOplgA6pkE%2FFTcuJIK%2BOaNwSHUzbadSwcVFaXH8C3WwRWcXWz7VIpnoLFQzr8G1mVx3Bc9AGW3w5oL30cb0EfP81KmjisZdH6%2BjwOQ%2BBvCz7n6rj18sqqIdp95JknThPD6KReaC%2FbHGa%2BH%2BsLA1zm8yWPReHTmAcEX1q%2F8Ec9K%2B9f5KznGB11h7OgcjpPIO%2BZcOGZsVxKZIvCES8K3k4wVmFGwjUXQBRN2cOcLQ83yFoeBxpwK28Zxw3CgFRt4YqMFPi%2BRYtkbm6Z5%2FGFae6ds9DrKLhQu9lRpJgvmnlV28qw10nf%2FnO7IWVoun6BtAsm37iKOV4TVzrSON0ly%2FzBPPk2mHycPH5K7oCdkCMHA9XJD6%2Btf0TpsJXS40UZXcl5xOjcs30UZK1F0bvuTRjRcXvQX0qiJ7Mu1o%2FZQ1bmP3kekeVPOcNrjN%2BXsrpxBql3T%2Fms1jYcLHf93avLf78akGxU%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