Files
swift-mirror/lib/SILOptimizer/Utils/VariableNameUtils.cpp
T
Joe Groff 097b0d3400 SIL: Split unchecked_*_enum_data_addr according to ownership and effects.
We cannot use spare bits or other overlapping storage layout tricks with fundamentally
address-only enums, and we can take advantage of this to do borrowing switches or other
in-place projections without copying the value. However, for resilient enums, the
implementation may use spare bit packing, but the type must be handled address-only
outside of its defining module, and we didn't have a way to express that with
borrowing switch. Optimization passes have also been running into problems with the
complexity that we were using `unchecked_take_enum_data_addr` sometimes as a pure
operation. This patch splits the instruction into three:

- `unchecked_inplace_enum_data_addr` represents a nondestructive in-place enum
  projection. It is only allowed for enums whose projection operation is
  nondestructive.
- `unchecked_take_enum_data_addr` represents a destructive enum projection,
  invalidating the enum and leaving the payload to be further consumed.
  This matches the current instruction's semantics.
- `unchecked_borrow_enum_data_addr` represents a borrowing enum projection.
  The instruction takes a second operand for "scratch" space, which the
  enum representation may be copied into in order to avoid invalidating the
  enum value, so the result is dependent on the lifetime of both the
  original enum and the scratch buffer. This allows for borrowing switches
  over resilient enums.

`unchecked_borrow_enum_data_addr` is implemented by taking advantage of the
"address-only enums can't do spare bit optimization" property at runtime.
We inspect the operand type's bitwise-borrowability from its metadata. If
the type is bitwise-borrowable, then we are allowed to bitwise-copy the
enum to the scratch space and apply the projection to the scratch space,
preserving the original value. If the type is not bitwise-borrowable, then
we cannot use spare bit optimization in its layout, so we apply the
projection in-place.

Fixes rdar://174952822.
2026-04-27 15:40:37 -07:00

759 lines
26 KiB
C++

