mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-11-15 22:28:14 +00:00
When unqualified lookup finds a name from an enclosing generic, create a `specific_constant` if it's a constant, or a `specific_inst` otherwise, to refer to that name from the inner generic. This allows consumers of generic instructions to always use a specific of the current scope's generic when accessing the constant value or type of a dependent instruction. Fixes crashes in dominance and in `GetExprCategory` caused by using specifics for the wrong generic. Assisted-by: Gemini via Antigravity
727 lines
27 KiB
C++
727 lines
27 KiB
C++
// 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 "toolchain/sem_ir/dominance.h"
|
|
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
#include "common/check.h"
|
|
#include "common/error.h"
|
|
#include "common/map.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/ADT/BitVector.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/Sequence.h"
|
|
#include "llvm/ADT/SmallBitVector.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "toolchain/base/grouped_value_store.h"
|
|
#include "toolchain/base/id_tag.h"
|
|
#include "toolchain/base/index_base.h"
|
|
#include "toolchain/base/kind_switch.h"
|
|
#include "toolchain/sem_ir/file.h"
|
|
#include "toolchain/sem_ir/function.h"
|
|
#include "toolchain/sem_ir/generic.h"
|
|
#include "toolchain/sem_ir/id_kind.h"
|
|
#include "toolchain/sem_ir/ids.h"
|
|
#include "toolchain/sem_ir/inst.h"
|
|
#include "toolchain/sem_ir/typed_insts.h"
|
|
|
|
namespace Carbon::SemIR {
|
|
namespace {
|
|
|
|
// The position of a block within a function's list of body blocks. This is used
|
|
// to index the control flow graph and dominator tree built for the function.
|
|
struct BlockIndex : public IndexBase<BlockIndex> {
|
|
// Only used when a `BlockIndex` is printed, which is useful when debugging.
|
|
[[maybe_unused]] static constexpr llvm::StringLiteral Label = "block";
|
|
|
|
using IndexBase::IndexBase;
|
|
};
|
|
|
|
// Execution of a function body starts in its first block.
|
|
constexpr BlockIndex EntryBlockIndex(0);
|
|
|
|
// A step in the walk over a function's dominator tree.
|
|
struct WalkStep {
|
|
// Returns a step that verifies the instructions in `block_index` and queues
|
|
// up the blocks it dominates.
|
|
static auto EnterBlock(BlockIndex block_index) -> WalkStep {
|
|
return {.block_index = block_index, .scope_start = 0};
|
|
}
|
|
|
|
// Returns a step that leaves the scope of a block that was entered when the
|
|
// list of evaluated instructions had size `scope_start`.
|
|
static auto LeaveBlock(int scope_start) -> WalkStep {
|
|
return {.block_index = BlockIndex::None, .scope_start = scope_start};
|
|
}
|
|
|
|
// The block to enter, or `None` if this step leaves a block instead.
|
|
BlockIndex block_index;
|
|
// The number of instructions that had been evaluated when the block being
|
|
// left was entered. Only used when leaving a block.
|
|
int scope_start;
|
|
};
|
|
|
|
// Returns the block that `inst` transfers control to, or `InstBlockId::None` if
|
|
// `inst` is not a branch.
|
|
auto GetBranchTargetId(Inst inst) -> InstBlockId {
|
|
if (auto branch = inst.TryAs<AnyBranch>()) {
|
|
return branch->target_id;
|
|
}
|
|
return InstBlockId::None;
|
|
}
|
|
|
|
// A set of the instructions in a file, holding one bit per instruction.
|
|
//
|
|
// Creating one of these costs a bit per instruction in the file, so they are
|
|
// created once for the file rather than once per function.
|
|
class InstSet {
|
|
public:
|
|
explicit InstSet(const File& file)
|
|
: tag_(file.insts().GetIdTag()), bits_(file.insts().size()) {}
|
|
|
|
// Adds `inst_id`, returning whether it wasn't already present.
|
|
auto Insert(InstId inst_id) -> bool {
|
|
int32_t index = tag_.Remove(inst_id);
|
|
if (bits_.test(index)) {
|
|
return false;
|
|
}
|
|
bits_.set(index);
|
|
return true;
|
|
}
|
|
|
|
auto Erase(InstId inst_id) -> void { bits_.reset(tag_.Remove(inst_id)); }
|
|
|
|
auto Contains(InstId inst_id) const -> bool {
|
|
return bits_.test(tag_.Remove(inst_id));
|
|
}
|
|
|
|
private:
|
|
// Instruction IDs are tagged, so the tag is removed to recover the index of
|
|
// the instruction, which is the index of its bit.
|
|
InstStore::IdTagType tag_;
|
|
llvm::BitVector bits_;
|
|
};
|
|
|
|
// Collects the instructions that are referenced from function bodies despite
|
|
// being evaluated at file scope, or not evaluated at all, into `decl_insts`.
|
|
//
|
|
// TODO: These are pre-existing dominance violations, not genuine exemptions:
|
|
//
|
|
// - File-scope instructions are evaluated in `__global_init`, if at all, so
|
|
// they don't dominate uses in another function.
|
|
// - A `let` in a class body produces a `wrapper_binding` that isn't evaluated
|
|
// in any function, but a qualified name reference to it can appear in one.
|
|
// For example, from the `public_global_access` case in
|
|
// `check/testdata/class/access/access_modifiers.carbon`:
|
|
//
|
|
// class A { let x: i32 = 5; }
|
|
// let x: i32 = A.x;
|
|
//
|
|
// Here the `name_ref` for `A.x` is in `__global_init`, and the
|
|
// `wrapper_binding` for `x` is only in `A`'s body block.
|
|
//
|
|
// Lowering only works today because such uses either happen to be constant or
|
|
// are never lowered. Remove this allowlist and diagnose the uses instead once
|
|
// global initialization semantics and the member reference model are settled.
|
|
auto CollectDeclInsts(const File& file, InstSet& decl_insts) -> void {
|
|
auto add_block = [&](InstBlockId block_id) {
|
|
if (block_id.has_value()) {
|
|
for (InstId inst_id : file.inst_blocks().Get(block_id)) {
|
|
if (inst_id.has_value()) {
|
|
decl_insts.Insert(inst_id);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
add_block(file.top_inst_block_id());
|
|
for (const auto& class_info : file.classes().values()) {
|
|
add_block(class_info.body_block_id);
|
|
}
|
|
}
|
|
|
|
// The specifics of each generic in a file.
|
|
using SpecificsByGeneric =
|
|
GroupedValueStore<GenericId, SpecificId, Tag<CheckIRId>>;
|
|
|
|
// Collects the specifics in `file`, grouped by the generic they're a specific
|
|
// of. A specific whose definition has never been resolved, or whose resolution
|
|
// failed, doesn't have body instructions to check, so is omitted.
|
|
//
|
|
// This grouping is built once for the file so that each generic function can
|
|
// find its own specifics without scanning all of them.
|
|
//
|
|
// TODO: Consider moving this to `File` or `SpecificStore` if other passes need
|
|
// to look up all specifics of a generic.
|
|
auto CollectSpecifics(const File& file) -> SpecificsByGeneric {
|
|
return SpecificsByGeneric(file.generics(), [&](auto add) {
|
|
for (const auto& [specific_id, specific] : file.specifics().enumerate()) {
|
|
if (!specific.definition_block_id.has_value() || specific.HasError()) {
|
|
continue;
|
|
}
|
|
add(specific.generic_id, specific_id);
|
|
}
|
|
});
|
|
}
|
|
|
|
// The dominator tree of a function body: the blocks that each block
|
|
// immediately dominates, indexed by `BlockIndex`.
|
|
// TODO: Explore replacing this with a flattened vector plus a vector of
|
|
// starting indexes to reduce the number of heap allocations required and the
|
|
// vector overhead.
|
|
using DominatorTree = llvm::SmallVector<llvm::SmallVector<BlockIndex, 2>>;
|
|
|
|
// Builds the control flow graph of a function body and its dominator tree.
|
|
//
|
|
// These depend only on the branches between the function's body blocks, which
|
|
// are the same in every specific of a generic function: a block spliced into
|
|
// the body never contains control flow. So the tree is built once per function
|
|
// and shared by the verification of each of its specifics.
|
|
class DominatorTreeBuilder {
|
|
public:
|
|
explicit DominatorTreeBuilder(const File& file, const Function& function)
|
|
: file_(file),
|
|
function_(function),
|
|
body_blocks_(function.body_block_ids) {}
|
|
|
|
// Returns the dominator tree of the function body, diagnosing a branch that
|
|
// leaves the body and blocks that are unreachable from the entry block.
|
|
auto Build() -> ErrorOr<DominatorTree>;
|
|
|
|
private:
|
|
// Builds `successors_` and `predecessors_` from the branches in each block.
|
|
auto BuildControlFlowGraph() -> ErrorOr<Success>;
|
|
|
|
// Appends the blocks reachable from the entry block to `post_order`, in
|
|
// post-order, and marks each of them `visited`.
|
|
auto BuildPostOrder(llvm::SmallBitVector& visited,
|
|
llvm::SmallVectorImpl<BlockIndex>& post_order) -> void;
|
|
|
|
auto num_blocks() const -> int { return body_blocks_.size(); }
|
|
|
|
const File& file_;
|
|
const Function& function_;
|
|
llvm::ArrayRef<InstBlockId> body_blocks_;
|
|
|
|
// The index of each block in `body_blocks_`.
|
|
Map<InstBlockId, BlockIndex> block_indexes_;
|
|
|
|
// The control flow graph, indexed by `BlockIndex`.
|
|
llvm::SmallVector<llvm::SmallVector<BlockIndex, 2>> successors_;
|
|
llvm::SmallVector<llvm::SmallVector<BlockIndex, 2>> predecessors_;
|
|
};
|
|
|
|
// Verifies that every operand of every instruction in one function body is
|
|
// dominated by an evaluation of that operand.
|
|
//
|
|
// This walks the dominator tree of the function's control flow graph, tracking
|
|
// the set of instructions whose evaluations dominate the point currently being
|
|
// checked. An instruction joins that set when its evaluation is reached, and
|
|
// leaves it again when the walk leaves the blocks that the evaluation
|
|
// dominates.
|
|
//
|
|
// TODO: Improve LLVM's GenericDomTree implementation so that it's compatible
|
|
// with our graph representation, then rewrite this to use that rather than
|
|
// implementing our own dominator tree construction. Currently, GenericDomTree
|
|
// requires a pointer-based data structure, and building such a data structure
|
|
// introduces a substantial performance overhead compared to running dominator
|
|
// tree construction directly on our SemIR representation.
|
|
class DominanceVerifier {
|
|
public:
|
|
explicit DominanceVerifier(const File& file, const InstSet& decl_insts,
|
|
const Function& function,
|
|
const DominatorTree& dom_tree, InstSet& evaluated,
|
|
SpecificId specific_id)
|
|
: file_(file),
|
|
decl_insts_(decl_insts),
|
|
function_(function),
|
|
dom_tree_(dom_tree),
|
|
specific_id_(specific_id),
|
|
body_blocks_(function.body_block_ids),
|
|
evaluated_(evaluated) {}
|
|
|
|
auto Verify() -> ErrorOr<Success>;
|
|
|
|
private:
|
|
// Verifies every block, walking the dominator tree from the entry block.
|
|
auto VerifyBlocks() -> ErrorOr<Success>;
|
|
|
|
// Verifies the operands of `inst_id`, then records `inst_id`, along with any
|
|
// instructions it splices into the enclosing block, as evaluated.
|
|
auto VerifyAndRecordInst(InstId root_inst_id, BlockIndex block_index)
|
|
-> ErrorOr<Success>;
|
|
|
|
// Verifies a single operand of `user_id`.
|
|
auto VerifyArg(InstId user_id, IdAndKind arg, BlockIndex block_index)
|
|
-> ErrorOr<Success>;
|
|
|
|
// Verifies that `operand_id`, used by `user_id` in `block_index`, is either
|
|
// constant or dominated by an evaluation.
|
|
auto VerifyOperand(InstId user_id, InstId operand_id, BlockIndex block_index)
|
|
-> ErrorOr<Success>;
|
|
|
|
// Records that `inst_id` is evaluated at the point currently being verified.
|
|
auto RecordEvaluated(InstId inst_id) -> void;
|
|
|
|
// Records that every instruction in `block_id`, if it has a value, is
|
|
// evaluated at the point currently being verified.
|
|
auto RecordEvaluatedBlock(InstBlockId block_id) -> void;
|
|
|
|
// Returns the instruction that `splice` splices in, or `InstId::None` if that
|
|
// can't be determined.
|
|
auto GetSplicedInstId(SpliceInst splice) const -> InstId;
|
|
|
|
const File& file_;
|
|
const InstSet& decl_insts_;
|
|
const Function& function_;
|
|
const DominatorTree& dom_tree_;
|
|
SpecificId specific_id_;
|
|
llvm::ArrayRef<InstBlockId> body_blocks_;
|
|
|
|
// The instructions whose evaluations dominate the point currently being
|
|
// verified, and the order in which they were added, so that they can be
|
|
// removed again when leaving a block.
|
|
//
|
|
// `evaluated_` is owned by the caller and shared between functions, because
|
|
// creating one costs a bit per instruction in the whole file. `Verify` leaves
|
|
// it empty.
|
|
InstSet& evaluated_;
|
|
llvm::SmallVector<InstId> evaluated_order_;
|
|
};
|
|
|
|
auto DominanceVerifier::Verify() -> ErrorOr<Success> {
|
|
CARBON_CHECK(!body_blocks_.empty());
|
|
|
|
// Parameters and other instructions from the function declaration are
|
|
// evaluated before the body begins, so they dominate the whole body.
|
|
RecordEvaluatedBlock(function_.call_params_id);
|
|
RecordEvaluatedBlock(function_.call_param_patterns_id);
|
|
RecordEvaluatedBlock(function_.pattern_block_id);
|
|
RecordEvaluated(function_.self_param_id);
|
|
RecordEvaluated(function_.return_form_inst_id);
|
|
RecordEvaluated(function_.return_pattern_id);
|
|
for (InstId decl_id :
|
|
{function_.definition_id, function_.first_owning_decl_id,
|
|
function_.non_owning_decl_id}) {
|
|
if (!decl_id.has_value()) {
|
|
continue;
|
|
}
|
|
if (auto decl = file_.insts().TryGetAs<FunctionDecl>(decl_id)) {
|
|
RecordEvaluatedBlock(decl->decl_block_id);
|
|
}
|
|
}
|
|
|
|
auto result = VerifyBlocks();
|
|
|
|
// `evaluated_` is shared with the verification of other functions, so put it
|
|
// back the way it was found. Every instruction this added to it is in
|
|
// `evaluated_order_`, including if `VerifyBlocks` stopped at an error.
|
|
for (InstId inst_id : evaluated_order_) {
|
|
evaluated_.Erase(inst_id);
|
|
}
|
|
evaluated_order_.clear();
|
|
|
|
return result;
|
|
}
|
|
|
|
auto DominatorTreeBuilder::BuildControlFlowGraph() -> ErrorOr<Success> {
|
|
for (auto [i, block] : llvm::enumerate(body_blocks_)) {
|
|
block_indexes_.Insert(block, BlockIndex(i));
|
|
}
|
|
|
|
successors_.resize(num_blocks());
|
|
predecessors_.resize(num_blocks());
|
|
for (auto [i, block] : llvm::enumerate(body_blocks_)) {
|
|
BlockIndex from(i);
|
|
for (InstId inst_id : file_.inst_blocks().Get(block)) {
|
|
InstBlockId target_id = GetBranchTargetId(file_.insts().Get(inst_id));
|
|
if (!target_id.has_value()) {
|
|
continue;
|
|
}
|
|
BlockIndex* to = block_indexes_[target_id];
|
|
if (!to) {
|
|
return ErrorBuilder()
|
|
<< "Branch in block " << block << " targets block " << target_id
|
|
<< " which is not in function body";
|
|
}
|
|
// A block that branches to the same target more than once produces a
|
|
// duplicate edge. That's harmless: the post-order walk skips blocks it
|
|
// has already visited, and intersecting the dominators of the same
|
|
// predecessor twice gives the same result. Removing duplicates here
|
|
// would instead be quadratic in a block's number of successors.
|
|
successors_[from.index].push_back(*to);
|
|
predecessors_[to->index].push_back(from);
|
|
}
|
|
}
|
|
return Success();
|
|
}
|
|
|
|
auto DominatorTreeBuilder::BuildPostOrder(
|
|
llvm::SmallBitVector& visited,
|
|
llvm::SmallVectorImpl<BlockIndex>& post_order) -> void {
|
|
// The blocks whose successors are still being visited, each paired with the
|
|
// number of its successors that have been visited so far. A block is appended
|
|
// to `post_order` once all of its successors have been visited.
|
|
llvm::SmallVector<std::pair<BlockIndex, int>, 30> stack;
|
|
visited.set(EntryBlockIndex.index);
|
|
stack.push_back({EntryBlockIndex, 0});
|
|
|
|
while (!stack.empty()) {
|
|
auto [block_index, num_visited] = stack.back();
|
|
const auto& successors = successors_[block_index.index];
|
|
if (num_visited == static_cast<int>(successors.size())) {
|
|
post_order.push_back(block_index);
|
|
stack.pop_back();
|
|
continue;
|
|
}
|
|
|
|
stack.back().second = num_visited + 1;
|
|
BlockIndex successor = successors[num_visited];
|
|
if (!visited.test(successor.index)) {
|
|
visited.set(successor.index);
|
|
stack.push_back({successor, 0});
|
|
}
|
|
}
|
|
}
|
|
|
|
auto DominatorTreeBuilder::Build() -> ErrorOr<DominatorTree> {
|
|
CARBON_CHECK(!body_blocks_.empty());
|
|
CARBON_RETURN_IF_ERROR(BuildControlFlowGraph());
|
|
|
|
// Order the blocks so that, apart from loop back edges, every block precedes
|
|
// its successors.
|
|
llvm::SmallBitVector visited(num_blocks());
|
|
llvm::SmallVector<BlockIndex> reverse_post_order;
|
|
reverse_post_order.reserve(num_blocks());
|
|
BuildPostOrder(visited, reverse_post_order);
|
|
std::reverse(reverse_post_order.begin(), reverse_post_order.end());
|
|
|
|
for (auto [i, block] : llvm::enumerate(body_blocks_)) {
|
|
if (!visited.test(i)) {
|
|
return ErrorBuilder() << "Block " << block << " in function "
|
|
<< file_.names().GetFormatted(function_.name_id)
|
|
<< " is unreachable from entry block";
|
|
}
|
|
}
|
|
|
|
llvm::SmallVector<int> order(num_blocks(), -1);
|
|
for (auto i : llvm::seq(num_blocks())) {
|
|
order[reverse_post_order[i].index] = i;
|
|
}
|
|
|
|
// Find the immediate dominator of each block using the algorithm from "A
|
|
// Simple, Fast Dominance Algorithm" by Cooper, Harvey, and Kennedy:
|
|
// repeatedly sweep the blocks in reverse post-order, intersecting the
|
|
// dominators of each block's predecessors, until the result stops changing.
|
|
llvm::SmallVector<BlockIndex> idom(num_blocks(), BlockIndex::None);
|
|
idom[EntryBlockIndex.index] = EntryBlockIndex;
|
|
|
|
// Returns the closest block that dominates both `lhs` and `rhs`, by walking
|
|
// both up the dominator tree built so far until they meet.
|
|
auto intersect = [&](BlockIndex lhs, BlockIndex rhs) -> BlockIndex {
|
|
while (lhs != rhs) {
|
|
while (order[lhs.index] > order[rhs.index]) {
|
|
lhs = idom[lhs.index];
|
|
}
|
|
while (order[rhs.index] > order[lhs.index]) {
|
|
rhs = idom[rhs.index];
|
|
}
|
|
}
|
|
return lhs;
|
|
};
|
|
|
|
for (bool changed = true; changed;) {
|
|
changed = false;
|
|
// The entry block is its own immediate dominator, so start after it.
|
|
for (BlockIndex block_index : llvm::drop_begin(reverse_post_order)) {
|
|
BlockIndex new_idom = BlockIndex::None;
|
|
for (BlockIndex predecessor : predecessors_[block_index.index]) {
|
|
if (!idom[predecessor.index].has_value()) {
|
|
continue;
|
|
}
|
|
new_idom = new_idom.has_value() ? intersect(predecessor, new_idom)
|
|
: predecessor;
|
|
}
|
|
if (new_idom.has_value() && idom[block_index.index] != new_idom) {
|
|
idom[block_index.index] = new_idom;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
DominatorTree dom_children(num_blocks());
|
|
for (BlockIndex block_index : llvm::drop_begin(reverse_post_order)) {
|
|
// Every block other than the entry block is reachable, and so is dominated
|
|
// by the predecessor it's reached through.
|
|
CARBON_CHECK(idom[block_index.index].has_value(), "No dominator for {0}",
|
|
body_blocks_[block_index.index]);
|
|
dom_children[idom[block_index.index].index].push_back(block_index);
|
|
}
|
|
return dom_children;
|
|
}
|
|
|
|
auto DominanceVerifier::VerifyBlocks() -> ErrorOr<Success> {
|
|
llvm::SmallVector<WalkStep, 30> worklist = {
|
|
WalkStep::EnterBlock(EntryBlockIndex)};
|
|
|
|
while (!worklist.empty()) {
|
|
auto [block_index, scope_start] = worklist.pop_back_val();
|
|
|
|
if (!block_index.has_value()) {
|
|
for (InstId inst_id : llvm::drop_begin(evaluated_order_, scope_start)) {
|
|
evaluated_.Erase(inst_id);
|
|
}
|
|
evaluated_order_.truncate(scope_start);
|
|
continue;
|
|
}
|
|
|
|
// Evaluations in this block dominate the rest of this block and the blocks
|
|
// below it in the dominator tree, but nothing else. This step is beneath
|
|
// this block's children on the worklist, so it runs once they're done.
|
|
worklist.push_back(WalkStep::LeaveBlock(evaluated_order_.size()));
|
|
|
|
for (InstId inst_id :
|
|
file_.inst_blocks().Get(body_blocks_[block_index.index])) {
|
|
CARBON_RETURN_IF_ERROR(VerifyAndRecordInst(inst_id, block_index));
|
|
}
|
|
for (BlockIndex child : dom_tree_[block_index.index]) {
|
|
worklist.push_back(WalkStep::EnterBlock(child));
|
|
}
|
|
}
|
|
return Success();
|
|
}
|
|
|
|
auto DominanceVerifier::VerifyAndRecordInst(InstId root_inst_id,
|
|
BlockIndex block_index)
|
|
-> ErrorOr<Success> {
|
|
struct Step {
|
|
InstId inst_id;
|
|
// Whether to finish a `SpliceBlock` after its block has been evaluated.
|
|
bool finish_splice_block = false;
|
|
};
|
|
|
|
llvm::SmallVector<Step> worklist = {{.inst_id = root_inst_id}};
|
|
while (!worklist.empty()) {
|
|
auto [inst_id, finish_splice_block] = worklist.pop_back_val();
|
|
if (finish_splice_block) {
|
|
auto splice_block = file_.insts().GetAs<SpliceBlock>(inst_id);
|
|
CARBON_RETURN_IF_ERROR(
|
|
VerifyOperand(inst_id, splice_block.result_id, block_index));
|
|
RecordEvaluated(inst_id);
|
|
continue;
|
|
}
|
|
|
|
Inst inst = file_.insts().Get(inst_id);
|
|
|
|
// A `SpliceBlock` evaluates the instructions in its block, and then
|
|
// produces the value of one of them.
|
|
if (auto splice_block = inst.TryAs<SpliceBlock>()) {
|
|
worklist.push_back({.inst_id = inst_id, .finish_splice_block = true});
|
|
if (splice_block->block_id.has_value()) {
|
|
for (InstId spliced_id :
|
|
llvm::reverse(file_.inst_blocks().Get(splice_block->block_id))) {
|
|
worklist.push_back({.inst_id = spliced_id});
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// A `SpliceInst` evaluates the instruction that its operand names.
|
|
if (auto splice = inst.TryAs<SpliceInst>()) {
|
|
CARBON_RETURN_IF_ERROR(
|
|
VerifyOperand(inst_id, splice->inst_id, block_index));
|
|
RecordEvaluated(inst_id);
|
|
if (InstId spliced_id = GetSplicedInstId(*splice);
|
|
spliced_id.has_value()) {
|
|
worklist.push_back({.inst_id = spliced_id});
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// A non-constant `SpecificInst` evaluates to the value of the instruction
|
|
// that its `AbsoluteInstId` operand names.
|
|
if (auto specific_inst = inst.TryAs<SpecificInst>()) {
|
|
if (!GetConstantValueInSpecific(file_, specific_id_, inst_id)
|
|
.is_constant()) {
|
|
CARBON_RETURN_IF_ERROR(
|
|
VerifyOperand(inst_id, specific_inst->inst_id, block_index));
|
|
}
|
|
RecordEvaluated(inst_id);
|
|
continue;
|
|
}
|
|
|
|
CARBON_RETURN_IF_ERROR(
|
|
VerifyArg(inst_id, inst.arg0_and_kind(), block_index));
|
|
CARBON_RETURN_IF_ERROR(
|
|
VerifyArg(inst_id, inst.arg1_and_kind(), block_index));
|
|
RecordEvaluated(inst_id);
|
|
}
|
|
return Success();
|
|
}
|
|
|
|
auto DominanceVerifier::VerifyArg(InstId user_id, IdAndKind arg,
|
|
BlockIndex block_index) -> ErrorOr<Success> {
|
|
CARBON_KIND_SWITCH(arg) {
|
|
// These operand kinds name the value produced by another instruction, so
|
|
// that instruction's evaluation must dominate this use.
|
|
case CARBON_KIND(InstId inst_id): {
|
|
return VerifyOperand(user_id, inst_id, block_index);
|
|
}
|
|
case CARBON_KIND(DestInstId inst_id): {
|
|
return VerifyOperand(user_id, inst_id, block_index);
|
|
}
|
|
// A `TypeInstId` always names a constant of type `type`, so this check
|
|
// should always pass, but checking it means we notice if that stops being
|
|
// true.
|
|
case CARBON_KIND(TypeInstId inst_id): {
|
|
return VerifyOperand(user_id, inst_id, block_index);
|
|
}
|
|
case CARBON_KIND(InstBlockId block_id): {
|
|
if (block_id.has_value()) {
|
|
for (InstId operand_id : file_.inst_blocks().Get(block_id)) {
|
|
CARBON_RETURN_IF_ERROR(
|
|
VerifyOperand(user_id, operand_id, block_index));
|
|
}
|
|
}
|
|
return Success();
|
|
}
|
|
default: {
|
|
// Every other operand kind either doesn't name an instruction at all, or
|
|
// names one in a way that doesn't require dominance:
|
|
//
|
|
// - `MetaInstId` and `MetaInstBlockId` name the identity of an
|
|
// instruction rather than its value.
|
|
// - `AbsoluteInstId` and `AbsoluteInstBlockId` name instructions that
|
|
// are typically in a different entity.
|
|
// - `LabelId` names another block in this function's control flow.
|
|
// - `DeclInstBlockId` names a declaration rather than a computation.
|
|
return Success();
|
|
}
|
|
}
|
|
}
|
|
|
|
auto DominanceVerifier::VerifyOperand(InstId user_id, InstId operand_id,
|
|
BlockIndex block_index)
|
|
-> ErrorOr<Success> {
|
|
if (!operand_id.has_value() || operand_id == ErrorInst::InstId) {
|
|
return Success();
|
|
}
|
|
// A constant isn't evaluated in the function body, so can be used anywhere.
|
|
if (GetConstantValueInSpecific(file_, specific_id_, operand_id)
|
|
.is_constant()) {
|
|
return Success();
|
|
}
|
|
if (evaluated_.Contains(operand_id)) {
|
|
return Success();
|
|
}
|
|
// TODO: Remove this allowlist; see `CollectDeclInsts`.
|
|
if (decl_insts_.Contains(operand_id)) {
|
|
return Success();
|
|
}
|
|
Inst operand = file_.insts().Get(operand_id);
|
|
|
|
// TODO: A non-constant import isn't evaluated in the importing file at all,
|
|
// so this is a real violation: `__global_init` can contain a `name_ref` to an
|
|
// `import_ref` for a non-constant imported variable. Remove this exemption
|
|
// and diagnose such uses once imported variables have a value model.
|
|
if (operand.Is<AnyImportRef>()) {
|
|
return Success();
|
|
}
|
|
|
|
return ErrorBuilder()
|
|
<< "Operand " << operand_id << " (" << operand.kind().ir_name()
|
|
<< ") used by instruction " << user_id << " ("
|
|
<< file_.insts().Get(user_id).kind().ir_name() << ") in block "
|
|
<< body_blocks_[block_index.index] << " of function "
|
|
<< function_.name_id
|
|
<< " is not dominated by any evaluation and is not constant";
|
|
}
|
|
|
|
auto DominanceVerifier::RecordEvaluated(InstId inst_id) -> void {
|
|
if (!inst_id.has_value()) {
|
|
return;
|
|
}
|
|
// An instruction can be evaluated more than once, for example in two blocks
|
|
// that don't dominate each other. Only the first evaluation in scope needs to
|
|
// be undone.
|
|
if (evaluated_.Insert(inst_id)) {
|
|
evaluated_order_.push_back(inst_id);
|
|
}
|
|
}
|
|
|
|
auto DominanceVerifier::RecordEvaluatedBlock(InstBlockId block_id) -> void {
|
|
if (!block_id.has_value()) {
|
|
return;
|
|
}
|
|
for (InstId inst_id : file_.inst_blocks().Get(block_id)) {
|
|
RecordEvaluated(inst_id);
|
|
}
|
|
}
|
|
|
|
auto DominanceVerifier::GetSplicedInstId(SpliceInst splice) const -> InstId {
|
|
ConstantId const_id =
|
|
GetConstantValueInSpecific(file_, specific_id_, splice.inst_id);
|
|
if (!const_id.is_constant()) {
|
|
return InstId::None;
|
|
}
|
|
InstId const_inst_id = file_.constant_values().GetInstIdIfValid(const_id);
|
|
if (!const_inst_id.has_value()) {
|
|
return InstId::None;
|
|
}
|
|
if (auto inst_value = file_.insts().TryGetAs<InstValue>(const_inst_id)) {
|
|
return inst_value->inst_id;
|
|
}
|
|
return InstId::None;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
auto VerifyDominance(const File& file) -> ErrorOr<Success> {
|
|
// Invariants don't necessarily hold for invalid IR.
|
|
if (file.has_errors()) {
|
|
return Success();
|
|
}
|
|
|
|
InstSet decl_insts(file);
|
|
CollectDeclInsts(file, decl_insts);
|
|
|
|
SpecificsByGeneric specifics = CollectSpecifics(file);
|
|
|
|
// Shared by the verification of every function and specific, because creating
|
|
// one costs a bit per instruction in the file. Each `Verify` call leaves it
|
|
// empty for the next one.
|
|
InstSet evaluated(file);
|
|
|
|
for (const Function& function : file.functions().values()) {
|
|
if (function.body_block_ids.empty()) {
|
|
continue;
|
|
}
|
|
|
|
CARBON_ASSIGN_OR_RETURN(DominatorTree dom_tree,
|
|
DominatorTreeBuilder(file, function).Build());
|
|
|
|
// Verify the body in the general, unspecialized context.
|
|
CARBON_RETURN_IF_ERROR(DominanceVerifier(file, decl_insts, function,
|
|
dom_tree, evaluated,
|
|
SpecificId::None)
|
|
.Verify());
|
|
|
|
// For a generic function, also verify the body as it will be evaluated in
|
|
// each of its specifics, in which spliced instructions can be resolved.
|
|
// TODO: Only re-verify the spliced instructions, rather than all the
|
|
// instructions in the function, for each specific.
|
|
for (SpecificId specific_id : specifics.Get(function.generic_id)) {
|
|
CARBON_RETURN_IF_ERROR(DominanceVerifier(file, decl_insts, function,
|
|
dom_tree, evaluated, specific_id)
|
|
.Verify());
|
|
}
|
|
}
|
|
|
|
return Success();
|
|
}
|
|
|
|
} // namespace Carbon::SemIR
|