From 312d158bfc314690cc3be1e082cab68e9ad73bbf Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 14 Mar 2024 15:57:53 -0700 Subject: [PATCH] Factor out CopyOnWriteBlock. (#3785) Generalize it to also support type blocks. --- toolchain/check/convert.cpp | 56 +++++---------------- toolchain/sem_ir/BUILD | 1 + toolchain/sem_ir/copy_on_write_block.h | 69 ++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 43 deletions(-) create mode 100644 toolchain/sem_ir/copy_on_write_block.h diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index f4134da323af..9f44b2c0a391 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -10,6 +10,7 @@ #include "common/check.h" #include "llvm/ADT/STLExtras.h" #include "toolchain/check/context.h" +#include "toolchain/sem_ir/copy_on_write_block.h" #include "toolchain/sem_ir/file.h" #include "toolchain/sem_ir/inst.h" @@ -194,47 +195,6 @@ static auto ConvertAggregateElement( return Convert(context, node_id, src_elem_id, target); } -namespace { -// A handle to a new block that may be modified, with copy-on-write semantics. -// -// The constructor is given the ID of an existing block that provides the -// initial contents of the new block. The new block is lazily allocated; if no -// modifications have been made, the `id()` function will return the original -// block ID. -// -// This is intended to avoid an unnecessary block allocation in the case where -// the new block ends up being exactly the same as the original block. -class CopyOnWriteBlock { - public: - // Constructs the block. If `source_id` is valid, it is used as the initial - // value of the block. Otherwise, uninitialized storage for `size` elements - // is allocated. - CopyOnWriteBlock(SemIR::File& file, SemIR::InstBlockId source_id, size_t size) - : file_(file), source_id_(source_id) { - if (!source_id_.is_valid()) { - id_ = file_.inst_blocks().AddUninitialized(size); - } - } - - auto id() const -> SemIR::InstBlockId { return id_; } - - auto Set(int i, SemIR::InstId value) -> void { - if (source_id_.is_valid() && file_.inst_blocks().Get(id_)[i] == value) { - return; - } - if (id_ == source_id_) { - id_ = file_.inst_blocks().Add(file_.inst_blocks().Get(source_id_)); - } - file_.inst_blocks().Get(id_)[i] = value; - } - - private: - SemIR::File& file_; - SemIR::InstBlockId source_id_; - SemIR::InstBlockId id_ = source_id_; -}; -} // namespace - // Performs a conversion from a tuple to an array type. This function only // converts the type, and does not perform a final conversion to the requested // expression category. @@ -370,7 +330,12 @@ static auto ConvertTupleToTuple(Context& context, SemIR::TupleType src_type, // Initialize each element of the destination from the corresponding element // of the source. // TODO: Annotate diagnostics coming from here with the element index. - CopyOnWriteBlock new_block(sem_ir, literal_elems_id, src_elem_types.size()); + auto new_block = + literal_elems_id.is_valid() + ? SemIR::CopyOnWriteInstBlock(sem_ir, literal_elems_id) + : SemIR::CopyOnWriteInstBlock( + sem_ir, SemIR::CopyOnWriteInstBlock::UninitializedBlock{ + src_elem_types.size()}); for (auto [i, src_type_id, dest_type_id] : llvm::enumerate(src_elem_types, dest_elem_types)) { // TODO: This call recurses back into conversion. Switch to an iterative @@ -463,7 +428,12 @@ static auto ConvertStructToStructOrClass(Context& context, // Initialize each element of the destination from the corresponding element // of the source. // TODO: Annotate diagnostics coming from here with the element index. - CopyOnWriteBlock new_block(sem_ir, literal_elems_id, src_elem_fields.size()); + auto new_block = + literal_elems_id.is_valid() + ? SemIR::CopyOnWriteInstBlock(sem_ir, literal_elems_id) + : SemIR::CopyOnWriteInstBlock( + sem_ir, SemIR::CopyOnWriteInstBlock::UninitializedBlock{ + src_elem_fields.size()}); for (auto [i, dest_field_id] : llvm::enumerate(dest_elem_fields)) { auto dest_field = sem_ir.insts().GetAs(dest_field_id); diff --git a/toolchain/sem_ir/BUILD b/toolchain/sem_ir/BUILD index cd84308be676..65c830c336c3 100644 --- a/toolchain/sem_ir/BUILD +++ b/toolchain/sem_ir/BUILD @@ -82,6 +82,7 @@ cc_library( hdrs = [ "class.h", "constant.h", + "copy_on_write_block.h", "file.h", "function.h", "impl.h", diff --git a/toolchain/sem_ir/copy_on_write_block.h b/toolchain/sem_ir/copy_on_write_block.h new file mode 100644 index 000000000000..b89ef8ac408f --- /dev/null +++ b/toolchain/sem_ir/copy_on_write_block.h @@ -0,0 +1,69 @@ +// 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_SEM_IR_COPY_ON_WRITE_BLOCK_H_ +#define CARBON_TOOLCHAIN_SEM_IR_COPY_ON_WRITE_BLOCK_H_ + +#include "toolchain/sem_ir/file.h" +#include "toolchain/sem_ir/ids.h" + +namespace Carbon::SemIR { + +// A handle to a new block that may be modified, with copy-on-write semantics. +// +// The constructor is given the ID of an existing block that provides the +// initial contents of the new block. The new block is lazily allocated; if no +// modifications have been made, the `id()` function will return the original +// block ID. +// +// This is intended to avoid an unnecessary block allocation in the case where +// the new block ends up being exactly the same as the original block. +template +class CopyOnWriteBlock { + public: + struct UninitializedBlock { + size_t size; + }; + + // Constructs the block. `source_id` is used as the initial value of the + // block. + CopyOnWriteBlock(SemIR::File& file, BlockIdType source_id) + : file_(file), source_id_(source_id) {} + + // Constructs the block, treating the original block as an uninitialized block + // with `size` elements. + CopyOnWriteBlock(SemIR::File& file, UninitializedBlock uninit) + : file_(file), + source_id_(BlockIdType::Invalid), + id_(file_.inst_blocks().AddUninitialized(uninit.size)) {} + + // Gets a block ID containing the resulting elements. Note that further + // modifications may or may not allocate a new ID, so this should only be + // called once all modifications have been performed. + auto id() const -> BlockIdType { return id_; } + + // Sets the element at index `i` within the block. Lazily allocates a new + // block when the value changes for the first time. + auto Set(int i, typename BlockIdType::ElementType value) -> void { + if (source_id_.is_valid() && (file_.*ValueStore)().Get(id_)[i] == value) { + return; + } + if (id_ == source_id_) { + id_ = (file_.*ValueStore)().Add((file_.*ValueStore)().Get(source_id_)); + } + (file_.*ValueStore)().Get(id_)[i] = value; + } + + private: + SemIR::File& file_; + BlockIdType source_id_; + BlockIdType id_ = source_id_; +}; + +using CopyOnWriteInstBlock = CopyOnWriteBlock; +using CopyOnWriteTypeBlock = CopyOnWriteBlock; + +} // namespace Carbon::SemIR + +#endif // CARBON_TOOLCHAIN_SEM_IR_COPY_ON_WRITE_BLOCK_H_