Files
carbon-lang/explorer/ast/clone_context.h
T
4845f40dff Switch CARBON_CHECK to a format string API (#4285)
This switches `DCHECK` and `FATAL` as well.

The goal is to reduce the code size impact of these assertions so that
we can keep more of them enabled. Currently, the largest cost I see from
`CHECK` is not the actual check or the cold code itself, but actually
the failure to inline trivial functions due to the presence of the cold
code. This means that our goal isn't to reduce apparent code size in the
final binary but the LLVM IR cost assessed for these routines in the
inliner, which closely correlates with code size but is a bit different.

As discussed in #4283, experimentation shows that a single function call
with a minimal number of arguments is the lowest cost model for these.
This is easily achieved with a format-string API that internally uses
`llvm::formatv`. This PR is essentially the `CHECK` version of #4283.

However, the check macros are substantially harder to make work with
both format strings and streaming because they also take a condition.
Also, unexpectedly, I was very successful at devising a regular
expression based automated rewrite from the streaming to the format
string form with only low 10s of manual fixes. This includes compacting
strings broken up across lines, etc. Given how well that went, I've
prepared this PR which just directly switches to the format string API
and migrate everything to use it.

One nice side-effect is that the format string approach ends up greatly
simplifying the implementation here as well.

This is ... *shockingly* effective. Parsing speeds up by more than 3%
with just this change. And checking speeds up by **8%** with this change
alone:
```
BM_CompileAPIFileDenseDecls<Phase::Parse>/256      86.3µs ± 1%  82.9µs ± 1%  -3.94%  (p=0.000 n=17+19)
BM_CompileAPIFileDenseDecls<Phase::Parse>/1024      431µs ± 1%   415µs ± 1%  -3.76%  (p=0.000 n=18+19)
BM_CompileAPIFileDenseDecls<Phase::Parse>/4096     1.77ms ± 1%  1.71ms ± 1%  -3.18%  (p=0.000 n=18+19)
BM_CompileAPIFileDenseDecls<Phase::Parse>/16384    7.44ms ± 1%  7.17ms ± 2%  -3.56%  (p=0.000 n=18+20)
BM_CompileAPIFileDenseDecls<Phase::Parse>/65536    30.7ms ± 1%  29.7ms ± 1%  -3.15%  (p=0.000 n=18+20)
BM_CompileAPIFileDenseDecls<Phase::Parse>/262144    131ms ± 1%   127ms ± 1%  -2.81%  (p=0.000 n=18+18)
BM_CompileAPIFileDenseDecls<Phase::Check>/256       878µs ± 2%   800µs ± 1%  -8.91%  (p=0.000 n=19+20)
BM_CompileAPIFileDenseDecls<Phase::Check>/1024     1.88ms ± 2%  1.72ms ± 1%  -8.56%  (p=0.000 n=19+20)
BM_CompileAPIFileDenseDecls<Phase::Check>/4096     5.78ms ± 2%  5.28ms ± 1%  -8.70%  (p=0.000 n=20+18)
BM_CompileAPIFileDenseDecls<Phase::Check>/16384    21.9ms ± 1%  20.1ms ± 1%  -8.02%  (p=0.000 n=18+20)
BM_CompileAPIFileDenseDecls<Phase::Check>/65536    90.4ms ± 2%  83.1ms ± 1%  -8.04%  (p=0.000 n=19+20)
BM_CompileAPIFileDenseDecls<Phase::Check>/262144    381ms ± 2%   352ms ± 1%  -7.79%  (p=0.000 n=19+19)
```

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
2024-09-12 16:42:08 +00:00

168 lines
5.6 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CARBON_EXPLORER_AST_CLONE_CONTEXT_H_
#define CARBON_EXPLORER_AST_CLONE_CONTEXT_H_
#include <optional>
#include <type_traits>
#include <vector>
#include "common/check.h"
#include "explorer/ast/ast_rtti.h"
#include "explorer/base/arena.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/Casting.h"
namespace Carbon {
class AstNode;
class Element;
class Value;
// A context for performing a deep copy of some fragment of the AST.
//
// This class carries the state necessary to make the copy, including ensuring
// that each node is cloned only once and mapping from old nodes to new ones.
class CloneContext {
public:
explicit CloneContext(Nonnull<Arena*> arena) : arena_(arena) {}
CloneContext(const CloneContext&) = delete;
auto operator=(const CloneContext&) -> const CloneContext& = delete;
// Clone an AST element.
template <typename T>
auto Clone(Nonnull<const T*> node) -> Nonnull<T*> {
if constexpr (std::is_convertible_v<const T*, const AstNode*>) {
const AstNode* base_node = node;
// Note, we can't use `llvm::cast<T>` here because we might not have
// finished cloning `base_node` yet and its kind might not be set. This
// happens when there is a pointer cycle in the AST.
return static_cast<T*>(CloneBase(base_node));
} else if constexpr (std::is_convertible_v<const T*, const Value*>) {
const Value* base_value = node;
return static_cast<T*>(CloneBase(base_value));
} else {
static_assert(std::is_convertible_v<const T*, const Element*>,
"unknown pointer type to clone");
const Element* base_elem = node;
return static_cast<T*>(CloneBase(base_elem));
}
}
// Clone anything with a clone constructor, that is, a constructor of the
// form:
//
// explicit MyType(CloneContext&, const MyType&)
//
// Clone constructors should call Clone on their owned elements to form a new
// value. Pointers returned by Clone should not be inspected by the clone
// constructor, as the pointee is not necessarily fully initialized until the
// overall cloning process completes.
//
// Clone constructors should call Remap on values that they do not own, such
// as for the declaration named by an IdentifierExpression.
template <typename T>
auto Clone(const T& other)
-> std::enable_if_t<std::is_constructible_v<T, CloneContext&, const T&>,
T> {
return T(*this, other);
}
template <typename T>
auto Clone(std::optional<T> node) -> std::optional<T> {
if (node) {
return Clone(*node);
}
return std::nullopt;
}
template <typename T>
auto Clone(const std::vector<T>& nodes) -> std::vector<T> {
std::vector<T> result;
result.reserve(nodes.size());
for (const auto& node : nodes) {
result.push_back(Clone(node));
}
return result;
}
// Find the new or existing node corresponding to the given node. This should
// be used when a cloned node has a non-owning reference to another node,
// that might refer to something being cloned or might refer to the original
// object. The returned node might not be fully constructed and should not be
// inspected.
template <typename T>
auto Remap(Nonnull<T*> node) -> Nonnull<T*> {
// Note, we can't use `llvm::cast<T>` here because we might not have
// finished cloning `base_node` yet and its kind might not be set. This
// happens when there is a pointer cycle in the AST.
T* cloned = static_cast<T*>(nodes_[node]);
return cloned ? cloned : node;
}
// It's safe to remap a `const` object by remapping the non-const version and
// adding back the `const`.
template <typename T>
auto Remap(Nonnull<const T*> node) -> Nonnull<const T*> {
return Remap(const_cast<T*>(node));
}
template <typename T>
auto Remap(std::optional<T> node) -> std::optional<T> {
if (node) {
return Remap(*node);
}
return std::nullopt;
}
template <typename T>
auto Remap(const std::vector<T>& nodes) -> std::vector<T> {
std::vector<T> result;
result.reserve(nodes.size());
for (const auto& node : nodes) {
result.push_back(Remap(node));
}
return result;
}
template <typename T>
auto GetExistingClone(Nonnull<const T*> node) -> Nonnull<T*> {
AstNode* cloned = nodes_.lookup(node);
CARBON_CHECK(cloned, "expected node to be cloned");
return llvm::cast<T>(cloned);
}
private:
// A value transform that remaps or clones AST elements referred to by the
// value being transformed.
class CloneValueTransform;
// Clone the given node, and remember the mapping from the original to the
// new node for remapping.
auto CloneBase(Nonnull<const AstNode*> node) -> Nonnull<AstNode*>;
// Clone the given value, replacing references to cloned local declarations
// with references to the copies.
auto CloneBase(Nonnull<const Value*> value) -> Nonnull<Value*>;
// Clone the given element reference.
auto CloneBase(Nonnull<const Element*> elem) -> Nonnull<Element*>;
// Clone the given node if it's not already been cloned. This should be used
// very sparingly, in cases where ownership is unclear.
void MaybeCloneBase(Nonnull<const AstNode*> node);
// Arena to allocate new nodes within.
Nonnull<Arena*> arena_;
// Mapping from old nodes to new nodes.
llvm::DenseMap<const AstNode*, AstNode*> nodes_;
};
} // namespace Carbon
#endif // CARBON_EXPLORER_AST_CLONE_CONTEXT_H_