diff --git a/toolchain/base/BUILD b/toolchain/base/BUILD index c9b1c86667fd..0e4979728118 100644 --- a/toolchain/base/BUILD +++ b/toolchain/base/BUILD @@ -84,6 +84,18 @@ cc_library( ], ) +cc_library( + name = "fixed_size_value_store", + hdrs = ["fixed_size_value_store.h"], + deps = [ + ":mem_usage", + ":value_store", + "//common:check", + "//common:move_only", + "@llvm-project//llvm:Support", + ], +) + cc_library( name = "value_store", hdrs = [ diff --git a/toolchain/base/fixed_size_value_store.h b/toolchain/base/fixed_size_value_store.h new file mode 100644 index 000000000000..862d3421f2d8 --- /dev/null +++ b/toolchain/base/fixed_size_value_store.h @@ -0,0 +1,74 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_BASE_FIXED_SIZE_VALUE_STORE_H_ +#define CARBON_TOOLCHAIN_BASE_FIXED_SIZE_VALUE_STORE_H_ + +#include "common/check.h" +#include "common/move_only.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "toolchain/base/mem_usage.h" +#include "toolchain/base/value_store.h" + +namespace Carbon { + +// A value store with a predetermined size. +template +class FixedSizeValueStore : public MoveOnly> { + public: + using ValueType = ValueStoreTypes::ValueType; + using RefType = ValueStoreTypes::RefType; + using ConstRefType = ValueStoreTypes::ConstRefType; + + // Makes a ValueStore of the specified size, but without initializing values. + // Entries must be set before reading. + static auto MakeForOverwrite(size_t size) -> FixedSizeValueStore { + FixedSizeValueStore store; + store.values_.resize_for_overwrite(size); + return store; + } + + // Makes a ValueStore of the specified size, initialized to a default. + explicit FixedSizeValueStore(size_t size, ValueT default_value) { + values_.resize(size, default_value); + } + + // Sets the value for an ID. + auto Set(IdT id, ValueType value) -> void { + CARBON_DCHECK(id.index >= 0, "{0}", id); + values_[id.index] = value; + } + + // Returns a mutable value for an ID. + auto Get(IdT id) -> RefType { + CARBON_DCHECK(id.index >= 0, "{0}", id); + return values_[id.index]; + } + + // Returns the value for an ID. + auto Get(IdT id) const -> ConstRefType { + CARBON_DCHECK(id.index >= 0, "{0}", id); + return values_[id.index]; + } + + // Collects memory usage of the values. + auto CollectMemUsage(MemUsage& mem_usage, llvm::StringRef label) const + -> void { + mem_usage.Collect(label.str(), values_); + } + + auto size() const -> size_t { return values_.size(); } + + private: + // Allow default construction for `MakeForOverwrite`. + FixedSizeValueStore() = default; + + // Storage for the `ValueT` objects, indexed by the id. + llvm::SmallVector values_; +}; + +} // namespace Carbon + +#endif // CARBON_TOOLCHAIN_BASE_FIXED_SIZE_VALUE_STORE_H_ diff --git a/toolchain/base/value_store.h b/toolchain/base/value_store.h index f721e9ecc42a..9cc1231df758 100644 --- a/toolchain/base/value_store.h +++ b/toolchain/base/value_store.h @@ -30,11 +30,29 @@ namespace Internal { // Used as a parent class for non-printable types. This is just for // std::conditional, not as an API. class ValueStoreNotPrintable {}; + } // namespace Internal template class ValueStoreRange; +// Common calculation for ValueStore types. +template +class ValueStoreTypes { + public: + using ValueType = std::decay_t; + + // Typically we want to use `ValueType&` and `const ValueType& to avoid + // copies, but when the value type is a `StringRef`, we assume external + // storage for the string data and both our value type and ref type will be + // `StringRef`. This will preclude mutation of the string data. + using RefType = std::conditional_t, + llvm::StringRef, ValueType&>; + using ConstRefType = + std::conditional_t, + llvm::StringRef, const ValueType&>; +}; + // A simple wrapper for accumulating values, providing IDs to later retrieve the // value. This does not do deduplication. // @@ -47,17 +65,9 @@ class ValueStore typename IdT::ValueType>, Yaml::Printable>, Internal::ValueStoreNotPrintable> { public: - using ValueType = std::decay_t; - - // Typically we want to use `ValueType&` and `const ValueType& to avoid - // copies, but when the value type is a `StringRef`, we assume external - // storage for the string data and both our value type and ref type will be - // `StringRef`. This will preclude mutation of the string data. - using RefType = std::conditional_t, - llvm::StringRef, ValueType&>; - using ConstRefType = - std::conditional_t, - llvm::StringRef, const ValueType&>; + using ValueType = ValueStoreTypes::ValueType; + using RefType = ValueStoreTypes::RefType; + using ConstRefType = ValueStoreTypes::ConstRefType; ValueStore() = default; @@ -211,9 +221,9 @@ class ValueStoreRange { template class CanonicalValueStore { public: - using ValueType = typename IdT::ValueType; - using RefType = typename ValueStore::RefType; - using ConstRefType = typename ValueStore::ConstRefType; + using ValueType = ValueStoreTypes::ValueType; + using RefType = ValueStoreTypes::RefType; + using ConstRefType = ValueStoreTypes::ConstRefType; // Stores a canonical copy of the value and returns an ID to reference it. auto Add(ValueType value) -> IdT; @@ -313,8 +323,8 @@ auto CanonicalValueStore::Reserve(size_t size) -> void { template class RelationalValueStore { public: - using ValueType = IdT::ValueType; - using ConstRefType = ValueStore::ConstRefType; + using ValueType = ValueStoreTypes::ValueType; + using ConstRefType = ValueStoreTypes::ConstRefType; // Given the related ID and a value, stores the value and returns a mapped ID // to reference it in the store. diff --git a/toolchain/parse/BUILD b/toolchain/parse/BUILD index c5488815147d..e6455f76a14c 100644 --- a/toolchain/parse/BUILD +++ b/toolchain/parse/BUILD @@ -156,6 +156,7 @@ cc_library( "//common:find", "//common:ostream", "//common:struct_reflection", + "//toolchain/base:fixed_size_value_store", "//toolchain/base:value_store", "//toolchain/lex:token_index", "//toolchain/lex:tokenized_buffer", diff --git a/toolchain/parse/tree_and_subtrees.cpp b/toolchain/parse/tree_and_subtrees.cpp index 82bae0555268..134545e7c246 100644 --- a/toolchain/parse/tree_and_subtrees.cpp +++ b/toolchain/parse/tree_and_subtrees.cpp @@ -7,15 +7,16 @@ #include #include +#include "toolchain/base/fixed_size_value_store.h" #include "toolchain/lex/token_index.h" namespace Carbon::Parse { TreeAndSubtrees::TreeAndSubtrees(const Lex::TokenizedBuffer& tokens, const Tree& tree) - : tokens_(&tokens), tree_(&tree) { - subtree_sizes_.reserve(tree_->size()); - + : tokens_(&tokens), + tree_(&tree), + subtree_sizes_(SubtreeSizeStore::MakeForOverwrite(tree_->size())) { // A stack of nodes which haven't yet been used as children. llvm::SmallVector size_stack; for (auto n : tree.postorder()) { @@ -31,7 +32,7 @@ TreeAndSubtrees::TreeAndSubtrees(const Lex::TokenizedBuffer& tokens, for (auto i : llvm::seq(kind.child_count())) { auto child = size_stack.pop_back_val(); CARBON_CHECK(static_cast(child.index) < subtree_sizes_.size()); - size += subtree_sizes_[child.index]; + size += subtree_sizes_.Get(child); if (kind.has_bracket() && i == kind.child_count() - 1) { CARBON_CHECK(kind.bracket() == tree.node_kind(child), "Node {0} with child count {1} needs bracket {2}, found " @@ -45,28 +46,26 @@ TreeAndSubtrees::TreeAndSubtrees(const Lex::TokenizedBuffer& tokens, CARBON_CHECK(!size_stack.empty(), "Node {0} is missing bracket {1}", kind, kind.bracket()); auto child = size_stack.pop_back_val(); - size += subtree_sizes_[child.index]; + size += subtree_sizes_.Get(child); if (kind.bracket() == tree.node_kind(child)) { break; } } } size_stack.push_back(n); - subtree_sizes_.push_back(size); + subtree_sizes_.Set(n, size); } - CARBON_CHECK(static_cast(subtree_sizes_.size()) == tree_->size()); - // Remaining nodes should all be roots in the tree; make sure they line up. CARBON_CHECK( size_stack.back().index == static_cast(tree_->size()) - 1, "{0} {1}", size_stack.back(), tree_->size() - 1); int prev_index = -1; for (const auto& n : size_stack) { - CARBON_CHECK(n.index - subtree_sizes_[n.index] == prev_index, + CARBON_CHECK(n.index - subtree_sizes_.Get(n) == prev_index, "NodeId {0} is a root {1} with subtree_size {2}, but previous " "root was at {3}.", - n, tree_->node_kind(n), subtree_sizes_[n.index], prev_index); + n, tree_->node_kind(n), subtree_sizes_.Get(n), prev_index); prev_index = n.index; } } @@ -117,14 +116,14 @@ auto TreeAndSubtrees::postorder(NodeId n) const -> llvm::iterator_range { // The postorder ends after this node, the root, and begins at the begin of // its subtree. - int begin_index = n.index - subtree_sizes_[n.index] + 1; + int begin_index = n.index - subtree_sizes_.Get(n) + 1; return Tree::PostorderIterator::MakeRange(NodeId(begin_index), n); } auto TreeAndSubtrees::children(NodeId n) const -> llvm::iterator_range { CARBON_CHECK(n.has_value()); - int end_index = n.index - subtree_sizes_[n.index]; + int end_index = n.index - subtree_sizes_.Get(n); return llvm::iterator_range( SiblingIterator(*this, NodeId(n.index - 1)), SiblingIterator(*this, NodeId(end_index))); @@ -153,8 +152,8 @@ auto TreeAndSubtrees::PrintNode(llvm::raw_ostream& output, NodeId n, int depth, output << ", has_error: yes"; } - if (subtree_sizes_[n.index] > 1) { - output << ", subtree_size: " << subtree_sizes_[n.index]; + if (subtree_sizes_.Get(n) > 1) { + output << ", subtree_size: " << subtree_sizes_.Get(n); if (preorder) { output << ", children: [\n"; return true; diff --git a/toolchain/parse/tree_and_subtrees.h b/toolchain/parse/tree_and_subtrees.h index 46eb6e58cf94..95ed081a533f 100644 --- a/toolchain/parse/tree_and_subtrees.h +++ b/toolchain/parse/tree_and_subtrees.h @@ -6,6 +6,7 @@ #define CARBON_TOOLCHAIN_PARSE_TREE_AND_SUBTREES_H_ #include "llvm/ADT/SmallVector.h" +#include "toolchain/base/fixed_size_value_store.h" #include "toolchain/lex/token_index.h" #include "toolchain/parse/tree.h" @@ -184,7 +185,8 @@ class TreeAndSubtrees { // first child of its parent, this will be an offset to the node's parent's // next sibling, or if it the parent is also a first child, the grandparent's // next sibling, and so on. - llvm::SmallVector subtree_sizes_; + using SubtreeSizeStore = FixedSizeValueStore; + SubtreeSizeStore subtree_sizes_; }; // A standard signature for a callback to support lazy construction. @@ -218,7 +220,7 @@ class TreeAndSubtrees::SiblingIterator using iterator_facade_base::operator++; auto operator++() -> SiblingIterator& { - node_.index -= tree_->subtree_sizes_[node_.index]; + node_.index -= tree_->subtree_sizes_.Get(node_); return *this; } diff --git a/toolchain/sem_ir/BUILD b/toolchain/sem_ir/BUILD index 4c6c5ac58c69..bf79e0cc7fc8 100644 --- a/toolchain/sem_ir/BUILD +++ b/toolchain/sem_ir/BUILD @@ -184,6 +184,7 @@ cc_library( ":typed_insts", "//common:concepts", "//common:ostream", + "//toolchain/base:fixed_size_value_store", "//toolchain/base:kind_switch", "//toolchain/base:shared_value_stores", "//toolchain/lex:tokenized_buffer", diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index 64977729c6c3..3369a4df93f4 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -11,6 +11,7 @@ #include "llvm/ADT/Sequence.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/SaveAndRestore.h" +#include "toolchain/base/fixed_size_value_store.h" #include "toolchain/base/kind_switch.h" #include "toolchain/base/shared_value_stores.h" #include "toolchain/lex/tokenized_buffer.h" @@ -40,12 +41,10 @@ Formatter::Formatter(const File* sem_ir, inst_namer_(sem_ir_), get_tree_and_subtrees_(get_tree_and_subtrees), include_ir_in_dumps_(include_ir_in_dumps), - use_dump_sem_ir_ranges_(use_dump_sem_ir_ranges) { - // Create a placeholder visible chunk and assign it to all instructions that - // don't have a chunk of their own. - auto first_chunk = AddChunkNoFlush(true); - tentative_inst_chunks_.resize(sem_ir_->insts().size(), first_chunk); - + use_dump_sem_ir_ranges_(use_dump_sem_ir_ranges), + // Create a placeholder visible chunk and assign it to all instructions + // that don't have a chunk of their own. + tentative_inst_chunks_(sem_ir_->insts().size(), AddChunkNoFlush(true)) { if (use_dump_sem_ir_ranges_) { ComputeNodeParents(); } @@ -54,7 +53,7 @@ Formatter::Formatter(const File* sem_ir, for (auto inst_id : llvm::concat( sem_ir_->constants().array_ref(), sem_ir_->inst_blocks().Get(InstBlockId::Imports))) { - tentative_inst_chunks_[inst_id.index] = AddChunkNoFlush(false); + tentative_inst_chunks_.Set(inst_id, AddChunkNoFlush(false)); } // Create a real chunk for the start of the output. @@ -103,11 +102,12 @@ auto Formatter::Format() -> void { } auto Formatter::ComputeNodeParents() -> void { - CARBON_CHECK(node_parents_.empty()); - node_parents_.resize(sem_ir_->parse_tree().size(), Parse::NodeId::None); + CARBON_CHECK(!node_parents_); + node_parents_ = + NodeParentStore(sem_ir_->parse_tree().size(), Parse::NodeId::None); for (auto n : sem_ir_->parse_tree().postorder()) { for (auto child : get_tree_and_subtrees_().children(n)) { - node_parents_[child.index] = n; + node_parents_->Set(child, n); } } } @@ -215,7 +215,7 @@ auto Formatter::ShouldFormatEntity(InstId decl_id) -> bool { // body. auto end_node_id = loc_id.node_id(); if (IsDefinitionStart(sem_ir_->parse_tree().node_kind(end_node_id))) { - end_node_id = node_parents_[end_node_id.index]; + end_node_id = node_parents_->Get(end_node_id); } Lex::InclusiveTokenRange range = { @@ -313,7 +313,7 @@ auto Formatter::FormatTopLevelScopeIfUsed(InstNamer::ScopeId scope_id, if (use_tentative_output_scopes) { // This is for constants and imports. These use tentative logic to // determine whether an instruction is printed. - TentativeOutputScope scope(*this, tentative_inst_chunks_[inst_id.index]); + TentativeOutputScope scope(*this, tentative_inst_chunks_.Get(inst_id)); FormatInst(inst_id); } else if (ShouldFormatInst(inst_id)) { // This is for the file scope. It uses only the range-based filtering. @@ -1336,7 +1336,7 @@ auto Formatter::FormatName(NameId id) -> void { auto Formatter::FormatName(InstId id) -> void { if (id.has_value()) { - IncludeChunkInOutput(tentative_inst_chunks_[id.index]); + IncludeChunkInOutput(tentative_inst_chunks_.Get(id)); } out_ << inst_namer_.GetNameFor(scope_, id); } diff --git a/toolchain/sem_ir/formatter.h b/toolchain/sem_ir/formatter.h index c9f986c0b7b6..376717d52544 100644 --- a/toolchain/sem_ir/formatter.h +++ b/toolchain/sem_ir/formatter.h @@ -9,6 +9,7 @@ #include "common/concepts.h" #include "llvm/Support/raw_ostream.h" +#include "toolchain/base/fixed_size_value_store.h" #include "toolchain/parse/tree_and_subtrees.h" #include "toolchain/sem_ir/file.h" #include "toolchain/sem_ir/inst_namer.h" @@ -375,13 +376,13 @@ class Formatter { llvm::StringRef pending_imported_from_; // Indexes of chunks of output that should be included when an instruction is - // referenced, indexed by the instruction's index. This is resized in advance - // to the correct size. - llvm::SmallVector tentative_inst_chunks_; + // referenced, indexed by the instruction's index. + FixedSizeValueStore tentative_inst_chunks_; // Maps nodes to their parents. Only set when dump ranges are in use, because // the parents aren't used otherwise. - llvm::SmallVector node_parents_; + using NodeParentStore = FixedSizeValueStore; + std::optional node_parents_; }; template