diff --git a/toolchain/check/check.cpp b/toolchain/check/check.cpp index b9f0023d2bd3..a52f32aab27c 100644 --- a/toolchain/check/check.cpp +++ b/toolchain/check/check.cpp @@ -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(); } } diff --git a/toolchain/check/check.h b/toolchain/check/check.h index 22696d9e1088..668f2962b113 100644 --- a/toolchain/check/check.h +++ b/toolchain/check/check.h @@ -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 diff --git a/toolchain/check/check_unit.cpp b/toolchain/check/check_unit.cpp index 55f3ebb375dc..c4394b8109e3 100644 --- a/toolchain/check/check_unit.cpp +++ b/toolchain/check/check_unit.cpp @@ -63,7 +63,7 @@ CheckUnit::CheckUnit( llvm::IntrusiveRefCntPtr fs, llvm::LLVMContext* llvm_context, std::shared_ptr 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"); diff --git a/toolchain/check/check_unit.h b/toolchain/check/check_unit.h index 5c937c520d99..d961c81dd8cd 100644 --- a/toolchain/check/check_unit.h +++ b/toolchain/check/check_unit.h @@ -129,7 +129,7 @@ class CheckUnit { llvm::IntrusiveRefCntPtr fs, llvm::LLVMContext* llvm_context, std::shared_ptr 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; diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 324b8cb272a3..a14a940e2f08 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -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); diff --git a/toolchain/check/context.h b/toolchain/check/context.h index c01f94ed5c61..cdab6891f5dd 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -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 = { diff --git a/toolchain/check/cpp/export.cpp b/toolchain/check/cpp/export.cpp index 96db62126188..e92d221a1615 100644 --- a/toolchain/check/cpp/export.cpp +++ b/toolchain/check/cpp/export.cpp @@ -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)); diff --git a/toolchain/driver/compile_subcommand.cpp b/toolchain/driver/compile_subcommand.cpp index 0c3b7a1b8163..1b627d14676d 100644 --- a/toolchain/driver/compile_subcommand.cpp +++ b/toolchain/driver/compile_subcommand.cpp @@ -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(); diff --git a/toolchain/driver/compile_subcommand.h b/toolchain/driver/compile_subcommand.h index aee3765be4da..c1042c990e4d 100644 --- a/toolchain/driver/compile_subcommand.h +++ b/toolchain/driver/compile_subcommand.h @@ -71,6 +71,8 @@ struct CompileOptions { llvm::SmallVector exclude_dump_file_prefixes; llvm::StringRef sem_ir_crash_dump; + + bool mangle_string_fingerprint = false; }; // Implements the compile subcommand of the driver. diff --git a/toolchain/lower/context.cpp b/toolchain/lower/context.cpp index 8e1e6f9f7989..37d0b5db6deb 100644 --- a/toolchain/lower/context.cpp +++ b/toolchain/lower/context.cpp @@ -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_)) { } diff --git a/toolchain/lower/context.h b/toolchain/lower/context.h index cdd90288b4fa..74819ed6f8f5 100644 --- a/toolchain/lower/context.h +++ b/toolchain/lower/context.h @@ -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>; diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index af4f13493898..b30af36d07aa 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -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() diff --git a/toolchain/lower/lower.cpp b/toolchain/lower/lower.cpp index 7d61a3416379..5101dd3cf83c 100644 --- a/toolchain/lower/lower.cpp +++ b/toolchain/lower/lower.cpp @@ -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. diff --git a/toolchain/lower/options.h b/toolchain/lower/options.h index 8a1a8d822768..e5097a1db356 100644 --- a/toolchain/lower/options.h +++ b/toolchain/lower/options.h @@ -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 diff --git a/toolchain/lower/testdata/packages/imported_package_mangle_string.carbon b/toolchain/lower/testdata/packages/imported_package_mangle_string.carbon new file mode 100644 index 000000000000..26a936280f8e --- /dev/null +++ b/toolchain/lower/testdata/packages/imported_package_mangle_string.carbon @@ -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,{},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,{},N,{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,{},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,{},N,{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,{},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,{},N,{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,{},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,{},N,{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) diff --git a/toolchain/sem_ir/inst_fingerprinter.cpp b/toolchain/sem_ir/inst_fingerprinter.cpp index cbb88e699474..db38ded38c26 100644 --- a/toolchain/sem_ir/inst_fingerprinter.cpp +++ b/toolchain/sem_ir/inst_fingerprinter.cpp @@ -5,6 +5,7 @@ #include "toolchain/sem_ir/inst_fingerprinter.h" #include +#include #include #include @@ -23,49 +24,38 @@ namespace Carbon::SemIR { -namespace { -struct Worklist { - using FingerprintStore = - FixedSizeValueStore>; - using FilesFingerprintStores = - FixedSizeValueStore; +// 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>> - 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 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 { + 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>; + using FilesFingerprintStores = + FixedSizeValueStore; + FilesFingerprintStores fingerprints_; + llvm::SmallVector 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 { + 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::move(str))); + return *entry; + } + + using FingerprintStore = + FixedSizeValueStore>; + using FilesFingerprintStores = + FixedSizeValueStore; + FilesFingerprintStores fingerprints_; + llvm::SmallVector contents_; + llvm::SmallVector> allocated_strings_; +}; + +namespace { + +template +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>> + 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 { + 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 auto AddBlock(llvm::ArrayRef 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 @@ -424,20 +575,20 @@ struct Worklist { // Add an instruction argument to the contents of the current instruction. auto AddWithKind(IdAndKind arg) -> void { - arg.Dispatch([this](auto id) { Add(id); }); + arg.Dispatch([&](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(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 +InstFingerprinterTemplate::InstFingerprinterTemplate( + int total_ir_count) + : store_(std::make_unique(total_ir_count)) {} + +template +InstFingerprinterTemplate::~InstFingerprinterTemplate() = + default; + +template +auto InstFingerprinterTemplate::GetOrCompute(const File* file, + InstId inst_id) + -> ResultT { + Worklist 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 +auto InstFingerprinterTemplate::GetOrCompute( + const File* file, InstBlockId inst_block_id) -> ResultT { + Worklist 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 +auto InstFingerprinterTemplate::GetOrCompute(const File* file, + ImplId impl_id) + -> ResultT { + Worklist 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 +auto InstFingerprinterTemplate::GetOrCompute( + const File* file, CppOverloadSetId overload_set_id) -> ResultT { + Worklist worklist = {.todo = {{file, overload_set_id}}, + .store = store_.get()}; return worklist.Run(); } +template class InstFingerprinterTemplate; +template class InstFingerprinterTemplate; + } // namespace Carbon::SemIR diff --git a/toolchain/sem_ir/inst_fingerprinter.h b/toolchain/sem_ir/inst_fingerprinter.h index f20214b55bc7..34872c31cd20 100644 --- a/toolchain/sem_ir/inst_fingerprinter.h +++ b/toolchain/sem_ir/inst_fingerprinter.h @@ -5,52 +5,57 @@ #ifndef CARBON_TOOLCHAIN_SEM_IR_INST_FINGERPRINTER_H_ #define CARBON_TOOLCHAIN_SEM_IR_INST_FINGERPRINTER_H_ +#include +#include + +#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 +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>; - using FilesFingerprintStores = - FixedSizeValueStore; - FilesFingerprintStores fingerprints_; + std::unique_ptr store_; }; +using HashInstFingerprinter = + InstFingerprinterTemplate; +using StringInstFingerprinter = + InstFingerprinterTemplate; +using InstFingerprinter = HashInstFingerprinter; + +extern template class InstFingerprinterTemplate; +extern template class InstFingerprinterTemplate; + } // namespace Carbon::SemIR #endif // CARBON_TOOLCHAIN_SEM_IR_INST_FINGERPRINTER_H_ diff --git a/toolchain/sem_ir/mangler.cpp b/toolchain/sem_ir/mangler.cpp index d1ae87a96b5f..ce83709ca3bd 100644 --- a/toolchain/sem_ir/mangler.cpp +++ b/toolchain/sem_ir/mangler.cpp @@ -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( + std::in_place_type, total_ir_count) + : std::variant( + std::in_place_type, + 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); } } diff --git a/toolchain/sem_ir/mangler.h b/toolchain/sem_ir/mangler.h index dc8398c8289b..120e1eb03b24 100644 --- a/toolchain/sem_ir/mangler.h +++ b/toolchain/sem_ir/mangler.h @@ -6,6 +6,7 @@ #define CARBON_TOOLCHAIN_SEM_IR_MANGLER_H_ #include +#include #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 + auto MangleFingerprint(llvm::raw_ostream& os, const File* file, IdT id) + -> void { + std::visit( + [&](auto& f) -> void { + using ResultT = typename std::decay_t::ResultType; + if constexpr (std::is_same_v) { + 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 fingerprinter_; }; } // namespace Carbon::SemIR