Files
carbon-lang/toolchain/parse/node_kind.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

231 lines
7.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_TOOLCHAIN_PARSE_NODE_KIND_H_
#define CARBON_TOOLCHAIN_PARSE_NODE_KIND_H_
#include <cstdint>
#include "common/enum_base.h"
#include "common/ostream.h"
#include "llvm/ADT/BitmaskEnum.h"
#include "toolchain/lex/token_kind.h"
namespace Carbon::Parse {
LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
// Represents a set of keyword modifiers, using a separate bit per modifier.
class NodeCategory : public Printable<NodeCategory> {
public:
// Provide values as an enum. This doesn't expose these as NodeCategory
// instances just due to the duplication of declarations that would cause.
//
// We expect this to grow, so are using a bigger size than needed.
// NOLINTNEXTLINE(performance-enum-size)
enum RawEnumType : uint32_t {
Decl = 1 << 0,
Expr = 1 << 1,
ImplAs = 1 << 2,
MemberExpr = 1 << 3,
MemberName = 1 << 4,
Modifier = 1 << 5,
Pattern = 1 << 6,
Statement = 1 << 7,
IntConst = 1 << 8,
Requirement = 1 << 9,
None = 0,
LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/Requirement)
};
// Support implicit conversion so that the difference with the member enum is
// opaque.
// NOLINTNEXTLINE(google-explicit-constructor)
constexpr NodeCategory(RawEnumType value) : value_(value) {}
// Returns true if there's a non-empty set intersection.
constexpr auto HasAnyOf(NodeCategory other) -> bool {
return value_ & other.value_;
}
// Returns the set inverse.
constexpr auto operator~() -> NodeCategory { return ~value_; }
auto operator==(const NodeCategory& other) const -> bool {
return value_ == other.value_;
}
auto Print(llvm::raw_ostream& out) const -> void;
private:
RawEnumType value_;
};
CARBON_DEFINE_RAW_ENUM_CLASS(NodeKind, uint8_t) {
#define CARBON_PARSE_NODE_KIND(Name) CARBON_RAW_ENUM_ENUMERATOR(Name)
#include "toolchain/parse/node_kind.def"
};
// A class wrapping an enumeration of the different kinds of nodes in the parse
// tree.
//
// In order to allow the children of a node to be determined without relying on
// the subtree size field in the parse node, each node kind must have one of:
//
// - a bracketing node kind, which is always the kind for the first child, and
// is never the kind of any other child, or
// - a fixed child count,
//
// or both. This is required even for nodes for which `Tree::node_has_errors`
// returns `true`.
class NodeKind : public CARBON_ENUM_BASE(NodeKind) {
public:
#define CARBON_PARSE_NODE_KIND(Name) CARBON_ENUM_CONSTANT_DECL(Name)
#include "toolchain/parse/node_kind.def"
// Validates that a `node_kind` parser node can be generated for a
// `lex_token_kind` lexer token.
auto CheckMatchesTokenKind(Lex::TokenKind lex_token_kind, bool has_error)
-> void;
// Returns true if the node is bracketed.
auto has_bracket() const -> bool;
// Returns the bracketing node kind for the current node kind. Requires that
// has_bracket is true.
auto bracket() const -> NodeKind;
// Returns true if the node is has a fixed child count.
auto has_child_count() const -> bool;
// Returns the number of children that the node must have, often 0. Requires
// that has_child_count is true.
auto child_count() const -> int32_t;
// Returns which categories this node kind is in.
auto category() const -> NodeCategory;
// Number of different kinds, usable in a constexpr context.
static const int ValidCount;
using EnumBase::AsInt;
using EnumBase::Make;
class Definition;
struct DefinitionArgs;
// Provides a definition for this parse node kind. Should only be called
// once, to construct the kind as part of defining it in `typed_nodes.h`.
constexpr auto Define(DefinitionArgs args) const -> Definition;
private:
// Looks up the definition for this instruction kind.
auto definition() const -> const Definition&;
};
#define CARBON_PARSE_NODE_KIND(Name) \
CARBON_ENUM_CONSTANT_DEFINITION(NodeKind, Name)
#include "toolchain/parse/node_kind.def"
constexpr int NodeKind::ValidCount = 0
#define CARBON_PARSE_NODE_KIND(Name) +1
#include "toolchain/parse/node_kind.def"
;
static_assert(
NodeKind::ValidCount != 0,
"The above `constexpr` definition of `ValidCount` makes it available in "
"a `constexpr` context despite being declared as merely `const`. We use it "
"in a static assert here to ensure that.");
// We expect the parse node kind to fit compactly into 8 bits.
static_assert(sizeof(NodeKind) == 1, "Kind objects include padding!");
// Optional arguments that can be supplied when defining a node kind. At least
// one of `bracketed_by` and `child_count` is required.
struct NodeKind::DefinitionArgs {
// The category for the node.
NodeCategory category = NodeCategory::None;
// The kind of the bracketing node, which is the first child.
std::optional<NodeKind> bracketed_by = std::nullopt;
// The fixed child count.
int32_t child_count = -1;
};
// A definition of a parse node kind. This is a NodeKind value, plus
// ancillary data such as the name to use for the node kind in LLVM IR. These
// are not copyable, and only one instance of this type is expected to exist per
// parse node kind, specifically `TypedNode::Kind`. Use `NodeKind` instead as a
// thin wrapper around a parse node kind index.
class NodeKind::Definition : public NodeKind {
public:
// Not copyable.
Definition(const Definition&) = delete;
auto operator=(const Definition&) -> Definition& = delete;
// Returns true if the node is bracketed.
constexpr auto has_bracket() const -> bool { return bracket_ != *this; }
// Returns the bracketing node kind for the current node kind. Requires that
// has_bracket is true.
constexpr auto bracket() const -> NodeKind {
CARBON_CHECK(has_bracket(), "{0}", *this);
return bracket_;
}
// Returns true if the node is has a fixed child count.
constexpr auto has_child_count() const -> bool { return child_count_ >= 0; }
// Returns the number of children that the node must have, often 0. Requires
// that has_child_count is true.
constexpr auto child_count() const -> int32_t {
CARBON_CHECK(has_child_count(), "{0}", *this);
return child_count_;
}
// Returns which categories this node kind is in.
constexpr auto category() const -> NodeCategory { return category_; }
private:
friend class NodeKind;
// This is factored out and non-constexpr to improve the compile-time error
// message if the check below fails.
auto MustSpecifyEitherBracketingNodeOrChildCount() {
CARBON_FATAL("Must specify either bracketing node or fixed child count.");
}
constexpr explicit Definition(NodeKind kind, DefinitionArgs args)
: NodeKind(kind),
category_(args.category),
bracket_(args.bracketed_by.value_or(kind)),
child_count_(args.child_count) {
if (!has_bracket() && !has_child_count()) {
MustSpecifyEitherBracketingNodeOrChildCount();
}
}
NodeCategory category_ = NodeCategory::None;
// Nodes are never self-bracketed, so we use *this to indicate that the node
// is not bracketed.
NodeKind bracket_ = *this;
int32_t child_count_ = -1;
};
constexpr auto NodeKind::Define(DefinitionArgs args) const -> Definition {
return Definition(*this, args);
}
// HasKindMember<T> is true if T has a `static const NodeKind::Definition Kind`
// member.
template <typename T, typename KindType = const NodeKind::Definition*>
inline constexpr bool HasKindMember = false;
template <typename T>
inline constexpr bool HasKindMember<T, decltype(&T::Kind)> = true;
} // namespace Carbon::Parse
#endif // CARBON_TOOLCHAIN_PARSE_NODE_KIND_H_