Add a text mode for fingerprinting. (#7231)

The intent is to add visibility into how the fingerprint is computed, so
that fingerprinting issues and mangling collisions can be more readily
understood and fixed.

Assisted-by: Gemini via Antigravity
This commit is contained in:
Richard Smith
2026-05-19 23:16:51 +00:00
committed by GitHub
parent ce080b3549
commit be70c092fa
19 changed files with 466 additions and 129 deletions
+2 -2
View File
@@ -487,7 +487,7 @@ auto CheckParseTrees(
auto* unit_info = ready_to_check[check_index];
CheckUnit(unit_info, &tree_and_subtrees_getters, fs,
unit_info->unit->llvm_context, clang_invocation,
options.vlog_stream)
options.vlog_stream, options.mangle_string_fingerprint)
.Run();
for (auto* incoming_import : unit_info->incoming_imports) {
--incoming_import->imports_remaining;
@@ -537,7 +537,7 @@ auto CheckParseTrees(
if (unit_info.imports_remaining > 0) {
CheckUnit(&unit_info, &tree_and_subtrees_getters, fs,
unit_info.unit->llvm_context, clang_invocation,
options.vlog_stream)
options.vlog_stream, options.mangle_string_fingerprint)
.Run();
}
}
+4
View File
@@ -75,6 +75,10 @@ struct CheckParseTreesOptions {
// If not empty, a raw SemIR dump should be written to this path in the event
// of a crash.
llvm::StringRef sem_ir_crash_dump;
// Whether to use the string form of the fingerprint from mangling instead of
// the hash form.
bool mangle_string_fingerprint = false;
};
// Checks a group of parse trees. This will use imports to decide the order of
+3 -2
View File
@@ -63,7 +63,7 @@ CheckUnit::CheckUnit(
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
llvm::LLVMContext* llvm_context,
std::shared_ptr<clang::CompilerInvocation> clang_invocation,
llvm::raw_ostream* vlog_stream)
llvm::raw_ostream* vlog_stream, bool mangle_string_fingerprint)
: unit_and_imports_(unit_and_imports),
tree_and_subtrees_getter_(tree_and_subtrees_getters->Get(
unit_and_imports->unit->sem_ir->check_ir_id())),
@@ -75,7 +75,8 @@ CheckUnit::CheckUnit(
context_(&emitter_, tree_and_subtrees_getter_,
unit_and_imports_->unit->sem_ir,
GetImportedIRCount(unit_and_imports),
unit_and_imports_->unit->total_ir_count, vlog_stream) {}
unit_and_imports_->unit->total_ir_count, vlog_stream,
mangle_string_fingerprint) {}
auto CheckUnit::Run() -> void {
Timings::ScopedTiming timing(unit_and_imports_->unit->timings, "check");
+1 -1
View File
@@ -129,7 +129,7 @@ class CheckUnit {
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
llvm::LLVMContext* llvm_context,
std::shared_ptr<clang::CompilerInvocation> clang_invocation,
llvm::raw_ostream* vlog_stream);
llvm::raw_ostream* vlog_stream, bool mangle_string_fingerprint = false);
// Produces and checks the IR for the provided unit.
auto Run() -> void;
+3 -2
View File
@@ -17,7 +17,7 @@ namespace Carbon::Check {
Context::Context(DiagnosticEmitterBase* emitter,
Parse::GetTreeAndSubtreesFn tree_and_subtrees_getter,
SemIR::File* sem_ir, int imported_ir_count, int total_ir_count,
llvm::raw_ostream* vlog_stream)
llvm::raw_ostream* vlog_stream, bool mangle_string_fingerprint)
: emitter_(emitter),
tree_and_subtrees_getter_(tree_and_subtrees_getter),
sem_ir_(sem_ir),
@@ -39,7 +39,8 @@ Context::Context(DiagnosticEmitterBase* emitter,
region_stack_([this](SemIR::LocId loc_id, std::string label) {
TODO(loc_id, label);
}),
core_identifiers_(&identifiers()) {
core_identifiers_(&identifiers()),
mangle_string_fingerprint_(mangle_string_fingerprint) {
// Prepare fields which relate to the number of IRs available for import.
import_irs().Reserve(imported_ir_count);
import_ir_constant_values_.reserve(imported_ir_count);
+7 -1
View File
@@ -61,7 +61,8 @@ class Context {
explicit Context(DiagnosticEmitterBase* emitter,
Parse::GetTreeAndSubtreesFn tree_and_subtrees_getter,
SemIR::File* sem_ir, int imported_ir_count,
int total_ir_count, llvm::raw_ostream* vlog_stream);
int total_ir_count, llvm::raw_ostream* vlog_stream,
bool mangle_string_fingerprint = false);
// Marks an implementation TODO. Always returns false.
auto TODO(SemIR::LocId loc_id, std::string label) -> bool;
@@ -398,6 +399,9 @@ class Context {
auto constants() -> SemIR::ConstantStore& { return sem_ir().constants(); }
auto bundles() -> SemIR::BundleStore& { return sem_ir().bundles(); }
auto total_ir_count() const -> int { return total_ir_count_; }
auto mangle_string_fingerprint() const -> bool {
return mangle_string_fingerprint_;
}
// --------------------------------------------------------------------------
// End of SemIR::File members.
@@ -557,6 +561,8 @@ class Context {
// See `CoreIdentifierCache` for details.
CoreIdentifierCache core_identifiers_;
bool mangle_string_fingerprint_;
};
inline constexpr Context::FormExpr Context::FormExpr::Error = {
+2 -1
View File
@@ -480,7 +480,8 @@ static auto BuildCppFunctionDeclForCarbonFn(Context& context,
function_decl->setParams(param_var_decls);
// Mangle the function name and attach it to the `FunctionDecl`.
SemIR::Mangler m(context.sem_ir(), context.total_ir_count());
SemIR::Mangler m(context.sem_ir(), context.total_ir_count(),
context.mangle_string_fingerprint());
std::string mangled_name = m.Mangle(function_id, SemIR::SpecificId::None);
function_decl->addAttr(
clang::AsmLabelAttr::Create(context.ast_context(), mangled_name));
+10
View File
@@ -391,6 +391,14 @@ in the check phase. If empty, the dump is not written.
)""",
},
[&](auto& arg_b) { arg_b.Set(&sem_ir_crash_dump); });
b.AddFlag(
{
.name = "mangle-string-fingerprint",
.help = R"""(
Use the string form of the fingerprint from mangling instead of the hash form.
)""",
},
[&](auto& arg_b) { arg_b.Set(&mangle_string_fingerprint); });
}
static constexpr CommandLine::CommandInfo SubcommandInfo = {
@@ -821,6 +829,7 @@ auto CompilationUnit::RunLower() -> void {
options.want_debug_info = options_->include_debug_info;
options.vlog_stream = vlog_stream_;
options.opt_level = options_->opt_level;
options.mangle_string_fingerprint = options_->mangle_string_fingerprint;
module_ = Lower::LowerToLLVM(*llvm_context_, driver_env_->fs,
cache_->tree_and_subtrees_getters(), *sem_ir_,
total_ir_count_, options);
@@ -1260,6 +1269,7 @@ auto CompileSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
options.prelude_import = options_.prelude_import;
options.vlog_stream = driver_env.vlog_stream;
options.fuzzing = driver_env.fuzzing;
options.mangle_string_fingerprint = options_.mangle_string_fingerprint;
if (options.vlog_stream || options_.dump_sem_ir || options_.dump_cpp_ast ||
options_.dump_raw_sem_ir) {
options.include_in_dumps = &cache.include_in_dumps();
+2
View File
@@ -71,6 +71,8 @@ struct CompileOptions {
llvm::SmallVector<llvm::StringRef> exclude_dump_file_prefixes;
llvm::StringRef sem_ir_crash_dump;
bool mangle_string_fingerprint = false;
};
// Implements the compile subcommand of the driver.
+2 -1
View File
@@ -20,7 +20,7 @@ Context::Context(
const Parse::GetTreeAndSubtreesStore* tree_and_subtrees_getters,
clang::CodeGenerator* clang_code_generator, llvm::StringRef module_name,
int total_ir_count, Lower::OptimizationLevel opt_level,
llvm::raw_ostream* vlog_stream)
bool mangle_string_fingerprint, llvm::raw_ostream* vlog_stream)
: llvm_context_(llvm_context),
clang_code_generator_(clang_code_generator),
llvm_module_owner_(
@@ -39,6 +39,7 @@ Context::Context(
tree_and_subtrees_getters_(tree_and_subtrees_getters),
vlog_stream_(vlog_stream),
total_ir_count_(total_ir_count),
mangle_string_fingerprint_(mangle_string_fingerprint),
file_contexts_(
FileContextStore::MakeForOverwriteWithExplicitSize(total_ir_count_)) {
}
+7 -1
View File
@@ -51,7 +51,7 @@ class Context {
const Parse::GetTreeAndSubtreesStore* tree_and_subtrees_getters,
clang::CodeGenerator* code_generator, llvm::StringRef module_name,
int total_ir_count, Lower::OptimizationLevel opt_level,
llvm::raw_ostream* vlog_stream);
bool mangle_string_fingerprint, llvm::raw_ostream* vlog_stream);
// Gets or creates the `FileContext` for a given SemIR file. If an
// `inst_namer` is specified the first time this is called for a file, it will
@@ -124,6 +124,9 @@ class Context {
return *tree_and_subtrees_getters_;
}
auto total_ir_count() -> int { return total_ir_count_; }
auto mangle_string_fingerprint() const -> bool {
return mangle_string_fingerprint_;
}
auto printf_int_format_string() -> llvm::Value* {
return printf_int_format_string_;
@@ -171,6 +174,9 @@ class Context {
// The total number of files.
int total_ir_count_;
// Whether to use the string form of the fingerprint for mangling.
bool mangle_string_fingerprint_;
// The `FileContext`s for each IR that is involved in this lowering action.
using FileContextStore =
FixedSizeValueStore<SemIR::CheckIRId, std::unique_ptr<FileContext>>;
+6 -3
View File
@@ -322,7 +322,8 @@ auto FileContext::GetOrCreateLLVMFunction(
sem_ir().clang_decls().Get(clang_decl_id).key.decl->getAsFunction());
}
SemIR::Mangler m(sem_ir(), context().total_ir_count());
SemIR::Mangler m(sem_ir(), context().total_ir_count(),
context().mangle_string_fingerprint());
std::string mangled_name = m.Mangle(function_id, specific_id);
if (auto* existing = llvm_module().getFunction(mangled_name)) {
// We might have already lowered this function while lowering a different
@@ -691,7 +692,8 @@ auto FileContext::BuildGlobalVariableDecl(SemIR::VarStorage var_storage)
auto FileContext::BuildNonCppGlobalVariableDecl(SemIR::VarStorage var_storage)
-> llvm::GlobalVariable* {
SemIR::Mangler m(sem_ir(), context().total_ir_count());
SemIR::Mangler m(sem_ir(), context().total_ir_count(),
context().mangle_string_fingerprint());
auto mangled_name = m.MangleGlobalVariable(var_storage.pattern_id);
auto linkage = llvm::GlobalVariable::ExternalLinkage;
@@ -726,7 +728,8 @@ auto FileContext::BuildVtable(const SemIR::Vtable& vtable,
}
const auto& class_info = sem_ir().classes().Get(vtable.class_id);
SemIR::Mangler m(sem_ir(), context().total_ir_count());
SemIR::Mangler m(sem_ir(), context().total_ir_count(),
context().mangle_string_fingerprint());
std::string mangled_name = m.MangleVTable(class_info, specific_id);
if (sem_ir()
+1 -1
View File
@@ -25,7 +25,7 @@ auto LowerToLLVM(
&tree_and_subtrees_getters,
sem_ir.cpp_file() ? sem_ir.cpp_file()->GetCodeGenerator() : nullptr,
sem_ir.filename(), total_ir_count, options.opt_level,
options.vlog_stream);
options.mangle_string_fingerprint, options.vlog_stream);
// TODO: Consider disabling instruction naming by default if we're not
// producing textual LLVM IR.
+3
View File
@@ -37,6 +37,9 @@ struct LowerToLLVMOptions {
// The optimization level to set on lowered functions by default.
OptimizationLevel opt_level = OptimizationLevel::Debug;
// Whether to use the string form of the fingerprint for mangling.
bool mangle_string_fingerprint = false;
};
} // namespace Carbon::Lower
@@ -0,0 +1,103 @@
// 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-FILE: toolchain/testing/testdata/min_prelude/int.carbon
//
// EXTRA-ARGS: --mangle-string-fingerprint
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/packages/imported_package_mangle_string.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/packages/imported_package_mangle_string.carbon
// --- a.carbon
package A;
class C {}
interface I {
fn F() {}
}
impl array(C, 1) as I {
fn F() {}
}
namespace N;
class N.D;
impl array(N.D, 1) as I {
fn F() {}
}
// --- b.carbon
package B;
import A;
fn G() {
(array(A.C, 1) as A.I).F();
(array(A.N.D, 1) as A.I).F();
}
// CHECK:STDOUT: ; ModuleID = 'a.carbon'
// CHECK:STDOUT: source_filename = "a.carbon"
// CHECK:STDOUT:
// CHECK:STDOUT: ; Function Attrs: nounwind
// CHECK:STDOUT: define void @"_CF.{array_type,{int_value,{Core.IntLiteral},1},{class_type,C,{namespace,{<namespace>},A,!invalid},!invalid}}:I.A"() #0 !dbg !4 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: ret void, !dbg !7
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: ; Function Attrs: nounwind
// CHECK:STDOUT: define void @"_CF.{array_type,{int_value,{Core.IntLiteral},1},{class_type,D,{namespace,{<namespace>},N,{namespace,{<namespace>},A,!invalid},!invalid},!invalid}}:I.A"() #0 !dbg !8 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: ret void, !dbg !9
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: attributes #0 = { nounwind }
// CHECK:STDOUT:
// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
// CHECK:STDOUT:
// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
// CHECK:STDOUT: !3 = !DIFile(filename: "a.carbon", directory: "")
// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.{array_type,{int_value,{Core.IntLiteral},1},{class_type,C,{namespace,{<namespace>},A,!invalid},!invalid}}:I.A", scope: null, file: !3, line: 10, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
// CHECK:STDOUT: !6 = !{null}
// CHECK:STDOUT: !7 = !DILocation(line: 10, column: 3, scope: !4)
// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "F", linkageName: "_CF.{array_type,{int_value,{Core.IntLiteral},1},{class_type,D,{namespace,{<namespace>},N,{namespace,{<namespace>},A,!invalid},!invalid},!invalid}}:I.A", scope: null, file: !3, line: 17, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !9 = !DILocation(line: 17, column: 3, scope: !8)
// CHECK:STDOUT: ; ModuleID = 'b.carbon'
// CHECK:STDOUT: source_filename = "b.carbon"
// CHECK:STDOUT:
// CHECK:STDOUT: ; Function Attrs: nounwind
// CHECK:STDOUT: define void @_CG.B() #0 !dbg !4 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: call void @"_CF.{array_type,{int_value,{Core.IntLiteral},1},{class_type,C,{namespace,{<namespace>},A,{import,!invalid,A}},!invalid}}:I.A"(), !dbg !7
// CHECK:STDOUT: call void @"_CF.{array_type,{int_value,{Core.IntLiteral},1},{class_type,D,{namespace,{<namespace>},N,{namespace,{<namespace>},A,{import,!invalid,A}},!invalid},!invalid}}:I.A"(), !dbg !8
// CHECK:STDOUT: ret void, !dbg !9
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: declare void @"_CF.{array_type,{int_value,{Core.IntLiteral},1},{class_type,C,{namespace,{<namespace>},A,{import,!invalid,A}},!invalid}}:I.A"()
// CHECK:STDOUT:
// CHECK:STDOUT: declare void @"_CF.{array_type,{int_value,{Core.IntLiteral},1},{class_type,D,{namespace,{<namespace>},N,{namespace,{<namespace>},A,{import,!invalid,A}},!invalid},!invalid}}:I.A"()
// CHECK:STDOUT:
// CHECK:STDOUT: attributes #0 = { nounwind }
// CHECK:STDOUT:
// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
// CHECK:STDOUT:
// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
// CHECK:STDOUT: !3 = !DIFile(filename: "b.carbon", directory: "")
// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "G", linkageName: "_CG.B", scope: null, file: !3, line: 4, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
// CHECK:STDOUT: !6 = !{null}
// CHECK:STDOUT: !7 = !DILocation(line: 5, column: 3, scope: !4)
// CHECK:STDOUT: !8 = !DILocation(line: 6, column: 3, scope: !4)
// CHECK:STDOUT: !9 = !DILocation(line: 4, column: 1, scope: !4)
+246 -76
View File
@@ -5,6 +5,7 @@
#include "toolchain/sem_ir/inst_fingerprinter.h"
#include <array>
#include <optional>
#include <utility>
#include <variant>
@@ -23,49 +24,38 @@
namespace Carbon::SemIR {
namespace {
struct Worklist {
using FingerprintStore =
FixedSizeValueStore<InstId, uint64_t, Tag<CheckIRId>>;
using FilesFingerprintStores =
FixedSizeValueStore<CheckIRId, FingerprintStore>;
// A fingerprint store that computes a hash via a Merkle tree.
class HashFingerprintStore {
public:
using ResultType = uint64_t;
// The file containing the instruction we're currently processing.
const File* sem_ir = nullptr;
// The instructions we need to compute fingerprints for.
llvm::SmallVector<std::pair<
const File*, std::variant<InstId, InstBlockId, ImplId, CppOverloadSetId>>>
todo;
// The contents of the current instruction as accumulated so far. This is used
// to build a Merkle tree containing a fingerprint for the current
// instruction.
llvm::SmallVector<llvm::stable_hash> contents = {};
// Known cached instruction fingerprints. Each item in `todo` will be added to
// the cache if not already present.
FilesFingerprintStores* fingerprints;
explicit HashFingerprintStore(int total_ir_count)
: fingerprints_(FilesFingerprintStores::MakeWithExplicitSizeFrom(
total_ir_count, [] {
return FingerprintStore::MakeForOverwriteWithExplicitSize(
0, CheckIRId::None);
})) {}
// Finish fingerprinting and compute the fingerprint.
auto Finish() -> uint64_t { return llvm::stable_hash_combine(contents); }
// Gets the known fingerprint from the cache, or returns 0.
auto GetFingerprint(const File* file, InstId inst_id) -> uint64_t {
auto& store = fingerprints->Get(file->check_ir_id());
auto GetFingerprint(const File* file, InstId inst_id)
-> std::optional<ResultType> {
auto& store = fingerprints_.Get(file->check_ir_id());
if (store.size() == 0) {
return 0;
return std::nullopt;
}
// These InstIds are constant values, so not in the ValueStore. We use a
// constant (negative) fingerprint for them.
if (inst_id == InstId::InitTombstone ||
inst_id == InstId::ImplWitnessTablePlaceholder) {
return inst_id.index;
}
return store.Get(inst_id);
auto fingerprint = store.Get(inst_id);
if (fingerprint == 0) {
return std::nullopt;
}
return fingerprint;
}
// Sets the fingerprint for an instruction in the cache. Since 0 is used to
// indicate empty, we map 0 to another fixed value.
auto SetFingerprint(const File* file, InstId inst_id, uint64_t fingerprint) {
auto& store = fingerprints->Get(file->check_ir_id());
auto SetFingerprint(const File* file, InstId inst_id, ResultType fingerprint)
-> void {
auto& store = fingerprints_.Get(file->check_ir_id());
if (store.size() == 0) {
store = FingerprintStore::MakeWithExplicitSize(
file->insts().size(), file->insts().GetIdTag(), 0);
@@ -73,15 +63,180 @@ struct Worklist {
store.Set(inst_id, fingerprint ? fingerprint : 1);
}
// Add an invalid marker to the contents. This is used when the entity
// contains a `None` ID. This uses an arbitrary fixed value that is assumed
// to be unlikely to collide with a valid value.
auto AddInvalid() -> void { contents.push_back(-1); }
auto Prepare() -> void { contents_.clear(); }
auto Finish() -> ResultType { return llvm::stable_hash_combine(contents_); }
auto AddFingerprint(ResultType fingerprint) -> void {
contents_.push_back(fingerprint);
}
auto AddInvalid() -> void { contents_.push_back(-1); }
auto AddString(llvm::StringRef string) -> void {
contents_.push_back(llvm::stable_hash_name(string));
}
auto AddInteger(uint64_t value) -> void { contents_.push_back(value); }
auto AddAPInt(const llvm::APInt& value) -> void {
contents_.push_back(value.getBitWidth());
contents_.append(value.getRawData(),
value.getRawData() + value.getNumWords());
}
private:
using FingerprintStore =
FixedSizeValueStore<InstId, uint64_t, Tag<CheckIRId>>;
using FilesFingerprintStores =
FixedSizeValueStore<CheckIRId, FingerprintStore>;
FilesFingerprintStores fingerprints_;
llvm::SmallVector<llvm::stable_hash> contents_;
};
// A fingerprint store that produces a string representation of the entity being
// fingerprinted.
class StringFingerprintStore {
public:
using ResultType = llvm::StringRef;
explicit StringFingerprintStore(int total_ir_count)
: fingerprints_(FilesFingerprintStores::MakeWithExplicitSizeFrom(
total_ir_count, [] {
return FingerprintStore::MakeForOverwriteWithExplicitSize(
0, CheckIRId::None);
})) {}
auto GetFingerprint(const File* file, InstId inst_id)
-> std::optional<ResultType> {
auto& store = fingerprints_.Get(file->check_ir_id());
if (store.size() == 0) {
return std::nullopt;
}
if (inst_id == InstId::InitTombstone) {
return llvm::StringRef("!tombstone");
}
if (inst_id == InstId::ImplWitnessTablePlaceholder) {
return llvm::StringRef("!placeholder");
}
auto fingerprint = store.Get(inst_id);
if (fingerprint.empty()) {
return std::nullopt;
}
return fingerprint;
}
auto SetFingerprint(const File* file, InstId inst_id, ResultType fingerprint)
-> void {
auto& store = fingerprints_.Get(file->check_ir_id());
if (store.size() == 0) {
store = FingerprintStore::MakeWithExplicitSize(
file->insts().size(), file->insts().GetIdTag(), llvm::StringRef());
}
store.Set(inst_id, fingerprint);
}
auto Prepare() -> void { contents_.clear(); }
auto Finish() -> ResultType {
std::string result = "{";
bool first = true;
for (const auto& item : contents_) {
if (!first) {
result += ",";
}
first = false;
result += item;
}
result += "}";
return SaveString(std::move(result));
}
auto AddFingerprint(ResultType fingerprint) -> void {
contents_.push_back(fingerprint);
}
auto AddInvalid() -> void { contents_.push_back("!invalid"); }
auto AddString(llvm::StringRef string) -> void {
constexpr llvm::StringRef SpecialChars = "{},!\\";
auto num_special_chars = llvm::count_if(
string, [&](char c) { return SpecialChars.contains(c); });
if (num_special_chars) {
std::string escaped;
escaped.reserve(string.size() + num_special_chars);
for (char c : string) {
if (SpecialChars.contains(c)) {
escaped.push_back('\\');
}
escaped.push_back(c);
}
contents_.push_back(SaveString(std::move(escaped)));
} else {
contents_.push_back(string);
}
}
auto AddInteger(uint64_t value) -> void {
contents_.push_back(SaveString(std::to_string(value)));
}
auto AddAPInt(const llvm::APInt& value) -> void {
contents_.push_back(
SaveString(llvm::toString(value, 10, /*isSigned=*/true)));
}
private:
auto SaveString(std::string str) -> llvm::StringRef {
auto& entry = allocated_strings_.emplace_back(
std::make_unique<std::string>(std::move(str)));
return *entry;
}
using FingerprintStore =
FixedSizeValueStore<InstId, llvm::StringRef, Tag<CheckIRId>>;
using FilesFingerprintStores =
FixedSizeValueStore<CheckIRId, FingerprintStore>;
FilesFingerprintStores fingerprints_;
llvm::SmallVector<llvm::StringRef> contents_;
llvm::SmallVector<std::unique_ptr<std::string>> allocated_strings_;
};
namespace {
template <typename StoreT>
struct Worklist {
using ResultType = StoreT::ResultType;
// The file containing the instruction we're currently processing.
const File* sem_ir = nullptr;
// The instructions we need to compute fingerprints for.
llvm::SmallVector<std::pair<
const File*, std::variant<InstId, InstBlockId, ImplId, CppOverloadSetId>>>
todo;
// Known cached instruction fingerprints.
StoreT* store;
// Finish fingerprinting and compute the fingerprint.
auto Finish() -> ResultType { return store->Finish(); }
// Gets the known fingerprint from the cache, or returns std::nullopt.
auto GetFingerprint(const File* file, InstId inst_id)
-> std::optional<ResultType> {
return store->GetFingerprint(file, inst_id);
}
// Sets the fingerprint for an instruction in the cache.
auto SetFingerprint(const File* file, InstId inst_id, ResultType fingerprint)
-> void {
store->SetFingerprint(file, inst_id, fingerprint);
}
// Add an invalid marker to the contents.
auto AddInvalid() -> void { store->AddInvalid(); }
// Add a string to the contents.
auto AddString(llvm::StringRef string) -> void {
contents.push_back(llvm::stable_hash_name(string));
}
auto AddString(llvm::StringRef string) -> void { store->AddString(string); }
// Each of the following `Add` functions adds a typed argument to the contents
// of the current instruction. If we don't yet have a fingerprint for the
@@ -131,7 +286,7 @@ struct Worklist {
return;
}
if (auto fingerprint = GetFingerprint(file, inner_id)) {
contents.push_back(fingerprint);
store->AddFingerprint(*fingerprint);
return;
}
todo.push_back({file, inner_id});
@@ -157,7 +312,7 @@ struct Worklist {
template <typename T>
auto AddBlock(llvm::ArrayRef<T> block) -> void {
contents.push_back(block.size());
store->AddInteger(block.size());
for (auto inner_id : block) {
Add(inner_id);
}
@@ -190,9 +345,9 @@ struct Worklist {
return;
}
auto block = sem_ir->custom_layouts().Get(custom_layout_id);
contents.push_back(block.size());
store->AddInteger(block.size());
for (auto size : block) {
contents.push_back(size.bits());
store->AddInteger(size.bits());
}
}
@@ -265,7 +420,7 @@ struct Worklist {
const auto& require = sem_ir->require_impls().Get(require_id);
Add(sem_ir->constant_values().Get(require.self_id));
Add(sem_ir->constant_values().Get(require.facet_type_inst_id));
contents.push_back(require.extend_self);
store->AddInteger(require.extend_self);
Add(require.parent_scope_id);
}
@@ -298,7 +453,7 @@ struct Worklist {
auto Add(FacetTypeId facet_type_id) -> void {
const auto& facet_type = sem_ir->facet_types().Get(facet_type_id);
auto add_constraints = [&](auto constraints) {
contents.push_back(constraints.size());
store->AddInteger(constraints.size());
for (auto [first, second] : constraints) {
Add(first);
Add(second);
@@ -307,7 +462,7 @@ struct Worklist {
add_constraints(facet_type.extend_constraints);
add_constraints(facet_type.self_impls_constraints);
add_constraints(facet_type.rewrite_constraints);
contents.push_back(facet_type.other_requirements);
store->AddInteger(facet_type.other_requirements);
}
auto Add(GenericId generic_id) -> void {
@@ -339,11 +494,7 @@ struct Worklist {
Add(interface.specific_id);
}
auto Add(const llvm::APInt& value) -> void {
contents.push_back(value.getBitWidth());
contents.append(value.getRawData(),
value.getRawData() + value.getNumWords());
}
auto Add(const llvm::APInt& value) -> void { store->AddAPInt(value); }
auto Add(IntId int_id) -> void { Add(sem_ir->ints().Get(int_id)); }
@@ -355,7 +506,7 @@ struct Worklist {
const auto& real = sem_ir->reals().Get(real_id);
Add(real.mantissa);
Add(real.exponent);
contents.push_back(real.is_decimal);
store->AddInteger(real.is_decimal);
}
auto Add(PackageNameId package_id) -> void {
@@ -407,7 +558,7 @@ struct Worklist {
ElementIndex, FloatKind, IntKind, CallParamIndex>)
auto Add(T arg) -> void {
// Index-like ID: just include the value directly.
contents.push_back(arg.index);
store->AddInteger(arg.index);
}
template <typename T>
@@ -424,20 +575,20 @@ struct Worklist {
// Add an instruction argument to the contents of the current instruction.
auto AddWithKind(IdAndKind arg) -> void {
arg.Dispatch<void>([this](auto id) { Add(id); });
arg.Dispatch<void>([&](auto id) { Add(id); });
}
// Ensure all the instructions on the todo list have fingerprints. To avoid a
// re-lookup, returns the fingerprint of the first instruction on the todo
// list, and requires the todo list to be non-empty.
auto Run() -> uint64_t {
auto Run() -> ResultType {
CARBON_CHECK(!todo.empty());
while (true) {
const size_t init_size = todo.size();
auto [next_sem_ir, next] = todo.back();
sem_ir = next_sem_ir;
contents.clear();
store->Prepare();
if (!std::holds_alternative<InstId>(next)) {
// Add the contents of the `next` instruction so they all contribute to
@@ -483,7 +634,7 @@ struct Worklist {
if (auto fingerprint = GetFingerprint(next_sem_ir, next_inst_id)) {
todo.pop_back();
if (todo.empty()) {
return fingerprint;
return *fingerprint;
}
continue;
}
@@ -510,7 +661,7 @@ struct Worklist {
// pop it from the todo list. Otherwise, we leave it on the todo list so
// we can compute its fingerprint once we've finished the work we added.
if (todo.size() == init_size) {
uint64_t fingerprint = Finish();
ResultType fingerprint = Finish();
SetFingerprint(next_sem_ir, next_inst_id, fingerprint);
todo.pop_back();
if (todo.empty()) {
@@ -520,35 +671,54 @@ struct Worklist {
}
}
};
} // namespace
auto InstFingerprinter::GetOrCompute(const File* file, InstId inst_id)
-> uint64_t {
Worklist worklist = {.todo = {{file, inst_id}},
.fingerprints = &fingerprints_};
template <typename StoreT, typename ResultT>
InstFingerprinterTemplate<StoreT, ResultT>::InstFingerprinterTemplate(
int total_ir_count)
: store_(std::make_unique<StoreT>(total_ir_count)) {}
template <typename StoreT, typename ResultT>
InstFingerprinterTemplate<StoreT, ResultT>::~InstFingerprinterTemplate() =
default;
template <typename StoreT, typename ResultT>
auto InstFingerprinterTemplate<StoreT, ResultT>::GetOrCompute(const File* file,
InstId inst_id)
-> ResultT {
Worklist<StoreT> worklist = {.todo = {{file, inst_id}},
.store = store_.get()};
return worklist.Run();
}
auto InstFingerprinter::GetOrCompute(const File* file,
InstBlockId inst_block_id) -> uint64_t {
Worklist worklist = {.todo = {{file, inst_block_id}},
.fingerprints = &fingerprints_};
template <typename StoreT, typename ResultT>
auto InstFingerprinterTemplate<StoreT, ResultT>::GetOrCompute(
const File* file, InstBlockId inst_block_id) -> ResultT {
Worklist<StoreT> worklist = {.todo = {{file, inst_block_id}},
.store = store_.get()};
return worklist.Run();
}
auto InstFingerprinter::GetOrCompute(const File* file, ImplId impl_id)
-> uint64_t {
Worklist worklist = {.todo = {{file, impl_id}},
.fingerprints = &fingerprints_};
template <typename StoreT, typename ResultT>
auto InstFingerprinterTemplate<StoreT, ResultT>::GetOrCompute(const File* file,
ImplId impl_id)
-> ResultT {
Worklist<StoreT> worklist = {.todo = {{file, impl_id}},
.store = store_.get()};
return worklist.Run();
}
auto InstFingerprinter::GetOrCompute(const File* file,
CppOverloadSetId overload_set_id)
-> uint64_t {
Worklist worklist = {.todo = {{file, overload_set_id}},
.fingerprints = &fingerprints_};
template <typename StoreT, typename ResultT>
auto InstFingerprinterTemplate<StoreT, ResultT>::GetOrCompute(
const File* file, CppOverloadSetId overload_set_id) -> ResultT {
Worklist<StoreT> worklist = {.todo = {{file, overload_set_id}},
.store = store_.get()};
return worklist.Run();
}
template class InstFingerprinterTemplate<HashFingerprintStore, uint64_t>;
template class InstFingerprinterTemplate<StringFingerprintStore,
llvm::StringRef>;
} // namespace Carbon::SemIR
+29 -24
View File
@@ -5,52 +5,57 @@
#ifndef CARBON_TOOLCHAIN_SEM_IR_INST_FINGERPRINTER_H_
#define CARBON_TOOLCHAIN_SEM_IR_INST_FINGERPRINTER_H_
#include <memory>
#include <string>
#include "llvm/ADT/StringRef.h"
#include "toolchain/base/fixed_size_value_store.h"
#include "toolchain/sem_ir/file.h"
#include "toolchain/sem_ir/ids.h"
namespace Carbon::SemIR {
class HashFingerprintStore;
class StringFingerprintStore;
// Computes fingerprints for instructions. These fingerprints are intended to be
// stable across compilations and across minor changes to the compiler.
class InstFingerprinter {
template <typename StoreT, typename ResultT>
class InstFingerprinterTemplate {
public:
explicit InstFingerprinter(int total_ir_count)
: fingerprints_(FilesFingerprintStores::MakeWithExplicitSizeFrom(
total_ir_count, [] {
return FingerprintStore::MakeForOverwriteWithExplicitSize(
0, CheckIRId::None);
})) {}
using StoreType = StoreT;
using ResultType = ResultT;
explicit InstFingerprinterTemplate(int total_ir_count);
~InstFingerprinterTemplate();
// Gets or computes a fingerprint for the given instruction.
auto GetOrCompute(const File* file, InstId inst_id) -> uint64_t;
auto GetOrCompute(const File* file, InstId inst_id) -> ResultType;
// Gets or computes a fingerprint for the given instruction block.
auto GetOrCompute(const File* file, InstBlockId inst_block_id) -> uint64_t;
auto GetOrCompute(const File* file, InstBlockId inst_block_id) -> ResultType;
// Gets or computes a fingerprint for the given impl.
auto GetOrCompute(const File* file, ImplId impl_id) -> uint64_t;
auto GetOrCompute(const File* file, ImplId impl_id) -> ResultType;
// Gets or computes a fingerprint for the given C++ overload set.
auto GetOrCompute(const File* file, CppOverloadSetId overload_set_id)
-> uint64_t;
-> ResultType;
private:
// The fingerprint for each instruction that has had its fingerprint computed,
// indexed by the InstId's index.
//
// TODO: Experiment with also caching fingerprints for instruction blocks once
// we can get realistic performance measurements for this. This would simplify
// the `GetOrCompute` overload for `InstBlockId`s, and may save some work if
// the same canonical inst block is used by multiple instructions, for example
// as a specific argument list.
using FingerprintStore =
FixedSizeValueStore<InstId, uint64_t, Tag<CheckIRId>>;
using FilesFingerprintStores =
FixedSizeValueStore<CheckIRId, FingerprintStore>;
FilesFingerprintStores fingerprints_;
std::unique_ptr<StoreT> store_;
};
using HashInstFingerprinter =
InstFingerprinterTemplate<HashFingerprintStore, uint64_t>;
using StringInstFingerprinter =
InstFingerprinterTemplate<StringFingerprintStore, llvm::StringRef>;
using InstFingerprinter = HashInstFingerprinter;
extern template class InstFingerprinterTemplate<HashFingerprintStore, uint64_t>;
extern template class InstFingerprinterTemplate<StringFingerprintStore,
llvm::StringRef>;
} // namespace Carbon::SemIR
#endif // CARBON_TOOLCHAIN_SEM_IR_INST_FINGERPRINTER_H_
+15 -11
View File
@@ -18,6 +18,17 @@
namespace Carbon::SemIR {
Mangler::Mangler(const SemIR::File& sem_ir, int total_ir_count,
bool use_string_fingerprint)
: sem_ir_(sem_ir),
fingerprinter_(
use_string_fingerprint
? std::variant<HashInstFingerprinter, StringInstFingerprinter>(
std::in_place_type<StringInstFingerprinter>, total_ir_count)
: std::variant<HashInstFingerprinter, StringInstFingerprinter>(
std::in_place_type<HashInstFingerprinter>,
total_ir_count)) {}
auto Mangler::MangleNameId(llvm::raw_ostream& os, SemIR::NameId name_id)
-> void {
CARBON_CHECK(name_id.AsIdentifierId().has_value(),
@@ -124,9 +135,7 @@ auto Mangler::MangleInverseQualifiedNameScope(llvm::raw_ostream& os,
}
default: {
// Fall back to including a fingerprint.
llvm::write_hex(
os, fingerprinter_.GetOrCompute(&sem_ir(), self_const_inst_id),
llvm::HexPrintStyle::Lower, 16);
MangleFingerprint(os, &sem_ir(), self_const_inst_id);
break;
}
}
@@ -199,9 +208,7 @@ auto Mangler::Mangle(SemIR::FunctionId function_id,
case SemIR::Function::SpecialFunctionKind::CoreWitness:
os << ".";
llvm::write_hex(
os, fingerprinter_.GetOrCompute(&sem_ir(), function.self_param_id),
llvm::HexPrintStyle::Lower, 16);
MangleFingerprint(os, &sem_ir(), function.self_param_id);
os << ":core";
break;
case SemIR::Function::SpecialFunctionKind::Thunk:
@@ -246,11 +253,8 @@ auto Mangler::MangleSpecificId(llvm::raw_ostream& os,
// but isn't necessarily stable across toolchain changes.
if (specific_id.has_value()) {
os << ".";
llvm::write_hex(
os,
fingerprinter_.GetOrCompute(
&sem_ir(), sem_ir().specifics().Get(specific_id).args_id),
llvm::HexPrintStyle::Lower, 16);
MangleFingerprint(os, &sem_ir(),
sem_ir().specifics().Get(specific_id).args_id);
}
}
+20 -3
View File
@@ -6,6 +6,7 @@
#define CARBON_TOOLCHAIN_SEM_IR_MANGLER_H_
#include <string>
#include <variant>
#include "clang/AST/Mangle.h"
#include "toolchain/sem_ir/constant.h"
@@ -21,8 +22,8 @@ class Mangler {
public:
// Initialize a new Mangler instance for mangling entities within the
// specified `File`.
Mangler(const SemIR::File& sem_ir, int total_ir_count)
: sem_ir_(sem_ir), fingerprinter_(total_ir_count) {}
Mangler(const SemIR::File& sem_ir, int total_ir_count,
bool use_string_fingerprint = false);
// Produce a deterministically unique mangled name for the function specified
// by `function_id` and `specific_id`.
@@ -77,8 +78,24 @@ class Mangler {
return sem_ir().constant_values();
}
template <typename IdT>
auto MangleFingerprint(llvm::raw_ostream& os, const File* file, IdT id)
-> void {
std::visit(
[&](auto& f) -> void {
using ResultT = typename std::decay_t<decltype(f)>::ResultType;
if constexpr (std::is_same_v<ResultT, llvm::StringRef>) {
os << f.GetOrCompute(file, id);
} else {
llvm::write_hex(os, f.GetOrCompute(file, id),
llvm::HexPrintStyle::Lower, 16);
}
},
fingerprinter_);
}
const SemIR::File& sem_ir_;
SemIR::InstFingerprinter fingerprinter_;
std::variant<HashInstFingerprinter, StringInstFingerprinter> fingerprinter_;
};
} // namespace Carbon::SemIR