-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Concrete type propagation using ProtocolConformanceAnalysis #17373
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@slavapestov Second PR for concrete type propagation of self arguments using ProtocolConformanceAnalysis, that was merged couple of days ago. |
@@ -298,6 +312,20 @@ class SILCombiner : | |||
WitnessMethodInst *WMI); | |||
SILInstruction *propagateConcreteTypeOfInitExistential(FullApplySite AI); | |||
|
|||
/// Propagate concrete types from ProtocolConformanceAnalysis. | |||
SILInstruction *propagateConcreteTypeFromPCA(FullApplySite AI); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"propagateSoleConformingType"
|
||
/// Internal method to prepare and check code for concrete type propagation | ||
/// using ProtocolConformanceAnalysis via unchecked cast instructions. | ||
bool prepareNCheckConcreteTypePropagationFromPCA( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"prepareAndCheckConcreteTypePropagation"
Maybe define a class with arguments?
|
||
/// Traverse ProtocolConformanceMapCache from ProtocolConformanceAnalysis | ||
/// recursively to determine concrete type. | ||
NominalTypeDecl *findSoleConformingTypeFromPCA(ProtocolDecl *Protocol); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"findSoleConformingType"
/// class, struct or enum type. | ||
NominalTypeDecl * | ||
SILCombiner::findSoleConformingTypeFromPCA(ProtocolDecl *Protocol) { | ||
SmallVector<ProtocolDecl *, 16> PDWorkList; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of this except for the CHA part should go into the protocol conformance analysis
return false; | ||
|
||
/// Ignore already visited Apply sites. | ||
if (VisitedAIForPCA.find(Apply) != VisitedAIForPCA.end()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this introduce a use-after-free?
return false; | ||
|
||
// Do not handle unbounded generics. | ||
auto ElementType = NTD->getDeclaredType(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better way to check: NTD->isGenericContext(). And move this into findSoleConformingTypeFromPCA()
|
||
SILValue Self = Apply.getSelfArgument(); | ||
auto *WMI = dyn_cast<WitnessMethodInst>(Apply.getCallee()); | ||
if (!WMI) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if I'm calling a statically-dispatched protocol extension method?
extension P { func f() { ... } }
@@ -858,7 +1029,7 @@ SILInstruction *SILCombiner::propagateConcreteTypeOfInitExistential( | |||
SILInstruction *InitExistential = findInitExistential( | |||
Apply, Self, OpenedArchetype, OpenedArchetypeDef, isCopied); | |||
if (!InitExistential) | |||
return nullptr; | |||
return propagateConcreteTypeFromPCA(Apply); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this to the callers of propagateConcreteTypeOfInitExistential()
// CHECK: integer_literal | ||
// CHECK: ref_element_addr | ||
// CHECK: open_existential_addr | ||
// CHECK: witness_method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate these witness_methods so its easier to read the test case
return false; | ||
|
||
// Determine the sole conforming type. | ||
auto *NTD = findSoleConformingTypeFromPCA(Protocol); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do this first, to avoid doing everything else
bb9fd48
to
e0a80de
Compare
@atrick @slavapestov can you review this PR now? I have taken care of all the comments. |
// Empty initilization of ConcreteExistentialInfo. This is used when the | ||
// concrete type is determined via ProtocolConformanceAnalysis. The fields | ||
// are populated during sil_combiner. | ||
ConcreteExistentialInfo() {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of default initializing, can you pass around an Optional<ConcreteExistentialInfo>
?
@@ -30,7 +30,7 @@ class NominalTypeWalker : public ASTWalker { | |||
|
|||
public: | |||
NominalTypeWalker(ProtocolConformanceAnalysis::ProtocolConformanceMap | |||
&ProtocolConformanceCache) | |||
&ProtocolConformanceCache) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary change?
ProtocolConformanceAnalysis::findSoleConformingType(ProtocolDecl *Protocol) { | ||
SmallVector<ProtocolDecl *, 16> PDWorkList; | ||
SmallPtrSet<ProtocolDecl *, 8> VisitedPDs; | ||
SmallPtrSet<NominalTypeDecl *, 8> ConformanceSet; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this just be a NominalTypeDecl *
?
/// class, struct or enum type. | ||
NominalTypeDecl * | ||
ProtocolConformanceAnalysis::findSoleConformingType(ProtocolDecl *Protocol) { | ||
SmallVector<ProtocolDecl *, 16> PDWorkList; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use 2 for the default size
if (!VisitedPDs.count(Proto)) | ||
PDWorkList.push_back(Proto); | ||
} else { // Classes, Structs and Enums are added here. | ||
ConformanceSet.insert(ConformingNTD); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bail out here
if (WMI->use_empty()) | ||
eraseInstFromFunction(*WMI); | ||
} | ||
auto *NewAI = createApplyWithConcreteType(Apply, CEI, BuilderCtx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create a new overload of createApplyWithConcreteType()
so you can share this block of code between here and SILCombiner::propagateConcreteTypeOfInitExistential()
@@ -711,7 +848,8 @@ SILCombiner::createApplyWithConcreteType(FullApplySite Apply, | |||
} | |||
// The apply can only be rewritten in terms of the concrete value if it is | |||
// legal to pass that value as the self argument. | |||
if (CEI.isCopied && !canReplaceCopiedSelf(Apply, CEI.InitExistential, DA)) | |||
if (CEI.InitExistential && CEI.isCopied && | |||
!canReplaceCopiedSelf(Apply, CEI.InitExistential, DA)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@atrick suggested this: if (isCopied && (!InitExistential || !canReplaceCopiedSelf(Apply, InitExistential)
which seem reasonable.
if (!propagateConcreteTypeOfInitExistential(AI, WMI) && | ||
(WMI = dyn_cast<WitnessMethodInst>(AI->getCallee()))) { | ||
// Propagate concrete type from ProtocolConformanceAnalysis. | ||
propagateSoleConformingType(AI, WMI); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if propagateConcreteTypeOfInitExistential() returned a boolean indicating if the WMI was replaced, you would only call propagateSoleConformingType()
if it returned false, and not have to worry about getting the WMI out again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. Will create a new PR with propagateConcreteTypeOfInitExistential() returning true/false.
if (!propagateConcreteTypeOfInitExistential(AI, WMI) && | ||
(WMI = dyn_cast<WitnessMethodInst>(AI->getCallee()))) { | ||
// Propagate concrete type from ProtocolConformanceAnalysis. | ||
propagateSoleConformingType(AI, WMI); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
} | ||
} | ||
|
||
// case 5: Generic protocol -- should not optimize |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generic conforming type
@atrick @slavapestov Any possibility of getting this review done? Thanks. |
|
||
SILValue Self = Apply.getSelfArgument(); | ||
auto *PD = WMI->getLookupProtocol(); | ||
ConcreteExistentialInfo CEI(Apply.getSelfArgumentOperand(), true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it really makes sense to construct a fake, invalid ConcreteExistentialInfo
, then fill in its fields here. It seems like this should be an alternate constructor.
@atrick Moved the code that separately populated ConcreteExistentialInfo as an alternative constructor for ConcreteExistentialInfo. Can you check and let me know if you have any other concerns? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really understand what's happening yet. Can you clarify some of the points below?
if ((CD = dyn_cast<ClassDecl>(NTD)) && | ||
(CD->getEffectiveAccess() == AccessLevel::Open || | ||
CHA->hasKnownDirectSubclasses(CD) || | ||
CHA->hasKnownIndirectSubclasses(CD))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, how can a class have indirect subclasses without having direct subclasses?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding was that they were disjoint sets. But looking at the code closely seems like Indirect Subclasses are derived from DirectSubClasses. So, check for hasKnownDirectSubClasses should suffice in this case.
ConcreteExistentialInfo(ConcreteExistentialInfo &) = delete; | ||
|
||
/// We do not not need to check for InitExistential not being not null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the comment. Aren't you allowing InitExistential to be null when the concrete type was derived through analysis instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will rephrase the comment.
// findInitExistential for the argument. | ||
isCopied = false; | ||
OpenedArchetypeDef = Arg; | ||
InitExistential = findInitExistential(ArgOperand, OpenedArchetype, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the returned InitExistential be nullptr? Please comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, InitExistential can be nullptr. Will update the comment. Thanks.
InitExistential = findInitExistential(ArgOperand, OpenedArchetype, | ||
OpenedArchetypeDef, isCopied); | ||
|
||
// If no InitExistential is found, check if the argument is already an opened |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I'm really confused now. The code below runs regardless of whether InitExistential
was found or not. How is it related to InitExstential
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dumb question: if the caller already knows the ConcreteType and Protocol, why do we need to bother looking for the InitExistential
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still need the OpenedArchetypeDef to be able to cast it to the concrete type. findInitExistential helps me determine this OpenedArchetypeDef. For cases where OpenedArchetypeDef can not be found (because there was no InitExistential), then the code below does extra effort in trying to find if Arg is itself OpenedArcheTypeDef or AllocStackInst, in which case it determines the OpenedArcheTypeDef. I will update the comment. Let me know if this makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the part I was missing. It almost looked like you could reuse the same constructor for both the current case and the CHA/PCA case but just pass in an optional ConcreteType in the later case.
ConcreteExistentialInfo::ConcreteExistentialInfo( | ||
ProtocolConformanceAnalysis *PCA, ClassHierarchyAnalysis *CHA, | ||
SILBuilder &Builder, ProtocolDecl *Protocol, FullApplySite Apply, | ||
SILValue &Arg, Operand &ArgOperand) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem like a clean encapsulation anymore. Why can't the caller just pass in ConcreteType
and Protocol
when it can obtain those from its analyses? Why do you need to pass in PCA
and CHA
?
Apply
, Arg
, and ArgOperand
all seem redundant. Why don't we just pass in a single openedUse
(the open_existential_ref's use) just like the original constructor?
Also, the top-level comment on struct ConcreteExistentialInfo
should be amended to mention new SIL patterns that we handle. Or move the existing comments onto the original constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I wanted it this way is that I can create ConcreteExistentialInfo from another optimization #17366. I did not want code duplication later. May be for now, I will simplify the constructor as you suggested and worry about it later with a common interface for the other PR. Again, will update the comment.
@atrick @slavapestov Cleaned up the interfaces a bit. Please have a look now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great. Just a few more comments...
isCopied = false; | ||
OpenedArchetypeDef = Arg; | ||
InitExistential = findInitExistential(ArgOperand, OpenedArchetype, | ||
OpenedArchetypeDef, isCopied); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the comments. If InitExistential is non-null, do you think it would be useful to assert that it's concrete type is the same as the passed in ConcreteType?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea!
|
||
// We have the open_existential; we still need the conformance. | ||
auto ConformanceRef = | ||
M.getSwiftModule()->lookupConformance(ConcreteType, Protocol); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're generally not supposed to call SwiftModule::lookupConformance
from the optimizer because the conformance hasn't necessarilly been type checked. In this case, I don't really see an alternative though. @slavapestov , do you have any suggestions? Are there certain conditions where we can say that it's safe to do this?
auto *URCI = | ||
Builder.createUncheckedRefCast(OER->getLoc(), OER, ConcreteSILType); | ||
OpenedArchetype = OER->getType().getASTType()->castTo<ArchetypeType>(); | ||
ConcreteValue = URCI; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, this works, but it's kind of hacky because ConcreteExistentialInfo is supposed to be a mini-analysis that just summarizes all the various SIL patterns we may encounter where the common thread is that we have an open_existential as the self
argument of an apply. It's confusing if an analysis utility also mutates the SIL. Rather than passing in a Builder, propagateSoleConformingType
should be responsible for generating the cast that it needs. I think it's fine if you allow a valid ConcreteExistentialInfo to have an invalid ConcreteValue when InitExistential == nullptr.
So maybe you could remove ConcreteValue
from the isValid
and add an assert instead: !InitExistential || ConcreteValue
Then, in your new CHA/PCA case, you could use just use the existing ConcreteValue if an InitExistential is found without doing the cast. Would that make sense? If there's no InitExistential, then propagateSoleConformingType
could generate the cast.
1b3ec63
to
cc1638c
Compare
@atrick @slavapestov Done. Anything else? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Just one argument type to fix, and a side question for @slavapestov that doesn't need to hold up the merge.
// known ConcreteType and ProtocolDecl pair. It determines the | ||
// OpenedArchetypeDef for the ArgOperand that will be used by unchecked_cast | ||
// instructions to cast OpenedArchetypeDef to ConcreteType. | ||
ConcreteExistentialInfo(Operand &ArgOperand, const CanType &ConcreteType, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The convention is to pass CanType
by value:
, CanType ConcreteType,
|
||
// We have the open_existential; we still need the conformance. | ||
auto ConformanceRef = | ||
M.getSwiftModule()->lookupConformance(ConcreteType, Protocol); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still curious what @slavapestov has to say about calling lookupConformance
here. It seems like we should come up with some rules for when it's ok for the optimizer to do this and enforce it in the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case it's fine to call lookupConformance() -- there's no way for the type checker to anticipate a conformance is needed here and store it somewhere. However, the resulting conformance might be incomplete, which will cause problems later in IRGen if you try to use it.
So I would add a check for the conformance being complete.
Also please try to come up with a test case that would fail without this check. It will require the conforming type to be defined in another file, so that when you build in non-WMO mode, the type checker will not check the conformance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... OK, but since this optimization only applies in WMO mode, you won't be able to get a test case. In that case, please just assert that the conformance is complete.
bd61a1d
to
9732bdf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, feel free to merge once you've addressed the other comments.
/// class, struct or enum type. | ||
NominalTypeDecl * | ||
ProtocolConformanceAnalysis::findSoleConformingType(ProtocolDecl *Protocol) { | ||
SmallVector<ProtocolDecl *, 8> PDWorkList; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the result of this be cached?
PDWorkList.push_back(Protocol); | ||
while (!PDWorkList.empty()) { | ||
auto *PD = PDWorkList.pop_back_val(); | ||
// Protocols must have internal or lower access scopes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just access, not access scope
} | ||
VisitedPDs.insert(PD); | ||
auto NTDList = getConformances(PD); | ||
if (NTDList.empty()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this check is necessary -- a for loop over an empty collection won't do much work.
Op.set(NewWMI); | ||
} | ||
if (WMI->use_empty()) | ||
eraseInstFromFunction(*WMI); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it has other uses, they could also be replaced with NewWMI. In fact you could avoid passing in the Apply here, and look at all uses of the WMI instead.
// TO | ||
// %4 = open_existential_ref %3 : $P to $@opened P | ||
// %7 = unchecked_ref_cast %4 : $@opened P to $C | ||
// %8 = class_method %7 : $C, #C.foo!1 : (C) -> () -> Int, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The actual replacement of the witness_method with class_method is done in the devirtualization pass, not this function. Also the SIL is just kind of hard to read. Can you instead just write a comment that says it replaces the witness_method instruction with one that has a concrete type, allowing other optimizations to devirtualize it later?
|
||
assert(CEI.ConcreteValue); | ||
|
||
/// Replace the old WitnessMehod with a new one that has concrete type and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: WitnessMehod
|
||
/// Determine the OpenedArchetype. | ||
OpenedArchetype = | ||
OpenedArchetypeDef->getType().getASTType()->castTo<ArchetypeType>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OpenedArchetypeDef->getType().castTo<ArchetypeType>();
|
||
// We have the open_existential; we still need the conformance. | ||
auto ConformanceRef = | ||
M.getSwiftModule()->lookupConformance(ConcreteType, Protocol); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... OK, but since this optimization only applies in WMO mode, you won't be able to get a test case. In that case, please just assert that the conformance is complete.
@atrick I believe you wrote the following test case in sil_combine_concrete_existential.swift. Turns out that my pass determines concrete type for p and makes the p.returnsOptionalIndirect() a direct call.
I have updated the test case by adding another unused struct so that S is not the SoleConformingType:
Let me know if you disagree. |
@rajbarik thanks for fixing the test. |
|
||
/// Determine the ExistentialConformances and SubstitutionMap. | ||
auto ExistentialConformances = ArrayRef<ProtocolConformanceRef>( | ||
ProtocolConformanceRef(ConcreteConformance)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this a use-after-free? I would rely on the implicit conversion between T[]
and ArrayRef<T>
and just pass {ProtocolConformanceRef(ConcreteConformance)}
as the parameter to SubstitutionMap::get below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried this before and even now. I get the following error:
../Existential.cpp:378:21: error: call to 'get' is ambiguous
ExistentialSubs = SubstitutionMap::get(ExistentialSig, {ConcreteType},
^~~~~~~~~~~~~~~~~~~~
.../swift/include/swift/AST/SubstitutionMap.h:105:26: note: candidate function
static SubstitutionMap get(GenericSignature *genericSig,
^.../swift/include/swift/AST/SubstitutionMap.h:118:26: note: candidate function
static SubstitutionMap get(GenericSignature *genericSig,
^
1 error generated.
Code changed:
ExistentialSubs = SubstitutionMap::get(ExistentialSig, {ConcreteType},
/*ExistentialConformances*/ {ProtocolConformanceRef(ConcreteConformance)});
@swift-ci Please test |
@swift-ci Please test source compatibility |
Build failed |
@swift-ci Please test source compatibility |
@swift-ci Please test |
Build failed |
Looks like you need to tweak the regex for 32 bit. Please run |
@slavapestov please run tests. |
@swift-ci Please test |
1 similar comment
@swift-ci Please test |
@rajbarik +1! Congrats! |
@atrick @slavapestov Thanks a lot for all your help! |
@rajbarik Thanks for keeping at it. |
Part 2 of #16739
This peephole optimization propagates concrete types for self arguments and rewrites witness methods using ProtocolConformanceAnalysis!