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>
221 lines
8.8 KiB
C++
221 lines
8.8 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/deduce.h"
|
|
|
|
#include "toolchain/base/kind_switch.h"
|
|
#include "toolchain/check/context.h"
|
|
#include "toolchain/check/generic.h"
|
|
#include "toolchain/check/subst.h"
|
|
#include "toolchain/sem_ir/typed_insts.h"
|
|
|
|
namespace Carbon::Check {
|
|
|
|
namespace {
|
|
// A list of pairs of (instruction from generic, corresponding instruction from
|
|
// call to of generic) for which we still need to perform deduction, along with
|
|
// methods to add and pop pending deductions from the list. Deductions are
|
|
// popped in order from most- to least-recently pushed, with the intent that
|
|
// they are visited in depth-first order, although the order is not expected to
|
|
// matter except when it influences which error is diagnosed.
|
|
class DeductionWorklist {
|
|
public:
|
|
explicit DeductionWorklist(Context& context) : context_(context) {}
|
|
|
|
struct PendingDeduction {
|
|
SemIR::InstId param;
|
|
SemIR::InstId arg;
|
|
bool needs_substitution;
|
|
};
|
|
|
|
// Adds a single (param, arg) deduction.
|
|
auto Add(SemIR::InstId param, SemIR::InstId arg, bool needs_substitution)
|
|
-> void {
|
|
deductions_.push_back(
|
|
{.param = param, .arg = arg, .needs_substitution = needs_substitution});
|
|
}
|
|
|
|
// Adds a list of (param, arg) deductions. These are added in reverse order so
|
|
// they are popped in forward order.
|
|
auto AddAll(llvm::ArrayRef<SemIR::InstId> params,
|
|
llvm::ArrayRef<SemIR::InstId> args, bool needs_substitution)
|
|
-> void {
|
|
if (params.size() != args.size()) {
|
|
// TODO: Decide whether to error on this or just treat the parameter list
|
|
// as non-deduced. For now we treat it as non-deduced.
|
|
return;
|
|
}
|
|
for (auto [param, arg] : llvm::reverse(llvm::zip_equal(params, args))) {
|
|
Add(param, arg, needs_substitution);
|
|
}
|
|
}
|
|
|
|
auto AddAll(SemIR::InstBlockId params, llvm::ArrayRef<SemIR::InstId> args,
|
|
bool needs_substitution) -> void {
|
|
AddAll(context_.inst_blocks().Get(params), args, needs_substitution);
|
|
}
|
|
|
|
auto AddAll(SemIR::InstBlockId params, SemIR::InstBlockId args,
|
|
bool needs_substitution) -> void {
|
|
AddAll(context_.inst_blocks().Get(params), context_.inst_blocks().Get(args),
|
|
needs_substitution);
|
|
}
|
|
|
|
// Returns whether we have completed all deductions.
|
|
auto Done() -> bool { return deductions_.empty(); }
|
|
|
|
// Pops the next deduction. Requires `!Done()`.
|
|
auto PopNext() -> PendingDeduction { return deductions_.pop_back_val(); }
|
|
|
|
private:
|
|
Context& context_;
|
|
llvm::SmallVector<PendingDeduction> deductions_;
|
|
};
|
|
} // namespace
|
|
|
|
static auto NoteGenericHere(Context& context, SemIR::GenericId generic_id,
|
|
Context::DiagnosticBuilder& diag) -> void {
|
|
CARBON_DIAGNOSTIC(DeductionGenericHere, Note,
|
|
"While deducing parameters of generic declared here.");
|
|
diag.Note(context.generics().Get(generic_id).decl_id, DeductionGenericHere);
|
|
}
|
|
|
|
auto DeduceGenericCallArguments(
|
|
Context& context, SemIR::LocId loc_id, SemIR::GenericId generic_id,
|
|
SemIR::SpecificId enclosing_specific_id,
|
|
[[maybe_unused]] SemIR::InstBlockId implicit_params_id,
|
|
SemIR::InstBlockId params_id, [[maybe_unused]] SemIR::InstId self_id,
|
|
llvm::ArrayRef<SemIR::InstId> arg_ids) -> SemIR::SpecificId {
|
|
DeductionWorklist worklist(context);
|
|
|
|
llvm::SmallVector<SemIR::InstId> result_arg_ids;
|
|
llvm::SmallVector<Substitution> substitutions;
|
|
|
|
// Copy any outer generic arguments from the specified instance and prepare to
|
|
// substitute them into the function declaration.
|
|
if (enclosing_specific_id.is_valid()) {
|
|
auto args = context.inst_blocks().Get(
|
|
context.specifics().Get(enclosing_specific_id).args_id);
|
|
result_arg_ids.assign(args.begin(), args.end());
|
|
|
|
// TODO: Subst is linear in the length of the substitutions list. Change it
|
|
// so we can pass in an array mapping indexes to substitutions instead.
|
|
substitutions.reserve(args.size());
|
|
for (auto [i, subst_inst_id] : llvm::enumerate(args)) {
|
|
substitutions.push_back(
|
|
{.bind_id = SemIR::CompileTimeBindIndex(i),
|
|
.replacement_id = context.constant_values().Get(subst_inst_id)});
|
|
}
|
|
}
|
|
auto first_deduced_index = SemIR::CompileTimeBindIndex(result_arg_ids.size());
|
|
|
|
// Initialize the deduced arguments to Invalid.
|
|
result_arg_ids.resize(context.inst_blocks()
|
|
.Get(context.generics().Get(generic_id).bindings_id)
|
|
.size(),
|
|
SemIR::InstId::Invalid);
|
|
|
|
// Prepare to perform deduction of the explicit parameters against their
|
|
// arguments.
|
|
// TODO: Also perform deduction for type of self.
|
|
worklist.AddAll(params_id, arg_ids, /*needs_substitution=*/true);
|
|
|
|
while (!worklist.Done()) {
|
|
auto [param_id, arg_id, needs_substitution] = worklist.PopNext();
|
|
|
|
// If the parameter has a symbolic type, deduce against that.
|
|
auto param_type_id = context.insts().Get(param_id).type_id();
|
|
if (param_type_id.AsConstantId().is_symbolic()) {
|
|
worklist.Add(
|
|
context.types().GetInstId(param_type_id),
|
|
context.types().GetInstId(context.insts().Get(arg_id).type_id()),
|
|
needs_substitution);
|
|
}
|
|
|
|
// If the parameter is a symbolic constant, deduce against it.
|
|
auto param_const_id = context.constant_values().Get(param_id);
|
|
if (!param_const_id.is_valid() || !param_const_id.is_symbolic()) {
|
|
continue;
|
|
}
|
|
|
|
// If we've not yet substituted into the parameter, do so now.
|
|
if (needs_substitution) {
|
|
param_const_id = SubstConstant(context, param_const_id, substitutions);
|
|
if (!param_const_id.is_valid() || !param_const_id.is_symbolic()) {
|
|
continue;
|
|
}
|
|
needs_substitution = false;
|
|
}
|
|
|
|
CARBON_KIND_SWITCH(context.insts().Get(context.constant_values().GetInstId(
|
|
param_const_id))) {
|
|
// Deducing a symbolic binding from an argument with a constant value
|
|
// deduces the binding as having that constant value.
|
|
case CARBON_KIND(SemIR::BindSymbolicName bind): {
|
|
auto& entity_name = context.entity_names().Get(bind.entity_name_id);
|
|
auto index = entity_name.bind_index;
|
|
if (index.is_valid() && index >= first_deduced_index) {
|
|
CARBON_CHECK(static_cast<size_t>(index.index) < result_arg_ids.size(),
|
|
"Deduced value for unexpected index {0}; expected to "
|
|
"deduce {1} arguments.",
|
|
index, result_arg_ids.size());
|
|
auto arg_const_inst_id =
|
|
context.constant_values().GetConstantInstId(arg_id);
|
|
if (arg_const_inst_id.is_valid()) {
|
|
if (result_arg_ids[index.index].is_valid() &&
|
|
result_arg_ids[index.index] != arg_const_inst_id) {
|
|
// TODO: Include the two different deduced values.
|
|
CARBON_DIAGNOSTIC(DeductionInconsistent, Error,
|
|
"Inconsistent deductions for value of generic "
|
|
"parameter `{0}`.",
|
|
SemIR::NameId);
|
|
auto diag = context.emitter().Build(loc_id, DeductionInconsistent,
|
|
entity_name.name_id);
|
|
NoteGenericHere(context, generic_id, diag);
|
|
diag.Emit();
|
|
return SemIR::SpecificId::Invalid;
|
|
}
|
|
result_arg_ids[index.index] = arg_const_inst_id;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// TODO: Handle more cases.
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check we deduced an argument value for every parameter.
|
|
for (auto [i, deduced_arg_id] :
|
|
llvm::enumerate(llvm::ArrayRef(result_arg_ids)
|
|
.drop_front(first_deduced_index.index))) {
|
|
if (!deduced_arg_id.is_valid()) {
|
|
auto binding_index = first_deduced_index.index + i;
|
|
auto binding_id = context.inst_blocks().Get(
|
|
context.generics().Get(generic_id).bindings_id)[binding_index];
|
|
auto entity_name_id =
|
|
context.insts().GetAs<SemIR::AnyBindName>(binding_id).entity_name_id;
|
|
CARBON_DIAGNOSTIC(DeductionIncomplete, Error,
|
|
"Cannot deduce value for generic parameter `{0}`.",
|
|
SemIR::NameId);
|
|
auto diag = context.emitter().Build(
|
|
loc_id, DeductionIncomplete,
|
|
context.entity_names().Get(entity_name_id).name_id);
|
|
NoteGenericHere(context, generic_id, diag);
|
|
diag.Emit();
|
|
return SemIR::SpecificId::Invalid;
|
|
}
|
|
}
|
|
|
|
// TODO: Convert the deduced values to the types of the bindings.
|
|
|
|
return MakeSpecific(context, generic_id,
|
|
context.inst_blocks().AddCanonical(result_arg_ids));
|
|
}
|
|
|
|
} // namespace Carbon::Check
|