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>
231 lines
9.5 KiB
C++
231 lines
9.5 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/check/impl.h"
|
|
|
|
#include "toolchain/base/kind_switch.h"
|
|
#include "toolchain/check/context.h"
|
|
#include "toolchain/check/function.h"
|
|
#include "toolchain/check/generic.h"
|
|
#include "toolchain/check/import_ref.h"
|
|
#include "toolchain/diagnostics/diagnostic_emitter.h"
|
|
#include "toolchain/sem_ir/generic.h"
|
|
#include "toolchain/sem_ir/ids.h"
|
|
#include "toolchain/sem_ir/impl.h"
|
|
#include "toolchain/sem_ir/inst.h"
|
|
#include "toolchain/sem_ir/typed_insts.h"
|
|
|
|
namespace Carbon::Check {
|
|
|
|
// Adds the location of the associated function to a diagnostic.
|
|
static auto NoteAssociatedFunction(Context& context,
|
|
Context::DiagnosticBuilder& builder,
|
|
SemIR::FunctionId function_id) -> void {
|
|
CARBON_DIAGNOSTIC(ImplAssociatedFunctionHere, Note,
|
|
"Associated function {0} declared here.", SemIR::NameId);
|
|
const auto& function = context.functions().Get(function_id);
|
|
builder.Note(function.latest_decl_id(), ImplAssociatedFunctionHere,
|
|
function.name_id);
|
|
}
|
|
|
|
// Gets the self specific of a generic declaration that is an interface member,
|
|
// given a specific for an enclosing generic, plus a type to use as `Self`.
|
|
static auto GetSelfSpecificForInterfaceMemberWithSelfType(
|
|
Context& context, SemIR::SpecificId enclosing_specific_id,
|
|
SemIR::GenericId generic_id, SemIR::TypeId self_type_id)
|
|
-> SemIR::SpecificId {
|
|
const auto& generic = context.generics().Get(generic_id);
|
|
auto bindings = context.inst_blocks().Get(generic.bindings_id);
|
|
|
|
llvm::SmallVector<SemIR::InstId> arg_ids;
|
|
arg_ids.reserve(bindings.size());
|
|
|
|
// Start with the enclosing arguments.
|
|
if (enclosing_specific_id.is_valid()) {
|
|
auto enclosing_specific_args_id =
|
|
context.specifics().Get(enclosing_specific_id).args_id;
|
|
auto enclosing_specific_args =
|
|
context.inst_blocks().Get(enclosing_specific_args_id);
|
|
arg_ids.assign(enclosing_specific_args.begin(),
|
|
enclosing_specific_args.end());
|
|
}
|
|
|
|
// Add the `Self` argument.
|
|
CARBON_CHECK(
|
|
context.entity_names()
|
|
.Get(context.insts()
|
|
.GetAs<SemIR::BindSymbolicName>(bindings[arg_ids.size()])
|
|
.entity_name_id)
|
|
.name_id == SemIR::NameId::SelfType,
|
|
"Expected a Self binding, found {0}",
|
|
context.insts().Get(bindings[arg_ids.size()]));
|
|
arg_ids.push_back(context.types().GetInstId(self_type_id));
|
|
|
|
// Take any trailing argument values from the self specific.
|
|
// TODO: If these refer to outer arguments, for example in their types, we may
|
|
// need to perform extra substitutions here.
|
|
auto self_specific_args = context.inst_blocks().Get(
|
|
context.specifics().Get(generic.self_specific_id).args_id);
|
|
for (auto arg_id : self_specific_args.drop_front(arg_ids.size())) {
|
|
arg_ids.push_back(context.constant_values().GetConstantInstId(arg_id));
|
|
}
|
|
|
|
auto args_id = context.inst_blocks().AddCanonical(arg_ids);
|
|
return MakeSpecific(context, generic_id, args_id);
|
|
}
|
|
|
|
// Checks that `impl_function_id` is a valid implementation of the function
|
|
// described in the interface as `interface_function_id`. Returns the value to
|
|
// put into the corresponding slot in the witness table, which can be
|
|
// `BuiltinError` if the function is not usable.
|
|
static auto CheckAssociatedFunctionImplementation(
|
|
Context& context, SemIR::FunctionType interface_function_type,
|
|
SemIR::InstId impl_decl_id, SemIR::TypeId self_type_id) -> SemIR::InstId {
|
|
auto impl_function_decl =
|
|
context.insts().TryGetAs<SemIR::FunctionDecl>(impl_decl_id);
|
|
if (!impl_function_decl) {
|
|
CARBON_DIAGNOSTIC(ImplFunctionWithNonFunction, Error,
|
|
"Associated function {0} implemented by non-function.",
|
|
SemIR::NameId);
|
|
auto builder = context.emitter().Build(
|
|
impl_decl_id, ImplFunctionWithNonFunction,
|
|
context.functions().Get(interface_function_type.function_id).name_id);
|
|
NoteAssociatedFunction(context, builder,
|
|
interface_function_type.function_id);
|
|
builder.Emit();
|
|
|
|
return SemIR::InstId::BuiltinError;
|
|
}
|
|
|
|
// Map from the specific for the function type to the specific for the
|
|
// function signature. The function signature may have additional generic
|
|
// parameters.
|
|
auto interface_function_specific_id =
|
|
GetSelfSpecificForInterfaceMemberWithSelfType(
|
|
context, interface_function_type.specific_id,
|
|
context.functions()
|
|
.Get(interface_function_type.function_id)
|
|
.generic_id,
|
|
self_type_id);
|
|
|
|
// TODO: This should be a semantic check rather than a syntactic one. The
|
|
// functions should be allowed to have different signatures as long as we can
|
|
// synthesize a suitable thunk.
|
|
if (!CheckFunctionTypeMatches(
|
|
context, context.functions().Get(impl_function_decl->function_id),
|
|
context.functions().Get(interface_function_type.function_id),
|
|
interface_function_specific_id,
|
|
/*check_syntax=*/false)) {
|
|
return SemIR::InstId::BuiltinError;
|
|
}
|
|
return impl_decl_id;
|
|
}
|
|
|
|
// Builds a witness that the specified impl implements the given interface.
|
|
static auto BuildInterfaceWitness(
|
|
Context& context, const SemIR::Impl& impl, SemIR::TypeId interface_type_id,
|
|
SemIR::InterfaceType interface_type,
|
|
llvm::SmallVectorImpl<SemIR::InstId>& used_decl_ids) -> SemIR::InstId {
|
|
const auto& interface = context.interfaces().Get(interface_type.interface_id);
|
|
if (!context.TryToDefineType(interface_type_id, [&] {
|
|
CARBON_DIAGNOSTIC(ImplOfUndefinedInterface, Error,
|
|
"Implementation of undefined interface {0}.",
|
|
SemIR::NameId);
|
|
return context.emitter().Build(
|
|
impl.definition_id, ImplOfUndefinedInterface, interface.name_id);
|
|
})) {
|
|
return SemIR::InstId::BuiltinError;
|
|
}
|
|
|
|
auto& impl_scope = context.name_scopes().Get(impl.scope_id);
|
|
|
|
llvm::SmallVector<SemIR::InstId> table;
|
|
auto assoc_entities =
|
|
context.inst_blocks().Get(interface.associated_entities_id);
|
|
table.reserve(assoc_entities.size());
|
|
|
|
for (auto decl_id : assoc_entities) {
|
|
LoadImportRef(context, decl_id);
|
|
decl_id =
|
|
context.constant_values().GetInstId(SemIR::GetConstantValueInSpecific(
|
|
context.sem_ir(), interface_type.specific_id, decl_id));
|
|
CARBON_CHECK(decl_id.is_valid(), "Non-constant associated entity");
|
|
auto decl = context.insts().Get(decl_id);
|
|
CARBON_KIND_SWITCH(decl) {
|
|
case CARBON_KIND(SemIR::StructValue struct_value): {
|
|
if (struct_value.type_id == SemIR::TypeId::Error) {
|
|
return SemIR::InstId::BuiltinError;
|
|
}
|
|
auto type_inst = context.types().GetAsInst(struct_value.type_id);
|
|
auto fn_type = type_inst.TryAs<SemIR::FunctionType>();
|
|
if (!fn_type) {
|
|
CARBON_FATAL("Unexpected type: {0}", type_inst);
|
|
}
|
|
auto& fn = context.functions().Get(fn_type->function_id);
|
|
auto [impl_decl_id, _] = context.LookupNameInExactScope(
|
|
decl_id, fn.name_id, impl.scope_id, impl_scope);
|
|
if (impl_decl_id.is_valid()) {
|
|
used_decl_ids.push_back(impl_decl_id);
|
|
table.push_back(CheckAssociatedFunctionImplementation(
|
|
context, *fn_type, impl_decl_id, impl.self_id));
|
|
} else {
|
|
CARBON_DIAGNOSTIC(
|
|
ImplMissingFunction, Error,
|
|
"Missing implementation of {0} in impl of interface {1}.",
|
|
SemIR::NameId, SemIR::NameId);
|
|
auto builder =
|
|
context.emitter().Build(impl.definition_id, ImplMissingFunction,
|
|
fn.name_id, interface.name_id);
|
|
NoteAssociatedFunction(context, builder, fn_type->function_id);
|
|
builder.Emit();
|
|
|
|
table.push_back(SemIR::InstId::BuiltinError);
|
|
}
|
|
break;
|
|
}
|
|
case SemIR::AssociatedConstantDecl::Kind:
|
|
// TODO: Check we have a value for this constant in the constraint.
|
|
context.TODO(impl.definition_id,
|
|
"impl of interface with associated constant");
|
|
return SemIR::InstId::BuiltinError;
|
|
default:
|
|
CARBON_CHECK(decl_id == SemIR::InstId::BuiltinError,
|
|
"Unexpected kind of associated entity {0}", decl);
|
|
table.push_back(SemIR::InstId::BuiltinError);
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto table_id = context.inst_blocks().Add(table);
|
|
return context.AddInst<SemIR::InterfaceWitness>(
|
|
context.insts().GetLocId(impl.definition_id),
|
|
{.type_id = context.GetBuiltinType(SemIR::BuiltinInstKind::WitnessType),
|
|
.elements_id = table_id});
|
|
}
|
|
|
|
auto BuildImplWitness(Context& context, SemIR::ImplId impl_id)
|
|
-> SemIR::InstId {
|
|
auto& impl = context.impls().Get(impl_id);
|
|
CARBON_CHECK(impl.is_being_defined());
|
|
|
|
// TODO: Handle non-interface constraints.
|
|
auto interface_type =
|
|
context.types().TryGetAs<SemIR::InterfaceType>(impl.constraint_id);
|
|
if (!interface_type) {
|
|
context.TODO(impl.definition_id, "impl as non-interface");
|
|
return SemIR::InstId::BuiltinError;
|
|
}
|
|
|
|
llvm::SmallVector<SemIR::InstId> used_decl_ids;
|
|
|
|
auto witness_id = BuildInterfaceWitness(context, impl, impl.constraint_id,
|
|
*interface_type, used_decl_ids);
|
|
|
|
// TODO: Diagnose if any declarations in the impl are not in used_decl_ids.
|
|
|
|
return witness_id;
|
|
}
|
|
|
|
} // namespace Carbon::Check
|