Skip to content

Commit 4a49a26

Browse files
authored
Merge pull request swiftlang#32703 from eeckstein/fold-kp-offset2
Reinstate "SILCombine: Constant-fold MemoryLayout<T>.offset(of: \.literalKeyPath)"
2 parents 0d44dad + 1a0c4d0 commit 4a49a26

File tree

4 files changed

+358
-20
lines changed

4 files changed

+358
-20
lines changed

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,11 @@ class SILCombiner :
246246
bool tryOptimizeKeypath(ApplyInst *AI);
247247
bool tryOptimizeInoutKeypath(BeginApplyInst *AI);
248248
bool tryOptimizeKeypathApplication(ApplyInst *AI, SILFunction *callee);
249-
bool tryOptimizeKeypathKVCString(ApplyInst *AI, SILDeclRef callee);
250-
249+
bool tryOptimizeKeypathOffsetOf(ApplyInst *AI, FuncDecl *calleeFn,
250+
KeyPathInst *kp);
251+
bool tryOptimizeKeypathKVCString(ApplyInst *AI, FuncDecl *calleeFn,
252+
KeyPathInst *kp);
253+
251254
// Optimize concatenation of string literals.
252255
// Constant-fold concatenation of string literals known at compile-time.
253256
SILInstruction *optimizeConcatenationOfStringLiterals(ApplyInst *AI);

lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp

Lines changed: 151 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,130 @@ bool SILCombiner::tryOptimizeKeypathApplication(ApplyInst *AI,
271271
return true;
272272
}
273273