//===--- VariableNameUtils.cpp --------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "sil-variable-name-inference"
#include "swift/Basic/Assertions.h"
#include "swift/SILOptimizer/Utils/VariableNameUtils.h"
#include "swift/SIL/AddressWalker.h"
#include "swift/SIL/Test.h"
using namespace swift;
namespace {
struct AddressWalkerState {
bool foundError = false;
InstructionSet writes;
AddressWalkerState(SILFunction *fn) : writes(fn) {}
};
} // namespace
static SILValue
findRootValueForNonTupleTempAllocation(AllocationInst *allocInst,
AddressWalkerState &state) {
// These are instructions which we are ok with looking through when
// identifying our allocation. It must always refer to the entire allocation.
auto isAlloc = [&](SILValue value) -> bool {
if (auto *ieai = dyn_cast<InitExistentialAddrInst>(value))
value = ieai->getOperand();
return value == SILValue(allocInst);
};
// Walk from our allocation to one of our writes. Then make sure that the
// write writes to our entire value.
for (auto &inst : allocInst->getParent()->getRangeStartingAtInst(allocInst)) {
// See if we have a full tuple value.
if (!state.writes.contains(&inst))
continue;
if (auto *copyAddr = dyn_cast<CopyAddrInst>(&inst)) {
if (isAlloc(copyAddr->getDest()) &&
copyAddr->isInitializationOfDest()) {
return copyAddr->getSrc();
}
}
if (auto *si = dyn_cast<StoreInst>(&inst)) {
if (isAlloc(si->getDest()) &&
si->getOwnershipQualifier() != StoreOwnershipQualifier::Assign) {
return si->getSrc();
}
}
if (auto *sbi = dyn_cast<StoreBorrowInst>(&inst)) {
if (isAlloc(sbi->getDest()))
return sbi->getSrc();
}
// If we do not identify the write... return SILValue(). We weren't able
// to understand the write.
break;
}
return SILValue();
}
static SILValue findRootValueForTupleTempAllocation(AllocationInst *allocInst,
AddressWalkerState &state) {
SmallVector<SILValue, 8> tupleValues;
for (unsigned i : range(allocInst->getType().getNumTupleElements())) {
(void)i;
tupleValues.push_back(nullptr);
}
unsigned numEltsLeft = tupleValues.size();
// If we have an empty tuple, just return SILValue() for now.
//
// TODO: What does this pattern look like out of SILGen?
if (!numEltsLeft)
return SILValue();
// Walk from our allocation to one of our writes. Then make sure that the
// write writes to our entire value.
DestructureTupleInst *foundDestructure = nullptr;
SILValue foundRootAddress;
for (auto &inst : allocInst->getParent()->getRangeStartingAtInst(allocInst)) {
if (!state.writes.contains(&inst))
continue;
if (auto *copyAddr = dyn_cast<CopyAddrInst>(&inst)) {
if (copyAddr->isInitializationOfDest()) {
if (auto *tei = dyn_cast<TupleElementAddrInst>(copyAddr->getDest())) {
if (tei->getOperand() == allocInst) {
unsigned i = tei->getFieldIndex();
if (auto *otherTei = dyn_cast_or_null<TupleElementAddrInst>(
copyAddr->getSrc()->getDefiningInstruction())) {
// If we already were processing destructures, then we have a mix
// of struct/destructures... we do not support that, so bail.
if (foundDestructure)
return SILValue();
// Otherwise, update our root address. If we already had a root
// address and it doesn't match our tuple_element_addr's operand,
// bail. There is some sort of mix/match of tuple addresses that
// we do not support. We are looking for a specific SILGen
// pattern.
if (!foundRootAddress) {
foundRootAddress = otherTei->getOperand();
} else if (foundRootAddress != otherTei->getOperand()) {
return SILValue();
}
if (i != otherTei->getFieldIndex())
return SILValue();
if (tupleValues[i])
return SILValue();
tupleValues[i] = otherTei;
// If we have completely covered the tuple, break.
--numEltsLeft;
if (!numEltsLeft)
break;
// Otherwise, continue so we keep processing.
continue;
}
}
}
}
}
if (auto *si = dyn_cast<StoreInst>(&inst)) {
if (si->getOwnershipQualifier() != StoreOwnershipQualifier::Assign) {
// Check if we are updating the entire tuple value.
if (si->getDest() == allocInst) {
// If we already found a root address (meaning we were processing
// tuple_elt_addr), bail. We have some sort of unhandled mix of
// copy_addr and store.
if (foundRootAddress)
return SILValue();
// If we already found a destructure, return SILValue(). We are
// initializing twice.
if (foundDestructure)
return SILValue();
// We are looking for a pattern where we construct a tuple from
// destructured parts.
if (auto *ti = dyn_cast<TupleInst>(si->getSrc())) {
for (auto p : llvm::enumerate(ti->getOperandValues())) {
SILValue value = lookThroughOwnershipInsts(p.value());
if (auto *dti = dyn_cast_or_null<DestructureTupleInst>(
value->getDefiningInstruction())) {
// We should always go through the same dti.
if (foundDestructure && foundDestructure != dti)
return SILValue();
if (!foundDestructure)
foundDestructure = dti;
// If we have a mixmatch of indices, we cannot look through.
if (p.index() != dti->getIndexOfResult(value))
return SILValue();
if (tupleValues[p.index()])
return SILValue();
tupleValues[p.index()] = value;
// If we have completely covered the tuple, break.
--numEltsLeft;
if (!numEltsLeft)
break;
}
}
// If we haven't completely covered the tuple, return SILValue(). We
// should completely cover the tuple.
if (numEltsLeft)
return SILValue();
// Otherwise, break since we are done.
break;
}
}
// If we store to a tuple_element_addr, update for a single value.
if (auto *tei = dyn_cast<TupleElementAddrInst>(si->getDest())) {
if (tei->getOperand() == allocInst) {
unsigned i = tei->getFieldIndex();
if (auto *dti = dyn_cast_or_null<DestructureTupleInst>(
si->getSrc()->getDefiningInstruction())) {
// If we already found a root address (meaning we were processing
// tuple_elt_addr), bail. We have some sort of unhandled mix of
// copy_addr and store [init].
if (foundRootAddress)
return SILValue();
if (!foundDestructure) {
foundDestructure = dti;
} else if (foundDestructure != dti) {
return SILValue();
}
if (i != dti->getIndexOfResult(si->getSrc()))
return SILValue();
if (tupleValues[i])
return SILValue();
tupleValues[i] = si->getSrc();
// If we have completely covered the tuple, break.
--numEltsLeft;
if (!numEltsLeft)
break;
// Otherwise, continue so we keep processing.
continue;
}
}
}
}
}
// Found a write that we did not understand... bail.
break;
}
// Now check if we have a complete tuple with all elements coming from the
// same destructure_tuple. In such a case, we can look through the
// destructure_tuple.
if (numEltsLeft)
return SILValue();
if (foundDestructure)
return foundDestructure->getOperand();
if (foundRootAddress)
return foundRootAddress;
return SILValue();
}
SILValue VariableNameInferrer::getRootValueForTemporaryAllocation(
AllocationInst *allocInst) {
struct AddressWalker final : public TransitiveAddressWalker<AddressWalker> {
AddressWalkerState &state;
AddressWalker(AddressWalkerState &state) : state(state) {}
bool visitUse(Operand *use) {
if (use->getUser()->mayWriteToMemory())
state.writes.insert(use->getUser());
return true;
}
TransitiveUseVisitation visitTransitiveUseAsEndPointUse(Operand *use) {
if (isa<StoreBorrowInst>(use->getUser()))
return TransitiveUseVisitation::OnlyUser;
return TransitiveUseVisitation::OnlyUses;
}
void onError(Operand *use) { state.foundError = true; }
};
AddressWalkerState state(allocInst->getFunction());
AddressWalker walker(state);
if (std::move(walker).walk(allocInst) == AddressUseKind::Unknown ||
state.foundError)
return SILValue();
if (allocInst->getType().is<TupleType>())
return findRootValueForTupleTempAllocation(allocInst, state);
return findRootValueForNonTupleTempAllocation(allocInst, state);
}
SILValue
VariableNameInferrer::findDebugInfoProvidingValue(SILValue searchValue) {
// NOTE: This should only return a non-empty SILValue if we actually have a
// full path (including base name) in the variable name path.
if (!searchValue)
return SILValue();
LLVM_DEBUG(llvm::dbgs() << "Searching for debug info providing value for: "
<< searchValue);
ValueSet valueSet(searchValue->getFunction());
SILValue result = findDebugInfoProvidingValueHelper(searchValue, valueSet);
if (result) {
LLVM_DEBUG(llvm::dbgs() << "Result: " << result);
} else {
LLVM_DEBUG(llvm::dbgs() << "Result: None\n");
}
return result;
}
SILValue VariableNameInferrer::findDebugInfoProvidingValuePhiArg(
SILValue incomingValue, ValueSet &visitedValues) {
// We use pushSnapShot to run recursively and if we fail to find a
// value, we just pop our list to the last snapshot end of list. If we
// succeed, we do not pop and just return recusive value. Our user
// will consume variableNamePath at this point.
LLVM_DEBUG(llvm::dbgs() << "Before pushing a snap shot!\n";
variableNamePath.print(llvm::dbgs()));
unsigned oldSnapShotIndex = variableNamePath.pushSnapShot();
LLVM_DEBUG(llvm::dbgs() << "After pushing a snap shot!\n";
variableNamePath.print(llvm::dbgs()));
if (SILValue recursiveValue =
findDebugInfoProvidingValueHelper(incomingValue, visitedValues)) {
LLVM_DEBUG(llvm::dbgs() << "Returned: " << recursiveValue);
variableNamePath.returnSnapShot(oldSnapShotIndex);
return recursiveValue;
}
variableNamePath.popSnapShot(oldSnapShotIndex);
LLVM_DEBUG(llvm::dbgs() << "After popping a snap shot!\n";
variableNamePath.print(llvm::dbgs()));
return SILValue();
}
static BeginBorrowInst *hasOnlyBorrowingNonDestroyUse(SILValue searchValue) {
BeginBorrowInst *result = nullptr;
for (auto *use : searchValue->getUses()) {
if (isIncidentalUse(use->getUser()))
continue;
if (use->isConsuming()) {
if (!isa<DestroyValueInst>(use->getUser()))
return nullptr;
continue;
}
auto *bbi = dyn_cast<BeginBorrowInst>(use->getUser());
if (!bbi || !bbi->isFromVarDecl())
return nullptr;
if (result)
return nullptr;
result = bbi;
}
return result;
}
namespace {
constexpr StringLiteral UnknownDeclString = "<unknown decl>";
} // namespace
SILValue VariableNameInferrer::findDebugInfoProvidingValueHelper(
SILValue searchValue, ValueSet &visitedValues) {
assert(searchValue);
while (true) {
assert(searchValue);
// If we already visited the value, return SILValue(). This prevents issues
// caused by looping phis. We treat this as a failure and visit the either
// phi values.
if (!visitedValues.insert(searchValue))
return SILValue();
LLVM_DEBUG(llvm::dbgs() << "Value: " << *searchValue);
// Before we do anything, lets see if we have an explicit match due to a
// debug_value use.
if (auto *use = getAnyDebugUse(searchValue)) {
if (auto debugVar = DebugVarCarryingInst(use->getUser())) {
assert(debugVar.getKind() == DebugVarCarryingInst::Kind::DebugValue);
variableNamePath.push_back(debugVar.getName());
// We return the value, not the debug_info.
return searchValue;
}
}
// If we are in Ownership SSA, see if we have an owned value that has one
// use, a move_value [var decl]. In such a case, check the move_value [var
// decl] for a debug_value.
//
// This pattern comes up if we are asked to get a name for an apply that is
// used to initialize a value. The name will not yet be associated with the
// value so we have to compensate.
//
// NOTE: This is a heuristic. Feel free to tweak accordingly.
if (auto *singleUse = searchValue->getSingleUse()) {
if (auto *mvi = dyn_cast<MoveValueInst>(singleUse->getUser())) {
if (mvi->isFromVarDecl()) {
if (auto *debugUse = getAnyDebugUse(mvi)) {
if (auto debugVar = DebugVarCarryingInst(debugUse->getUser())) {
assert(debugVar.getKind() ==
DebugVarCarryingInst::Kind::DebugValue);
variableNamePath.push_back(debugVar.getName());
// We return the value, not the debug_info.
return searchValue;
}
}
}
}
}
if (auto *bbi = hasOnlyBorrowingNonDestroyUse(searchValue)) {
if (auto *debugUse = getAnyDebugUse(bbi)) {
if (auto debugVar = DebugVarCarryingInst(debugUse->getUser())) {
assert(debugVar.getKind() == DebugVarCarryingInst::Kind::DebugValue);
variableNamePath.push_back(debugVar.getName());
// We return the value, not the debug_info.
return searchValue;
}
}
}
if (auto *allocInst = dyn_cast<AllocationInst>(searchValue)) {
// If the instruction itself doesn't carry any variable info, see
// whether it's copied from another place that does.
auto allocInstHasInfo = [](AllocationInst *allocInst) {
if (allocInst->getDecl())
return true;
auto debugVar = DebugVarCarryingInst(allocInst);
return debugVar && debugVar.maybeGetName().has_value();
};
if (!allocInstHasInfo(allocInst)) {
if (auto value = getRootValueForTemporaryAllocation(allocInst)) {
searchValue = value;
continue;
}
return SILValue();
}
variableNamePath.push_back(DebugVarCarryingInst(allocInst).getName());
return allocInst;
}
if (auto *abi = dyn_cast<AllocBoxInst>(searchValue)) {
variableNamePath.push_back(DebugVarCarryingInst(abi).getName());
return abi;
}
// If we have a store_borrow, always look at the dest. We are going to see
// if we can determine if dest is a temporary alloc_stack.
if (auto *sbi = dyn_cast<StoreBorrowInst>(searchValue)) {
searchValue = sbi->getDest();
continue;
}
if (auto *globalAddrInst = dyn_cast<GlobalAddrInst>(searchValue)) {
variableNamePath.push_back(VarDeclCarryingInst(globalAddrInst).getName());
return globalAddrInst;
}
if (auto *oeInst = dyn_cast<OpenExistentialAddrInst>(searchValue)) {
searchValue = oeInst->getOperand();
continue;
}
if (auto *rei = dyn_cast<RefElementAddrInst>(searchValue)) {
variableNamePath.push_back(VarDeclCarryingInst(rei).getName());
searchValue = rei->getOperand();
continue;
}
if (auto *sei = dyn_cast<StructExtractInst>(searchValue)) {
variableNamePath.push_back(getNameFromDecl(sei->getField()));
searchValue = sei->getOperand();
continue;
}
if (auto *uedi = dyn_cast<UncheckedEnumDataInst>(searchValue)) {
variableNamePath.push_back(getNameFromDecl(uedi->getElement()));
searchValue = uedi->getOperand();
continue;
}
if (auto *tei = dyn_cast<TupleExtractInst>(searchValue)) {
variableNamePath.push_back(getStringRefForIndex(tei->getFieldIndex()));
searchValue = tei->getOperand();
continue;
}
if (auto *sei = dyn_cast<StructElementAddrInst>(searchValue)) {
variableNamePath.push_back(getNameFromDecl(sei->getField()));
searchValue = sei->getOperand();
continue;
}
if (auto *tei = dyn_cast<TupleElementAddrInst>(searchValue)) {
variableNamePath.push_back(getStringRefForIndex(tei->getFieldIndex()));
searchValue = tei->getOperand();
continue;
}
if (auto *utedai = dyn_cast<UncheckedEnumDataAddrInstBase>(searchValue)) {
variableNamePath.push_back(getNameFromDecl(utedai->getElement()));
searchValue = utedai->getEnum();
continue;
}
// Enums only have a single possible parent and is used sometimes like a
// transformation (e.x.: constructing an optional). We want to look through
// them and add the case to the variableNamePath.
if (auto *e = dyn_cast<EnumInst>(searchValue)) {
if (e->hasOperand()) {
variableNamePath.push_back(getNameFromDecl(e->getElement()));
searchValue = e->getOperand();
continue;
}
}
if (auto *dti = dyn_cast_or_null<DestructureTupleInst>(
searchValue->getDefiningInstruction())) {
variableNamePath.push_back(
getStringRefForIndex(*dti->getIndexOfResult(searchValue)));
searchValue = dti->getOperand();
continue;
}
if (auto *dsi = dyn_cast_or_null<DestructureStructInst>(
searchValue->getDefiningInstruction())) {
unsigned index = *dsi->getIndexOfResult(searchValue);
variableNamePath.push_back(
getNameFromDecl(dsi->getStructDecl()->getStoredProperties()[index]));
searchValue = dsi->getOperand();
continue;
}
if (auto *fArg = dyn_cast<SILFunctionArgument>(searchValue)) {
if (auto *decl = fArg->getDecl()) {
variableNamePath.push_back(decl->getBaseName().userFacingName());
return fArg;
}
}
// If we have a phi argument, visit each of the incoming values and pick the
// first one that gives us a name.
if (auto *phiArg = dyn_cast<SILPhiArgument>(searchValue)) {
if (auto *term = phiArg->getSingleTerminator()) {
if (auto *swi = dyn_cast<SwitchEnumInst>(term)) {
if (auto value = findDebugInfoProvidingValuePhiArg(swi->getOperand(),
visitedValues))
return value;
}
}
SmallVector<SILValue, 8> incomingValues;
if (phiArg->getIncomingPhiValues(incomingValues)) {
for (auto value : incomingValues) {
if (auto resultValue =
findDebugInfoProvidingValuePhiArg(value, visitedValues))
return resultValue;
}
}
}
auto getNamePathComponentFromCallee = [&](FullApplySite call) -> SILValue {
// Use the name of the property being accessed if we can get to it.
if (call.getSubstCalleeType()->hasSelfParam()) {
if (auto *f = dyn_cast<FunctionRefBaseInst>(call.getCallee())) {
if (auto dc = f->getInitiallyReferencedFunction()->getDeclContext()) {
variableNamePath.push_back(getNameFromDecl(dc->getAsDecl()));
return call.getSelfArgument();
}
}
if (auto *mi = dyn_cast<MethodInst>(call.getCallee())) {
variableNamePath.push_back(
getNameFromDecl(mi->getMember().getDecl()));
return call.getSelfArgument();
}
}
return SILValue();
};
// Read or modify accessor.
if (auto bai = dyn_cast_or_null<BeginApplyInst>(
searchValue->getDefiningInstruction())) {
if (auto selfParam = getNamePathComponentFromCallee(bai)) {
searchValue = selfParam;
continue;
}
}
if (options.contains(Flag::InferSelfThroughAllAccessors)) {
if (auto *inst = searchValue->getDefiningInstruction()) {
if (auto fas = FullApplySite::isa(inst)) {
if (auto selfParam = getNamePathComponentFromCallee(fas)) {
searchValue = selfParam;
continue;
}
}
}
}
// Borrow/mutate accessor
if (searchValue->isBorrowAccessorResult()) {
if (auto fas =
FullApplySite::isa(searchValue->getDefiningInstruction())) {
if (auto selfParam = getNamePathComponentFromCallee(fas)) {
searchValue = selfParam;
continue;
}
}
}
// Addressor accessor.
if (auto ptrToAddr =
dyn_cast<PointerToAddressInst>(stripAccessMarkers(searchValue))) {
// The addressor can either produce the raw pointer itself or an
// `UnsafePointer` stdlib type wrapping it.
ApplyInst *addressorInvocation;
if (auto structExtract =
dyn_cast<StructExtractInst>(ptrToAddr->getOperand())) {
addressorInvocation = dyn_cast<ApplyInst>(structExtract->getOperand());
} else {
addressorInvocation = dyn_cast<ApplyInst>(ptrToAddr->getOperand());
}
if (addressorInvocation) {
if (auto selfParam =
getNamePathComponentFromCallee(addressorInvocation)) {
searchValue = selfParam;
continue;
}
}
}
// Look through a function conversion thunk if we have one.
if (auto *pai = dyn_cast<PartialApplyInst>(searchValue)) {
if (auto *fn = pai->getCalleeFunction()) {
if (fn->isThunk() && ApplySite(pai).getNumArguments() == 1) {
SILValue value = ApplySite(pai).getArgument(0);
if (value->getType().isFunction()) {
searchValue = value;
continue;
}
}
}
}
// Otherwise, try to see if we have a single value instruction we can look
// through.
if (isa<BeginBorrowInst>(searchValue) || isa<LoadInst>(searchValue) ||
isa<LoadBorrowInst>(searchValue) || isa<BeginAccessInst>(searchValue) ||
isa<MarkUnresolvedNonCopyableValueInst>(searchValue) ||
isa<ProjectBoxInst>(searchValue) || isa<CopyValueInst>(searchValue) ||
isa<ExplicitCopyValueInst>(searchValue) ||
isa<ConvertFunctionInst>(searchValue) ||
isa<MarkUninitializedInst>(searchValue) ||
isa<MarkDependenceInst>(searchValue) ||
isa<CopyableToMoveOnlyWrapperAddrInst>(searchValue) ||
isa<MoveOnlyWrapperToCopyableAddrInst>(searchValue) ||
isa<MoveOnlyWrapperToCopyableValueInst>(searchValue) ||
isa<CopyableToMoveOnlyWrapperValueInst>(searchValue) ||
isa<EndInitLetRefInst>(searchValue) ||
isa<ConvertEscapeToNoEscapeInst>(searchValue) ||
isa<ConvertFunctionInst>(searchValue)) {
searchValue = cast<SingleValueInstruction>(searchValue)->getOperand(0);
continue;
}
// Return SILValue() if we ever get to the bottom to signal we failed to
// find anything.
return SILValue();
}
}
StringRef VariableNameInferrer::getNameFromDecl(Decl *d) {
if (d) {
if (auto accessor = dyn_cast<AccessorDecl>(d)) {
return accessor->getStorage()->getBaseName().userFacingName();
}
if (auto vd = dyn_cast<ValueDecl>(d)) {
return vd->getBaseName().userFacingName();
}
}
return UnknownDeclString;
}
void VariableNameInferrer::drainVariableNamePath() {
if (variableNamePath.empty())
return;
// Walk backwards, constructing our string.
while (true) {
resultingString += variableNamePath.pop_back_val();
if (variableNamePath.empty())
return;
resultingString += '.';
}
}
std::optional<Identifier> VariableNameInferrer::inferName(SILValue value) {
auto *fn = value->getFunction();
if (!fn)
return {};
VariableNameInferrer::Options options;
options |= VariableNameInferrer::Flag::InferSelfThroughAllAccessors;
SmallString<64> resultingName;
VariableNameInferrer inferrer(fn, options, resultingName);
if (!inferrer.inferByWalkingUsesToDefsReturningRoot(value))
return {};
return fn->getASTContext().getIdentifier(resultingName);
}
std::optional<std::pair<Identifier, SILValue>>
VariableNameInferrer::inferNameAndRoot(SILValue value) {
auto *fn = value->getFunction();
if (!fn)
return {};
VariableNameInferrer::Options options;
options |= VariableNameInferrer::Flag::InferSelfThroughAllAccessors;
SmallString<64> resultingName;
VariableNameInferrer inferrer(fn, options, resultingName);
SILValue rootValue = inferrer.inferByWalkingUsesToDefsReturningRoot(value);
if (!rootValue)
return {};
return {{fn->getASTContext().getIdentifier(resultingName), rootValue}};
}
//===----------------------------------------------------------------------===//
// MARK: Tests
//===----------------------------------------------------------------------===//
namespace swift::test {
// Arguments:
// - SILValue: value to emit a name for.
// Dumps:
// - The inferred name
// - The inferred value.
static FunctionTest VariableNameInferrerTests(
"variable_name_inference", [](auto &function, auto &arguments, auto &test) {
auto value = arguments.takeValue();
SmallString<64> finalString;
VariableNameInferrer::Options options;
options |= VariableNameInferrer::Flag::InferSelfThroughAllAccessors;
VariableNameInferrer inferrer(&function, options, finalString);
SILValue rootValue =
inferrer.inferByWalkingUsesToDefsReturningRoot(value);
llvm::outs() << "Input Value: " << *value;
if (!rootValue) {
llvm::outs() << "Name: 'unknown'\nRoot: 'unknown'\n";
return;
}
llvm::outs() << "Name: '" << finalString << "'\nRoot: " << rootValue;
});
} // namespace swift::test