mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
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>
436 lines
16 KiB
C++
436 lines
16 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
|
|
|
|
#include "toolchain/lex/token_kind.h"
|
|
#include "toolchain/parse/context.h"
|
|
#include "toolchain/parse/handle.h"
|
|
|
|
namespace Carbon::Parse {
|
|
|
|
static auto DiagnoseStatementOperatorAsSubExpr(Context& context) -> void {
|
|
CARBON_DIAGNOSTIC(StatementOperatorAsSubExpr, Error,
|
|
"Operator `{0}` can only be used as a complete statement.",
|
|
Lex::TokenKind);
|
|
context.emitter().Emit(*context.position(), StatementOperatorAsSubExpr,
|
|
context.PositionKind());
|
|
}
|
|
|
|
auto HandleExpr(Context& context) -> void {
|
|
auto state = context.PopState();
|
|
|
|
// Check for a prefix operator.
|
|
if (auto operator_precedence =
|
|
PrecedenceGroup::ForLeading(context.PositionKind())) {
|
|
if (PrecedenceGroup::GetPriority(state.ambient_precedence,
|
|
*operator_precedence) !=
|
|
OperatorPriority::RightFirst) {
|
|
// The precedence rules don't permit this prefix operator in this
|
|
// context. Diagnose this, but carry on and parse it anyway.
|
|
if (PrecedenceGroup::GetPriority(PrecedenceGroup::ForTopLevelExpr(),
|
|
*operator_precedence) ==
|
|
OperatorPriority::RightFirst) {
|
|
CARBON_DIAGNOSTIC(
|
|
UnaryOperatorRequiresParentheses, Error,
|
|
"Parentheses are required around this unary `{0}` operator.",
|
|
Lex::TokenKind);
|
|
context.emitter().Emit(*context.position(),
|
|
UnaryOperatorRequiresParentheses,
|
|
context.PositionKind());
|
|
} else {
|
|
// This operator wouldn't be allowed even if parenthesized.
|
|
DiagnoseStatementOperatorAsSubExpr(context);
|
|
}
|
|
} else {
|
|
// Check that this operator follows the proper whitespace rules.
|
|
context.DiagnoseOperatorFixity(Context::OperatorFixity::Prefix);
|
|
}
|
|
|
|
if (context.PositionIs(Lex::TokenKind::If)) {
|
|
context.PushState(State::IfExprFinish);
|
|
context.PushState(State::IfExprFinishCondition);
|
|
} else {
|
|
context.PushStateForExprLoop(State::ExprLoopForPrefixOperator,
|
|
state.ambient_precedence,
|
|
*operator_precedence);
|
|
}
|
|
|
|
context.ConsumeAndDiscard();
|
|
context.PushStateForExpr(*operator_precedence);
|
|
} else {
|
|
context.PushStateForExprLoop(State::ExprLoop, state.ambient_precedence,
|
|
PrecedenceGroup::ForPostfixExpr());
|
|
context.PushState(State::ExprInPostfix);
|
|
}
|
|
}
|
|
|
|
auto HandleExprInPostfix(Context& context) -> void {
|
|
auto state = context.PopState();
|
|
|
|
// Continue to the loop state.
|
|
state.state = State::ExprInPostfixLoop;
|
|
|
|
// Parses a primary expression, which is either a terminal portion of an
|
|
// expression tree, such as an identifier or literal, or a parenthesized
|
|
// expression.
|
|
switch (context.PositionKind()) {
|
|
case Lex::TokenKind::Identifier: {
|
|
context.AddLeafNode(NodeKind::IdentifierNameExpr, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::False: {
|
|
context.AddLeafNode(NodeKind::BoolLiteralFalse, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::True: {
|
|
context.AddLeafNode(NodeKind::BoolLiteralTrue, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::IntLiteral: {
|
|
context.AddLeafNode(NodeKind::IntLiteral, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::RealLiteral: {
|
|
context.AddLeafNode(NodeKind::RealLiteral, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::StringLiteral: {
|
|
context.AddLeafNode(NodeKind::StringLiteral, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::Bool: {
|
|
context.AddLeafNode(NodeKind::BoolTypeLiteral, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::IntTypeLiteral: {
|
|
context.AddLeafNode(NodeKind::IntTypeLiteral, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::UnsignedIntTypeLiteral: {
|
|
context.AddLeafNode(NodeKind::UnsignedIntTypeLiteral, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::FloatTypeLiteral: {
|
|
context.AddLeafNode(NodeKind::FloatTypeLiteral, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::StringTypeLiteral: {
|
|
context.AddLeafNode(NodeKind::StringTypeLiteral, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::Type: {
|
|
context.AddLeafNode(NodeKind::TypeTypeLiteral, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::Auto: {
|
|
context.AddLeafNode(NodeKind::AutoTypeLiteral, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::OpenCurlyBrace: {
|
|
context.PushState(state);
|
|
context.PushState(State::BraceExpr);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::OpenParen: {
|
|
context.PushState(state);
|
|
context.PushState(State::ParenExpr);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::OpenSquareBracket: {
|
|
context.PushState(state);
|
|
context.PushState(State::ArrayExpr);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::Package: {
|
|
context.AddLeafNode(NodeKind::PackageExpr, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::SelfValueIdentifier: {
|
|
context.AddLeafNode(NodeKind::SelfValueNameExpr, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::SelfTypeIdentifier: {
|
|
context.AddLeafNode(NodeKind::SelfTypeNameExpr, context.Consume());
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::Period: {
|
|
// For periods, we look at the next token to form a designator like
|
|
// `.Member` or `.Self`.
|
|
auto period = context.Consume();
|
|
if (context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::Identifier,
|
|
NodeKind::IdentifierName)) {
|
|
// OK, `.` identifier.
|
|
} else if (context.ConsumeAndAddLeafNodeIf(
|
|
Lex::TokenKind::SelfTypeIdentifier,
|
|
NodeKind::SelfTypeName)) {
|
|
// OK, `.Self`.
|
|
} else {
|
|
CARBON_DIAGNOSTIC(ExpectedIdentifierOrSelfAfterPeriod, Error,
|
|
"Expected identifier or `Self` after `.`.");
|
|
context.emitter().Emit(*context.position(),
|
|
ExpectedIdentifierOrSelfAfterPeriod);
|
|
// Only consume if it is a number or word.
|
|
if (context.PositionKind().is_keyword()) {
|
|
context.AddLeafNode(NodeKind::IdentifierName, context.Consume(),
|
|
/*has_error=*/true);
|
|
} else if (context.PositionIs(Lex::TokenKind::IntLiteral)) {
|
|
context.AddLeafNode(NodeKind::InvalidParse, context.Consume(),
|
|
/*has_error=*/true);
|
|
} else {
|
|
context.AddLeafNode(NodeKind::InvalidParse, *context.position(),
|
|
/*has_error=*/true);
|
|
// Indicate the error to the parent state so that it can avoid
|
|
// producing more errors. We only do this on this path where we don't
|
|
// consume the token after the period, where we expect further errors
|
|
// since we likely haven't recovered.
|
|
context.ReturnErrorOnState();
|
|
}
|
|
state.has_error = true;
|
|
}
|
|
context.AddNode(NodeKind::DesignatorExpr, period, state.has_error);
|
|
context.PushState(state);
|
|
break;
|
|
}
|
|
default: {
|
|
// Add a node to keep the parse tree balanced.
|
|
context.AddLeafNode(NodeKind::InvalidParse, *context.position(),
|
|
/*has_error=*/true);
|
|
CARBON_DIAGNOSTIC(ExpectedExpr, Error, "Expected expression.");
|
|
context.emitter().Emit(*context.position(), ExpectedExpr);
|
|
context.ReturnErrorOnState();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto HandleExprInPostfixLoop(Context& context) -> void {
|
|
// This is a cyclic state that repeats, so this state is typically pushed back
|
|
// on.
|
|
auto state = context.PopState();
|
|
state.token = *context.position();
|
|
switch (context.PositionKind()) {
|
|
case Lex::TokenKind::Period: {
|
|
context.PushState(state);
|
|
context.PushState(state, State::PeriodAsExpr);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::MinusGreater: {
|
|
context.PushState(state);
|
|
context.PushState(state, State::ArrowExpr);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::OpenParen: {
|
|
context.PushState(state);
|
|
context.PushState(state, State::CallExpr);
|
|
break;
|
|
}
|
|
case Lex::TokenKind::OpenSquareBracket: {
|
|
context.PushState(state);
|
|
context.PushState(state, State::IndexExpr);
|
|
break;
|
|
}
|
|
default: {
|
|
if (state.has_error) {
|
|
context.ReturnErrorOnState();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto HandleExprLoop(Context& context) -> void {
|
|
auto state = context.PopState();
|
|
|
|
auto operator_kind = context.PositionKind();
|
|
auto trailing_operator = PrecedenceGroup::ForTrailing(
|
|
operator_kind, context.IsTrailingOperatorInfix());
|
|
if (!trailing_operator) {
|
|
if (state.has_error) {
|
|
context.ReturnErrorOnState();
|
|
}
|
|
return;
|
|
}
|
|
auto [operator_precedence, is_binary] = *trailing_operator;
|
|
|
|
// TODO: If this operator is ambiguous with either the ambient precedence
|
|
// or the LHS precedence, and there's a variant with a different fixity
|
|
// that would work, use that one instead for error recovery.
|
|
if (PrecedenceGroup::GetPriority(state.ambient_precedence,
|
|
operator_precedence) !=
|
|
OperatorPriority::RightFirst) {
|
|
// The precedence rules don't permit this operator in this context. Try
|
|
// again in the enclosing expression context.
|
|
if (state.has_error) {
|
|
context.ReturnErrorOnState();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (PrecedenceGroup::GetPriority(state.lhs_precedence, operator_precedence) !=
|
|
OperatorPriority::LeftFirst) {
|
|
// Either the LHS operator and this operator are ambiguous, or the
|
|
// LHS operator is a unary operator that can't be nested within
|
|
// this operator. Either way, parentheses are required.
|
|
if (PrecedenceGroup::GetPriority(PrecedenceGroup::ForTopLevelExpr(),
|
|
operator_precedence) ==
|
|
OperatorPriority::RightFirst) {
|
|
CARBON_DIAGNOSTIC(
|
|
OperatorRequiresParentheses, Error,
|
|
"Parentheses are required to disambiguate operator precedence.");
|
|
context.emitter().Emit(*context.position(), OperatorRequiresParentheses);
|
|
} else {
|
|
// This operator wouldn't be allowed even if parenthesized.
|
|
DiagnoseStatementOperatorAsSubExpr(context);
|
|
}
|
|
state.has_error = true;
|
|
} else {
|
|
context.DiagnoseOperatorFixity(is_binary
|
|
? Context::OperatorFixity::Infix
|
|
: Context::OperatorFixity::Postfix);
|
|
}
|
|
|
|
state.token = context.Consume();
|
|
state.lhs_precedence = operator_precedence;
|
|
|
|
if (is_binary) {
|
|
switch (operator_kind) {
|
|
// For `and` and `or`, wrap the first operand in a virtual parse tree
|
|
// node so that checking can insert control flow here.
|
|
case Lex::TokenKind::And:
|
|
context.AddNode(NodeKind::ShortCircuitOperandAnd, state.token,
|
|
state.has_error);
|
|
state.state = State::ExprLoopForShortCircuitOperatorAsAnd;
|
|
break;
|
|
case Lex::TokenKind::Or:
|
|
context.AddNode(NodeKind::ShortCircuitOperandOr, state.token,
|
|
state.has_error);
|
|
state.state = State::ExprLoopForShortCircuitOperatorAsOr;
|
|
break;
|
|
|
|
// `where` also needs a virtual parse tree node, and parses its right
|
|
// argument in a mode where it can handle requirement operators like
|
|
// `impls` and `=`.
|
|
case Lex::TokenKind::Where:
|
|
context.AddNode(NodeKind::WhereOperand, state.token, state.has_error);
|
|
context.PushState(state, State::WhereFinish);
|
|
context.PushState(State::RequirementBegin);
|
|
return;
|
|
|
|
default:
|
|
state.state = State::ExprLoopForInfixOperator;
|
|
break;
|
|
}
|
|
|
|
context.PushState(state);
|
|
context.PushStateForExpr(operator_precedence);
|
|
} else {
|
|
NodeKind node_kind;
|
|
switch (operator_kind) {
|
|
#define CARBON_PARSE_NODE_KIND(...)
|
|
#define CARBON_PARSE_NODE_KIND_POSTFIX_OPERATOR(Name, ...) \
|
|
case Lex::TokenKind::Name: \
|
|
node_kind = NodeKind::PostfixOperator##Name; \
|
|
break;
|
|
#include "toolchain/parse/node_kind.def"
|
|
|
|
default:
|
|
CARBON_FATAL("Unexpected token kind for postfix operator: {0}",
|
|
operator_kind);
|
|
}
|
|
|
|
context.AddNode(node_kind, state.token, state.has_error);
|
|
state.has_error = false;
|
|
context.PushState(state);
|
|
}
|
|
}
|
|
|
|
// Adds the operator node and returns the main expression loop.
|
|
static auto HandleExprLoopForOperator(Context& context,
|
|
Context::StateStackEntry state,
|
|
NodeKind node_kind) -> void {
|
|
context.AddNode(node_kind, state.token, state.has_error);
|
|
state.has_error = false;
|
|
context.PushState(state, State::ExprLoop);
|
|
}
|
|
|
|
auto HandleExprLoopForInfixOperator(Context& context) -> void {
|
|
auto state = context.PopState();
|
|
|
|
switch (auto token_kind = context.tokens().GetKind(state.token)) {
|
|
#define CARBON_PARSE_NODE_KIND(...)
|
|
#define CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Name, ...) \
|
|
case Lex::TokenKind::Name: \
|
|
HandleExprLoopForOperator(context, state, NodeKind::InfixOperator##Name); \
|
|
break;
|
|
#include "toolchain/parse/node_kind.def"
|
|
|
|
default:
|
|
CARBON_FATAL("Unexpected token kind for infix operator: {0}", token_kind);
|
|
}
|
|
}
|
|
|
|
auto HandleExprLoopForPrefixOperator(Context& context) -> void {
|
|
auto state = context.PopState();
|
|
|
|
switch (auto token_kind = context.tokens().GetKind(state.token)) {
|
|
#define CARBON_PARSE_NODE_KIND(...)
|
|
#define CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Name, ...) \
|
|
case Lex::TokenKind::Name: \
|
|
HandleExprLoopForOperator(context, state, NodeKind::PrefixOperator##Name); \
|
|
break;
|
|
#include "toolchain/parse/node_kind.def"
|
|
|
|
default:
|
|
CARBON_FATAL("Unexpected token kind for prefix operator: {0}",
|
|
token_kind);
|
|
}
|
|
}
|
|
|
|
auto HandleExprLoopForShortCircuitOperatorAsAnd(Context& context) -> void {
|
|
auto state = context.PopState();
|
|
|
|
HandleExprLoopForOperator(context, state, NodeKind::ShortCircuitOperatorAnd);
|
|
}
|
|
|
|
auto HandleExprLoopForShortCircuitOperatorAsOr(Context& context) -> void {
|
|
auto state = context.PopState();
|
|
|
|
HandleExprLoopForOperator(context, state, NodeKind::ShortCircuitOperatorOr);
|
|
}
|
|
|
|
auto HandleExprStatementFinish(Context& context) -> void {
|
|
auto state = context.PopState();
|
|
|
|
if (auto semi = context.ConsumeIf(Lex::TokenKind::Semi)) {
|
|
context.AddNode(NodeKind::ExprStatement, *semi, state.has_error);
|
|
return;
|
|
}
|
|
|
|
if (!state.has_error) {
|
|
CARBON_DIAGNOSTIC(ExpectedExprSemi, Error,
|
|
"Expected `;` after expression statement.");
|
|
context.emitter().Emit(*context.position(), ExpectedExprSemi);
|
|
}
|
|
|
|
context.AddNode(NodeKind::ExprStatement,
|
|
context.SkipPastLikelyEnd(state.token), /*has_error=*/true);
|
|
}
|
|
|
|
} // namespace Carbon::Parse
|