274+
/// Replaces a call of the getter of AnyKeyPath._storedInlineOffset with a
275+
/// "constant" offset, in case of a keypath literal.
276+
///
277+
/// "Constant" offset means a series of struct_element_addr and
278+
/// tuple_element_addr instructions with a 0-pointer as base address.
279+
/// These instructions can then be lowered to "real" constants in IRGen for
280+
/// concrete types, or to metatype offset lookups for generic or resilient types.
281+
///
282+
/// Replaces:
283+
/// %kp = keypath ...
284+
/// %offset = apply %_storedInlineOffset_method(%kp)
285+
/// with:
286+
/// %zero = integer_literal $Builtin.Word, 0
287+
/// %null_ptr = unchecked_trivial_bit_cast %zero to $Builtin.RawPointer
288+
/// %null_addr = pointer_to_address %null_ptr
289+
/// %projected_addr = struct_element_addr %null_addr
290+
/// ... // other address projections
291+
/// %offset_ptr = address_to_pointer %projected_addr
292+
/// %offset_builtin_int = unchecked_trivial_bit_cast %offset_ptr
293+
/// %offset_int = struct $Int (%offset_builtin_int)
294+
/// %offset = enum $Optional<Int>, #Optional.some!enumelt, %offset_int
295+
bool SILCombiner::tryOptimizeKeypathOffsetOf(ApplyInst *AI,
296+
FuncDecl *calleeFn,
297+
KeyPathInst *kp) {
298+
auto *accessor = dyn_cast<AccessorDecl>(calleeFn);
299+
if (!accessor || !accessor->isGetter())
300+
return false;
301+
302+
AbstractStorageDecl *storage = accessor->getStorage();
303+
DeclName name = storage->getName();
304+
if (!name.isSimpleName() ||
305+
(name.getBaseIdentifier().str() != "_storedInlineOffset"))
306+
return false;
307+
308+
KeyPathPattern *pattern = kp->getPattern();
309+
SubstitutionMap patternSubs = kp->getSubstitutions();
310+
CanType rootTy = pattern->getRootType().subst(patternSubs)->getCanonicalType();
311+
CanType parentTy = rootTy;
312+
313+
// First check if _storedInlineOffset would return an offset or nil. Basically
314+
// only stored struct and tuple elements produce an offset. Everything else
315+
// (e.g. computed properties, class properties) result in nil.
316+
bool hasOffset = true;
317+
for (const KeyPathPatternComponent &component : pattern->getComponents()) {
318+
switch (component.getKind()) {
319+
case KeyPathPatternComponent::Kind::StoredProperty: {
320+
321+
// Handle the special case of C tail-allocated arrays. IRGen would
322+
// generate an undef offset for struct_element_addr of C tail-allocated
323+
// arrays.
324+
VarDecl *propDecl = component.getStoredPropertyDecl();
325+
if (propDecl->hasClangNode() && propDecl->getType()->isVoid())
326+
return false;
327+
328+
if (!parentTy.getStructOrBoundGenericStruct())
329+
hasOffset = false;
330+
break;
331+
}
332+
case KeyPathPatternComponent::Kind::TupleElement:
333+
break;
334+
case KeyPathPatternComponent::Kind::GettableProperty:
335+
case KeyPathPatternComponent::Kind::SettableProperty:
336+
// We cannot predict the offset of fields in resilient types, because it's
337+
// unknown if a resilient field is a computed or stored property.
338+
if (component.getExternalDecl())
339+
return false;
340+
hasOffset = false;
341+
break;
342+
case KeyPathPatternComponent::Kind::OptionalChain:
343+
case KeyPathPatternComponent::Kind::OptionalForce:
344+
case KeyPathPatternComponent::Kind::OptionalWrap:
345+
hasOffset = false;
346+
break;
347+
}
348+
parentTy = component.getComponentType();
349+
}
350+
351+
SILLocation loc = AI->getLoc();
352+
SILValue result;
353+
354+
if (hasOffset) {
355+
SILType rootAddrTy = SILType::getPrimitiveAddressType(rootTy);
356+
SILValue rootAddr = Builder.createBaseAddrForOffset(loc, rootAddrTy);
357+
358+
auto projector = KeyPathProjector::create(kp, rootAddr, loc, Builder);
359+
if (!projector)
360+
return false;
361+
362+
// Create the address projections of the keypath.
363+
SILType ptrType = SILType::getRawPointerType(Builder.getASTContext());
364+
SILValue offsetPtr;
365+
projector->project(KeyPathProjector::AccessType::Get, [&](SILValue addr) {
366+
offsetPtr = Builder.createAddressToPointer(loc, addr, ptrType);
367+
});
368+
369+
// The result of the _storedInlineOffset call should be Optional<Int>. If
370+
// not, something is wrong with the stdlib. Anyway, if it's not like we
371+
// expect, bail.
372+
SILType intType = AI->getType().getOptionalObjectType();
373+
if (!intType)
374+
return false;
375+
StructDecl *intDecl = intType.getStructOrBoundGenericStruct();
376+
if (!intDecl || intDecl->getStoredProperties().size() != 1)
377+
return false;
378+
VarDecl *member = intDecl->getStoredProperties()[0];
379+
CanType builtinIntTy = member->getType()->getCanonicalType();
380+
if (!isa<BuiltinIntegerType>(builtinIntTy))
381+
return false;
382+
383+
// Convert the projected address back to an optional integer.
384+
SILValue offset = Builder.createUncheckedBitCast(loc, offsetPtr,
385+
SILType::getPrimitiveObjectType(builtinIntTy));
386+
SILValue offsetInt = Builder.createStruct(loc, intType, { offset });
387+
result = Builder.createOptionalSome(loc, offsetInt, AI->getType());
388+
} else {
389+
// The keypath has no offset.
390+
result = Builder.createOptionalNone(loc, AI->getType());
391+
}
392+
AI->replaceAllUsesWith(result);
393+
eraseInstFromFunction(*AI);
394+
++NumOptimizedKeypaths;
395+
return true;
396+
}
397+
274398
/// Try to optimize a keypath KVC string access on a literal key path.
275399
///
276400
/// Replace:
@@ -279,17 +403,8 @@ bool SILCombiner::tryOptimizeKeypathApplication(ApplyInst *AI,
279403
/// With:
280404
/// %string = string_literal "blah"
281405
bool SILCombiner::tryOptimizeKeypathKVCString(ApplyInst *AI,
282-
SILDeclRef callee) {
283-
if (AI->getNumArguments() != 1) {
284-
return false;
285-
}
286-
if (!callee.hasDecl()) {
287-
return false;
288-
}
289-
auto calleeFn = dyn_cast<FuncDecl>(callee.getDecl());
290-
if (!calleeFn)
291-
return false;
292-
406+
FuncDecl *calleeFn,
407+
KeyPathInst *kp) {
293408
if (!calleeFn->getAttrs()
294409
.hasSemanticsAttr(semantics::KEYPATH_KVC_KEY_PATH_STRING))
295410
return false;
@@ -300,11 +415,6 @@ bool SILCombiner::tryOptimizeKeypathKVCString(ApplyInst *AI,
300415
if (!objTy || objTy.getStructOrBoundGenericStruct() != C.getStringDecl())
301416
return false;
302417

303-
KeyPathInst *kp
304-
= KeyPathProjector::getLiteralKeyPath(AI->getArgument(0));
305-
if (!kp || !kp->hasPattern())
306-
return false;
307-
308418
auto objcString = kp->getPattern()->getObjCString();
309419

310420
SILValue literalValue;
@@ -357,10 +467,33 @@ bool SILCombiner::tryOptimizeKeypath(ApplyInst *AI) {
357467
return tryOptimizeKeypathApplication(AI, callee);
358468
}
359469

360-
if (auto method = dyn_cast<ClassMethodInst>(AI->getCallee())) {
361-
return tryOptimizeKeypathKVCString(AI, method->getMember());
470+
// Try optimize keypath method calls.
471+
auto *methodInst = dyn_cast<ClassMethodInst>(AI->getCallee());
472+
if (!methodInst)
473+
return false;
474+
475+
if (AI->getNumArguments() != 1) {
476+
return false;
477+
}
478+
479+
SILDeclRef callee = methodInst->getMember();
480+
if (!callee.hasDecl()) {
481+
return false;
362482
}
483+
auto *calleeFn = dyn_cast<FuncDecl>(callee.getDecl());
484+
if (!calleeFn)
485+
return false;
486+
487+
KeyPathInst *kp = KeyPathProjector::getLiteralKeyPath(AI->getArgument(0));
488+
if (!kp || !kp->hasPattern())
489+
return false;
363490

491+
if (tryOptimizeKeypathOffsetOf(AI, calleeFn, kp))
492+
return true;
493+
494+
if (tryOptimizeKeypathKVCString(AI, calleeFn, kp))
495+
return true;
496+
364497
return false;
365498
}
366499

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
public struct TestStruct {
3+
public var x: Int
4+
public var y: Int
5+
}

0 commit comments

Comments
 (0)