Change lexical lookup to use an array instead of hash map. (#3582)

This is primarily being done for performance reasons, removing hash
lookups. The increased memory consumption is accepted.

Refactors LexicalLookup out to its own structure.
This commit is contained in:
Jon Ross-Perkins
2024-01-10 23:06:37 +00:00
committed by GitHub
parent 5e004b854e
commit dc75295a72
9 changed files with 151 additions and 72 deletions
+18 -29
View File
@@ -34,7 +34,8 @@ Context::Context(const Lex::TokenizedBuffer& tokens, DiagnosticEmitter& emitter,
inst_block_stack_("inst_block_stack_", sem_ir, vlog_stream),
params_or_args_stack_("params_or_args_stack_", sem_ir, vlog_stream),
args_type_info_stack_("args_type_info_stack_", sem_ir, vlog_stream),
decl_name_stack_(this) {
decl_name_stack_(this),
lexical_lookup_(sem_ir_->identifiers()) {
// Inserts the "Error" and "Type" types as "used types" so that
// canonicalization can skip them. We don't emit either for lowering.
canonical_types_.insert({SemIR::InstId::BuiltinError, SemIR::TypeId::Error});
@@ -54,7 +55,6 @@ auto Context::VerifyOnFinish() -> void {
// various pieces of context go out of scope. At this point, nothing should
// remain.
// node_stack_ will still contain top-level entities.
CARBON_CHECK(name_lookup_.empty()) << name_lookup_.size();
CARBON_CHECK(scope_stack_.empty()) << scope_stack_.size();
CARBON_CHECK(inst_block_stack_.empty()) << inst_block_stack_.size();
CARBON_CHECK(params_or_args_stack_.empty()) << params_or_args_stack_.size();
@@ -157,14 +157,15 @@ auto Context::AddNameToLookup(Parse::NodeId name_node, SemIR::NameId name_id,
if (current_scope().names.insert(name_id).second) {
// TODO: Reject if we previously performed a failed lookup for this name in
// this scope or a scope nested within it.
auto& lexical_results = name_lookup_[name_id];
auto& lexical_results = lexical_lookup_.Get(name_id);
CARBON_CHECK(lexical_results.empty() ||
lexical_results.back().scope_index < current_scope_index())
<< "Failed to clean up after scope nested within the current scope";
lexical_results.push_back(
{.inst_id = target_id, .scope_index = current_scope_index()});
} else {
DiagnoseDuplicateName(name_node, name_lookup_[name_id].back().inst_id);
DiagnoseDuplicateName(name_node,
lexical_lookup_.Get(name_id).back().inst_id);
}
}
@@ -235,11 +236,9 @@ auto Context::LookupNameInDecl(Parse::NodeId /*parse_node*/,
// In this case, we're not in the correct scope to define a member of
// class A, so we should reject, and we achieve this by not finding the
// name A from the outer scope.
if (auto name_it = name_lookup_.find(name_id);
name_it != name_lookup_.end()) {
CARBON_CHECK(!name_it->second.empty())
<< "Should have been erased: " << names().GetFormatted(name_id);
auto result = name_it->second.back();
auto& lexical_results = lexical_lookup_.Get(name_id);
if (!lexical_results.empty()) {
auto result = lexical_results.back();
if (result.scope_index == current_scope_index()) {
ResolveIfLazyImportRef(result.inst_id);
return result.inst_id;
@@ -266,13 +265,8 @@ auto Context::LookupUnqualifiedName(Parse::NodeId parse_node,
// Find the results from enclosing lexical scopes. These will be combined with
// results from non-lexical scopes such as namespaces and classes.
llvm::ArrayRef<LexicalLookupResult> lexical_results;
if (auto name_it = name_lookup_.find(name_id);
name_it != name_lookup_.end()) {
lexical_results = name_it->second;
CARBON_CHECK(!lexical_results.empty())
<< "Should have been erased: " << names().GetFormatted(name_id);
}
llvm::ArrayRef<LexicalLookup::Result> lexical_results =
lexical_lookup_.Get(name_id);
// Walk the non-lexical scopes and perform lookups into each of them.
for (auto [index, name_scope_id] : llvm::reverse(non_lexical_scope_stack_)) {
@@ -300,7 +294,7 @@ auto Context::LookupUnqualifiedName(Parse::NodeId parse_node,
}
// We didn't find anything at all.
if (!name_lookup_has_load_error_) {
if (!lexical_lookup_has_load_error_) {
DiagnoseNameNotFound(parse_node, name_id);
}
return SemIR::InstId::BuiltinError;
@@ -367,17 +361,17 @@ auto Context::LookupQualifiedName(Parse::NodeId parse_node,
auto Context::PushScope(SemIR::InstId scope_inst_id,
SemIR::NameScopeId scope_id,
bool name_lookup_has_load_error) -> void {
bool lexical_lookup_has_load_error) -> void {
scope_stack_.push_back(
{.index = next_scope_index_,
.scope_inst_id = scope_inst_id,
.scope_id = scope_id,
.prev_name_lookup_has_load_error = name_lookup_has_load_error_});
.prev_lexical_lookup_has_load_error = lexical_lookup_has_load_error_});
if (scope_id.is_valid()) {
non_lexical_scope_stack_.push_back({next_scope_index_, scope_id});
}
name_lookup_has_load_error_ |= name_lookup_has_load_error;
lexical_lookup_has_load_error_ |= lexical_lookup_has_load_error;
// TODO: Handle this case more gracefully.
CARBON_CHECK(next_scope_index_.index != std::numeric_limits<int32_t>::max())
@@ -388,18 +382,13 @@ auto Context::PushScope(SemIR::InstId scope_inst_id,
auto Context::PopScope() -> void {
auto scope = scope_stack_.pop_back_val();
name_lookup_has_load_error_ = scope.prev_name_lookup_has_load_error;
lexical_lookup_has_load_error_ = scope.prev_lexical_lookup_has_load_error;
for (const auto& str_id : scope.names) {
auto it = name_lookup_.find(str_id);
CARBON_CHECK(it->second.back().scope_index == scope.index)
auto& lexical_results = lexical_lookup_.Get(str_id);
CARBON_CHECK(lexical_results.back().scope_index == scope.index)
<< "Inconsistent scope index for name " << names().GetFormatted(str_id);
if (it->second.size() == 1) {
// Erase names that no longer resolve.
name_lookup_.erase(it);
} else {
it->second.pop_back();
}
lexical_results.pop_back();
}
if (scope.scope_id.is_valid()) {