Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d9b95a0
Implement devirtualization calls to composition type protocols
zoecarver Nov 3, 2019
bfb3dd7
Clean up implementation and remove debug
zoecarver Nov 3, 2019
287bbd4
Add a test
zoecarver Nov 3, 2019
f3ac3bd
Cleanup and fix spacing (again)
zoecarver Nov 3, 2019
f1c75f6
Remove assertion all together and use getAddressOfStackInit instead o…
zoecarver Nov 4, 2019
4b36f3f
Fix based on review and add more tests
zoecarver Nov 4, 2019
f1c15d2
Fix tests
zoecarver Nov 4, 2019
6c48a8d
Ensure that only correct patterns are optimized
zoecarver Nov 5, 2019
01a452f
Update comment
zoecarver Nov 5, 2019
17894bb
Remove redundant protocols
zoecarver Nov 5, 2019
febbb70
Merge branch 'master' into fix/substitution-map-composition
zoecarver Nov 5, 2019
3ab158e
Merge branch 'master' into fix/substitution-map-composition
zoecarver Nov 30, 2019
2913dbc
Check dominance order and add .sil test
zoecarver Dec 1, 2019
331a6af
Remove .dump in unrelated file
zoecarver Dec 1, 2019
d032de6
Move relevant part of sil test to the top of the file
zoecarver Dec 1, 2019
644689d
Rename test
zoecarver Dec 1, 2019
6fcf437
Merge branch 'master' into fix/substitution-map-composition
zoecarver Dec 22, 2019
758f1f3
Update getAddressOfStackInit and getStackInitInst to support store in…
zoecarver Dec 22, 2019
f4e3ca9
Formatting
zoecarver Dec 22, 2019
a52fe57
Cleanup tests
zoecarver Jan 5, 2020
33987c5
Inline/remove getAddressOfStackInit and update tests to make them mor…
zoecarver Jan 23, 2020
0992e48
Abort if not address type
zoecarver Jan 24, 2020
ae64719
Support store instructions in createApplyWithConcreteType
zoecarver Jan 24, 2020
e93919b
Add comments, remove commented code, and other general cleanup
zoecarver Jan 24, 2020
2ac715c
Update devirtualize_(...)_two_stores RUN to use sil-opt
zoecarver Jan 24, 2020
e38da06
Stash debugging changes
zoecarver Jan 27, 2020
809f569
Remove debug code and update check for successful conversion of
zoecarver Jan 28, 2020
ddeef64
Re-add support for try_apply instruction and update tests.
zoecarver Jan 29, 2020
ac4fa33
Stash optimization debugging
zoecarver Jan 31, 2020
4d2c342
Update how createApplyWithConcreteType checks if it can update Apply
zoecarver Feb 2, 2020
a0fb139
Format
zoecarver Feb 2, 2020
65e8d37
Add a `madeUpdate` check to `createApplyWithConcreteType` to prevent
zoecarver Feb 3, 2020
37c0eb2
Manually check if types are the same when checking for change and update
zoecarver Feb 3, 2020
727cd96
Format
zoecarver Feb 3, 2020
dbadd99
Add benchmark for devirtualization performance measurements
zoecarver Feb 3, 2020
bdd9480
Update added tests to require objec interop
zoecarver Feb 3, 2020
ea821b1
Use blackHole to make sure benchmark isn't optimized away
zoecarver Feb 4, 2020
1347929
Merge branch 'master' into fix/substitution-map-composition
zoecarver Feb 5, 2020
209bfd0
Break argument checking out of a lambda and add comments
zoecarver Feb 5, 2020
464b585
Update benchmark to be more similar to other devirtualization benchmarks
zoecarver Feb 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ set(SWIFT_BENCH_MODULES
single-source/Combos
single-source/DataBenchmarks
single-source/DeadArray
single-source/DevirtualizeProtocolComposition
single-source/DictOfArraysToArrayOfDicts
single-source/DictTest
single-source/DictTest2
Expand Down
45 changes: 45 additions & 0 deletions benchmark/single-source/DevirtualizeProtocolComposition.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//===--- DevirtualizeProtocolComposition.swift -------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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
//
//===----------------------------------------------------------------------===//

import TestsUtils

public let DevirtualizeProtocolComposition = [
BenchmarkInfo(name: "DevirtualizeProtocolComposition", runFunction: run_DevirtualizeProtocolComposition, tags: [.validation, .api]),
]

protocol Pingable { func ping() -> Int; func pong() -> Int}

public class Game<T> {
func length() -> Int { return 10 }
}

public class PingPong: Game<String> { }

extension PingPong : Pingable {
func ping() -> Int { return 1 }
func pong() -> Int { return 2 }
}

func playGame<T>(_ x: Game<T> & Pingable) -> Int {
var sum = 0
for _ in 0..<x.length() {
sum += x.ping() + x.pong()
}
return sum
}

@inline(never)
public func run_DevirtualizeProtocolComposition(N: Int) {
for _ in 0..<N * 20_000 {
blackHole(playGame(PingPong()))
}
}
2 changes: 2 additions & 0 deletions benchmark/utils/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import Codable
import Combos
import DataBenchmarks
import DeadArray
import DevirtualizeProtocolComposition
import DictOfArraysToArrayOfDicts
import DictTest
import DictTest2
Expand Down Expand Up @@ -231,6 +232,7 @@ registerBenchmark(Combos)
registerBenchmark(ClassArrayGetter)
registerBenchmark(DataBenchmarks)
registerBenchmark(DeadArray)
registerBenchmark(DevirtualizeProtocolComposition)
registerBenchmark(DictOfArraysToArrayOfDicts)
registerBenchmark(Dictionary)
registerBenchmark(Dictionary2)
Expand Down
63 changes: 49 additions & 14 deletions lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -775,10 +775,10 @@ bool SILCombiner::canReplaceArg(FullApplySite Apply,
/// (@in or @owned convention).
struct ConcreteArgumentCopy {
SILValue origArg;
CopyAddrInst *tempArgCopy;
AllocStackInst *tempArg;

ConcreteArgumentCopy(SILValue origArg, CopyAddrInst *tempArgCopy)
: origArg(origArg), tempArgCopy(tempArgCopy) {
ConcreteArgumentCopy(SILValue origArg, AllocStackInst *tempArg)
: origArg(origArg), tempArg(tempArg) {
assert(origArg->getType().isAddress());
}

Expand Down Expand Up @@ -817,9 +817,18 @@ struct ConcreteArgumentCopy {
SILBuilderWithScope B(apply.getInstruction(), BuilderCtx);
auto loc = apply.getLoc();
auto *ASI = B.createAllocStack(loc, CEI.ConcreteValue->getType());
auto *CAI = B.createCopyAddr(loc, CEI.ConcreteValue, ASI, IsNotTake,
IsInitialization_t::IsInitialization);
return ConcreteArgumentCopy(origArg, CAI);
// If the type is an address, simple copy it.
if (CEI.ConcreteValue->getType().isAddress()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally tried to early-exit here if we didn't have an address but, it turned out to be easier just to generalize this a bit to work with stores as well.

B.createCopyAddr(loc, CEI.ConcreteValue, ASI, IsNotTake,
IsInitialization_t::IsInitialization);
} else {
// Otherwise, we probably got the value from the source of a store
// instruction so, create a store into the temporary argument.
B.createStrongRetain(loc, CEI.ConcreteValue, B.getDefaultAtomicity());
B.createStore(loc, CEI.ConcreteValue, ASI,
StoreOwnershipQualifier::Unqualified);
}
return ConcreteArgumentCopy(origArg, ASI);
}
};

Expand Down Expand Up @@ -850,20 +859,19 @@ SILInstruction *SILCombiner::createApplyWithConcreteType(
FullApplySite Apply,
const llvm::SmallDenseMap<unsigned, ConcreteOpenedExistentialInfo> &COAIs,
SILBuilderContext &BuilderCtx) {

// Ensure that the callee is polymorphic.
assert(Apply.getOrigCalleeType()->isPolymorphic());

// Create the new set of arguments to apply including their substitutions.
SubstitutionMap NewCallSubs = Apply.getSubstitutionMap();
SmallVector<SILValue, 8> NewArgs;
bool UpdatedArgs = false;
unsigned ArgIdx = 0;
// Push the indirect result arguments.
for (unsigned EndIdx = Apply.getSubstCalleeConv().getSILArgIndexOfFirstParam();
ArgIdx < EndIdx; ++ArgIdx) {
NewArgs.push_back(Apply.getArgument(ArgIdx));
}

// Transform the parameter arguments.
SmallVector<ConcreteArgumentCopy, 4> concreteArgCopies;
for (unsigned EndIdx = Apply.getNumArguments(); ArgIdx < EndIdx; ++ArgIdx) {
Expand All @@ -882,16 +890,18 @@ SILInstruction *SILCombiner::createApplyWithConcreteType(
NewArgs.push_back(Apply.getArgument(ArgIdx));
continue;
}
UpdatedArgs = true;

// Ensure that we have a concrete value to propagate.
assert(CEI.ConcreteValue);

auto argSub =
ConcreteArgumentCopy::generate(CEI, Apply, ArgIdx, BuilderCtx);
if (argSub) {
concreteArgCopies.push_back(*argSub);
NewArgs.push_back(argSub->tempArgCopy->getDest());
} else
NewArgs.push_back(argSub->tempArg);
} else {
NewArgs.push_back(CEI.ConcreteValue);
}

// Form a new set of substitutions where the argument is
// replaced with a concrete type.
Expand All @@ -914,7 +924,33 @@ SILInstruction *SILCombiner::createApplyWithConcreteType(
});
}

if (!UpdatedArgs) {
// We need to make sure that we can a) update Apply to use the new args and b)
// at least one argument has changed. If no arguments have changed, we need
// to return nullptr. Otherwise, we will have an infinite loop.
auto substTy =
Apply.getCallee()
->getType()
.substGenericArgs(Apply.getModule(), NewCallSubs,
Apply.getFunction()->getTypeExpansionContext())
.getAs<SILFunctionType>();
SILFunctionConventions conv(substTy,
SILModuleConventions(Apply.getModule()));
bool canUpdateArgs = true;
bool madeUpdate = false;
for (unsigned index = 0; index < conv.getNumSILArguments(); ++index) {
// Make sure that *all* the arguments in both the new substitution function
// and our vector of new arguments have the same type.
canUpdateArgs &=
conv.getSILArgumentType(index) == NewArgs[index]->getType();
// Make sure that we have changed at least one argument.
madeUpdate |=
NewArgs[index]->getType() != Apply.getArgument(index)->getType();
}

// If we can't update the args (because of a type mismatch) or the args don't
// change, bail out by removing the instructions we've added and returning
// nullptr.
if (!canUpdateArgs || !madeUpdate) {
// Remove any new instructions created while attempting to optimize this
// apply. Since the apply was never rewritten, if they aren't removed here,
// they will be removed later as dead when visited by SILCombine, causing
Expand Down Expand Up @@ -961,8 +997,7 @@ SILInstruction *SILCombiner::createApplyWithConcreteType(
auto cleanupLoc = RegularLocation::getAutoGeneratedLocation();
for (ConcreteArgumentCopy &argCopy : llvm::reverse(concreteArgCopies)) {
cleanupBuilder.createDestroyAddr(cleanupLoc, argCopy.origArg);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will destroy the value that you stored, but you never retained that value. In non-OSSA, all the ownership operations need to be explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, something around here needs to be updated. See my below comments. I'm curious how this worked with before this patch. Wouldn't the same issue occur for copy_addr?

cleanupBuilder.createDeallocStack(cleanupLoc,
argCopy.tempArgCopy->getDest());
cleanupBuilder.createDeallocStack(cleanupLoc, argCopy.tempArg);
}
}
return NewApply.getInstruction();
Expand Down
54 changes: 29 additions & 25 deletions lib/SILOptimizer/Utils/Existential.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ findInitExistentialFromGlobalAddr(GlobalAddrInst *GAI, SILInstruction *Insn) {

/// Returns the instruction that initializes the given stack address. This is
/// currently either a init_existential_addr, unconditional_checked_cast_addr,
/// or copy_addr (if the instruction initializing the source of the copy cannot
/// be determined). Returns nullptr if the initializer does not dominate the
/// alloc_stack user \p ASIUser. If the value is copied from another stack
/// store, or copy_addr (if the instruction initializing the source of the copy
/// cannot be determined). Returns nullptr if the initializer does not dominate
/// the alloc_stack user \p ASIUser. If the value is copied from another stack
/// location, \p isCopied is set to true.
///
/// allocStackAddr may either itself be an AllocStackInst or an
Expand Down Expand Up @@ -111,6 +111,19 @@ static SILInstruction *getStackInitInst(SILValue allocStackAddr,
}
continue;
}
if (auto *store = dyn_cast<StoreInst>(User)) {
if (store->getDest() == allocStackAddr) {
if (SingleWrite)
return nullptr;
SingleWrite = store;
// When we support OSSA here, we need to insert a new copy of the value
// before `store` (and make sure that the copy is destroyed when
// replacing the apply operand).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My previous comment might have been misleading because I didn't realize we're inserting a new destroy. In OSSA, the store will do an implicit copy. But, currently, you need to copy the stored value, probably using a retain_value before the store.

To keep the ownership clean what probably ends up happening is this I think:

We have an original existential argument value and a new concrete argument value.

The original existential argument value is removed from a "consuming" call operand, and a new destroy_addr of that value is generated in its place (right after the call).

The copy of the new concrete argument value is emitted. That copy is then consumed by the call argument that it is assigned to.

That will be fairly straightforward to update for OSSA later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a question I've been wanting to ask for a while: what's the relationship between OSSA and reference counting? Is it handled differently in OSSA?

Thanks for laying it all out. That mostly makes sense except for the part about adding a new destroy_addr instruction. Currently, the destroy_addr is for the new value (the "tempArg") are you saying it should instead (or in addition) be for the old argument?

Again, thank you for all the help and information you've given me in this patch. I'm very grateful.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OSSA is a more strict form of SIL. That's all. It means that each SSA value has its own ownership. So you can't arbitrarily retain/release values and hope they all balance out. A value can only be consumed once (by a destroy_value or owned argument) and cannot be used after it is consumed.

It looks to me like the new destroy at the call site is only created when a ConcreteArgumentCopy was generated for the argument. Since ConcreateArgumentCopy always copies/retains, the new destroy will balance that out.

There's another issue I was worried about, which is a totally separate issue from ConcreteArgumentCopy: getStackInitInst will discover the source of the store instruction. We use that as the new ConcreteValue. But, we don't set isConcreteValueCopied. So, for stored values, we never check canReplaceCopiedArg. The purpose of canReplaceCopiedArg is to ensure that the concrete value is "available" at the call site. So you could have:

address = alloc_stack
retain concreteValue // copy to stack
store concreteValue to address
release concreteValue // concreteValue out of scope
apply (@guaranteed address)
use address
destroy address

Optimizes to:

address = alloc_stack
retain concreteValue // copy to stack
store concreteValue to address
release concreteValue // no longer makes sense
apply (@guaranteed concreteValue)
use address
destroy address

That's kind of an abuse of SIL, but it's fine for non-OSSA, and in
fact optimal because ARCCodeMotion will turn it into this:

address = alloc_stack
store concreteValue to address
apply (@guaranteed concreteValue)
use address
destroy address

But with OSSA it will be:

address = alloc_stack
store [init] concreteValue to address
destroy concreteValue
apply (address)
use address
destroy address

Optimizes to:

address = alloc_stack
copied = copy_value concreteValue
store [init] concreteValue to address
destroy concreteValue
apply (concreteValue)
destroy copied
use address
destroy address

I think this PR is fine as-is since you've added a comment for how to handle OSSA later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Really good to know, thank you for taking the time to explain. I'm happy to work on updating more parts of the codebase to use OSSA if you or someone else can help me get started (the knowledge transfer may be more effort than it's worth, though).

assert(store->getOwnershipQualifier() ==
StoreOwnershipQualifier::Unqualified);
}
continue;
}
if (isa<InitExistentialAddrInst>(User)) {
if (SingleWrite)
return nullptr;
Expand Down Expand Up @@ -144,6 +157,9 @@ static SILInstruction *getStackInitInst(SILValue allocStackAddr,
if (BB != allocStackAddr->getParentBlock() && BB != ASIUser->getParent())
return nullptr;

if (auto *store = dyn_cast<StoreInst>(SingleWrite))
return store;

if (auto *IE = dyn_cast<InitExistentialAddrInst>(SingleWrite))
return IE;

Expand Down Expand Up @@ -180,24 +196,6 @@ static SILInstruction *getStackInitInst(SILValue allocStackAddr,
return CAI;
}

/// Return the address of the value used to initialize the given stack location.
/// If the value originates from init_existential_addr, then it will be a
/// different type than \p allocStackAddr.
static SILValue getAddressOfStackInit(SILValue allocStackAddr,
SILInstruction *ASIUser, bool &isCopied) {
SILInstruction *initI = getStackInitInst(allocStackAddr, ASIUser, isCopied);
if (!initI)
return SILValue();

if (auto *IEA = dyn_cast<InitExistentialAddrInst>(initI))
return IEA;

if (auto *CAI = dyn_cast<CopyAddrInst>(initI))
return CAI->getSrc();

return SILValue();
}

/// Check if the given operand originates from a recognized OpenArchetype
/// instruction. If so, return the Opened, otherwise return nullptr.
OpenedArchetypeInfo::OpenedArchetypeInfo(Operand &use) {
Expand All @@ -207,11 +205,17 @@ OpenedArchetypeInfo::OpenedArchetypeInfo(Operand &use) {
// Handle:
// %opened = open_existential_addr
// %instance = alloc $opened
// copy_addr %opened to %stack
// <copy|store> %opened to %stack
// <opened_use> %instance
if (auto stackInitVal =
getAddressOfStackInit(instance, user, isOpenedValueCopied)) {
openedVal = stackInitVal;
if (auto *initI = getStackInitInst(instance, user, isOpenedValueCopied)) {
// init_existential_addr isn't handled here because it isn't considered an
// "opened" archtype. init_existential_addr should be handled by
// ConcreteExistentialInfo.

if (auto *CAI = dyn_cast<CopyAddrInst>(initI))
openedVal = CAI->getSrc();
if (auto *store = dyn_cast<StoreInst>(initI))
openedVal = store->getSrc();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existential value stored on the stack here might not be the same value on the stack when it is opened.

Also, the value stored on the stack might no longer be live at the apply site.

There's a whole bunch of complexity in getAddressOfStackInit and canReplaceCopiedValue to deal with on-stack existentials.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think I understand your first comment but, could you elaborate on the following?

Also, the value stored on the stack might no longer be live at the apply site.

I've updated line 222 to use getAddressOfStackInit(..., firstUse->getUser(), ...) instead of a dyn_cast<StoreInst>(...). Do you think this will be sufficient?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the abstract, we want to avoid producing a sequence like

r = open_existential_ref
destroy(r)
apply(r)

That shouldn't actually be a problem though because we're not in OSSA form so there will only be 'retain/release_value(r)' operations. As long as none of the retains get eliminated during this transformation, it should be safe.

A lot of the complexity in the existing code comes from the fact that it's handling 'open_existential_addr', which can be destroyed with 'destroy_addr'.

The main thing here is to prove that the stored value is the value used by the apply. One way to do that is to recognize all uses of the alloc_stack and make sure only one of them is a store. getStackInitInst already does that, but only handles copy_addr, not store.

Please try to write some .sil test cases to for corner cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, that was extremely helpful.

A lot of the complexity in the existing code comes from the fact that it's handling 'open_existential_addr', which can be destroyed with 'destroy_addr'.

The constructor I modified does handle open_existential_addr but the specific optimization I'm working on should only handle open_existential_ref, right?

The main thing here is to prove that the stored value is the value used by the apply. One way to do that is to recognize all uses of the alloc_stack and make sure only one of them is a store.

I've added more checking around the above code but, I suspect it isn't quite right yet. Should the stack alloc instruction ever be used in more than the initial store, the apply, and then the dealloc instructions? If not it may make it a bit easier to filter out what we can optimize. But that might be too harsh of a filter.

Anyway, I've added many more tests and have uncovered several bugs. Right now I'm in the process of testing those bugs against master to see if they're my issue and if so, fixing them. I'm going to be pretty busy this week with other things so, turn around on this patch might be a bit slow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atrick I've added a dominance order check which should ensure that there aren't those kinds of issues. I've also added one .sil test. I plan on writing some more, what other edge cases should I cover?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Developing a breadth of .sil test cases is extremely helpful. That's what this code was missing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to generalize this pattern match, I think it should be part of the existing utilities so there's no new code in this function. getStackInitInst should be extended so it can return a store. It already checks for dominance of ASIUser. It's a bit more general than your new code. Whether it should set isCopied in that case is very subtle. The purpose of that flag is to make sure the original value isn't destroyed before the apply that needs to use it. In the case of a store it's simpler of we do not set isCopied, but then we should assert that the store's ownership qualifier is Unqualified (non-OSSA) and comment when we support OSSA here, we need to insert a new copy of the value before the store (and make sure that copy is destroyed when replacing the apply operand).

FYI: in non-OSSA it should just work. If we have

retain val
store val to address
destroy val
use address
destroy_addr

It's actually ok in non-OSSA to reuse the original value like this:

retain val
store val to address
destroy val
use val
destroy_addr

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea-- that's much cleaner. Also, thanks for the information about OSSA/non-OSSA and when copies are needed. Very interesting.

}
if (auto *Open = dyn_cast<OpenExistentialAddrInst>(openedVal)) {
Expand Down
3 changes: 0 additions & 3 deletions lib/SILOptimizer/Utils/InstOptUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -814,9 +814,6 @@ SILValue swift::castValueToABICompatibleType(SILBuilder *builder,
if (srcTy == destTy)
return value;

assert(srcTy.isAddress() == destTy.isAddress()
&& "Addresses aren't compatible with values");

if (srcTy.isAddress() && destTy.isAddress()) {
// Cast between two addresses and that's it.
return builder->createUncheckedAddrCast(loc, value, destTy);
Expand Down
Loading