Skip to content

Commit d7d829c

Browse files
committed
SILCombine: Constant-fold MemoryLayout<T>.offset(of: \.literalKeyPath)
Replace a call of the getter of AnyKeyPath._storedInlineOffset with a "constant" offset, in case of a keypath literal. "Constant" offset means a series of struct_element_addr and tuple_element_addr instructions with a 0-pointer as base address. These instructions can then be lowered to "real" constants in IRGen for concrete types, or to metatype offset lookups for generic or resilient types. Replace: %kp = keypath ... %offset = apply %_storedInlineOffset_method(%kp) with: %zero = integer_literal $Builtin.Word, 0 %null_ptr = unchecked_trivial_bit_cast %zero to $Builtin.RawPointer %null_addr = pointer_to_address %null_ptr %projected_addr = struct_element_addr %null_addr ... // other address projections %offset_ptr = address_to_pointer %projected_addr %offset_builtin_int = unchecked_trivial_bit_cast %offset_ptr %offset_int = struct $Int (%offset_builtin_int) %offset = enum $Optional<Int>, #Optional.some!enumelt, %offset_int rdar://problem/53309403
1 parent 6760555 commit d7d829c

File tree

4 files changed

+350
-20
lines changed

4 files changed

+350
-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: 143 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,122 @@ 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+
if (!parentTy.getStructOrBoundGenericStruct())
321+
hasOffset = false;
322+
break;
323+
}
324+
case KeyPathPatternComponent::Kind::TupleElement:
325+
break;
326+
case KeyPathPatternComponent::Kind::GettableProperty:
327+
case KeyPathPatternComponent::Kind::SettableProperty:
328+
// We cannot predict the offset of fields in resilient types, because it's
329+
// unknown if a resilient field is a computed or stored property.
330+
if (component.getExternalDecl())
331+
return false;
332+
hasOffset = false;
333+
break;
334+
case KeyPathPatternComponent::Kind::OptionalChain:
335+
case KeyPathPatternComponent::Kind::OptionalForce:
336+
case KeyPathPatternComponent::Kind::OptionalWrap:
337+
hasOffset = false;
338+
break;
339+
}
340+
parentTy = component.getComponentType();
341+
}
342+
343+
SILLocation loc = AI->getLoc();
344+
SILValue result;
345+
346+
if (hasOffset) {
347+
SILType rootAddrTy = SILType::getPrimitiveAddressType(rootTy);
348+
SILValue rootAddr = Builder.createBaseAddrForOffset(loc, rootAddrTy);
349+
350+
auto projector = KeyPathProjector::create(kp, rootAddr, loc, Builder);
351+
if (!projector)
352+
return false;
353+
354+
// Create the address projections of the keypath.
355+
SILType ptrType = SILType::getRawPointerType(Builder.getASTContext());
356+
SILValue offsetPtr;
357+
projector->project(KeyPathProjector::AccessType::Get, [&](SILValue addr) {
358+
offsetPtr = Builder.createAddressToPointer(loc, addr, ptrType);
359+
});
360+
361+
// The result of the _storedInlineOffset call should be Optional<Int>. If
362+
// not, something is wrong with the stdlib. Anyway, if it's not like we
363+
// expect, bail.
364+
SILType intType = AI->getType().getOptionalObjectType();
365+
if (!intType)
366+
return false;
367+
StructDecl *intDecl = intType.getStructOrBoundGenericStruct();
368+
if (!intDecl || intDecl->getStoredProperties().size() != 1)
369+
return false;
370+
VarDecl *member = intDecl->getStoredProperties()[0];
371+
CanType builtinIntTy = member->getType()->getCanonicalType();
372+
if (!isa<BuiltinIntegerType>(builtinIntTy))
373+
return false;
374+
375+
// Convert the projected address back to an optional integer.
376+
SILValue offset = Builder.createUncheckedBitCast(loc, offsetPtr,
377+
SILType::getPrimitiveObjectType(builtinIntTy));
378+
SILValue offsetInt = Builder.createStruct(loc, intType, { offset });
379+
result = Builder.createOptionalSome(loc, offsetInt, AI->getType());
380+
} else {
381+
// The keypath has no offset.
382+
result = Builder.createOptionalNone(loc, AI->getType());
383+
}
384+
AI->replaceAllUsesWith(result);
385+
eraseInstFromFunction(*AI);
386+
++NumOptimizedKeypaths;
387+
return true;
388+
}
389+
274390
/// Try to optimize a keypath KVC string access on a literal key path.
275391
///
276392
/// Replace:
@@ -279,17 +395,8 @@ bool SILCombiner::tryOptimizeKeypathApplication(ApplyInst *AI,
279395
/// With:
280396
/// %string = string_literal "blah"
281397
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-
398+
FuncDecl *calleeFn,
399+
KeyPathInst *kp) {
293400
if (!calleeFn->getAttrs()
294401
.hasSemanticsAttr(semantics::KEYPATH_KVC_KEY_PATH_STRING))
295402
return false;
@@ -300,11 +407,6 @@ bool SILCombiner::tryOptimizeKeypathKVCString(ApplyInst *AI,
300407
if (!objTy || objTy.getStructOrBoundGenericStruct() != C.getStringDecl())
301408
return false;
302409

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

310412
SILValue literalValue;
@@ -357,10 +459,33 @@ bool SILCombiner::tryOptimizeKeypath(ApplyInst *AI) {
357459
return tryOptimizeKeypathApplication(AI, callee);
358460
}
359461

360-
if (auto method = dyn_cast<ClassMethodInst>(AI->getCallee())) {
361-
return tryOptimizeKeypathKVCString(AI, method->getMember());
462+
// Try optimize keypath method calls.
463+
auto *methodInst = dyn_cast<ClassMethodInst>(AI->getCallee());
464+
if (!methodInst)
465+
return false;
466+
467+
if (AI->getNumArguments() != 1) {
468+
return false;
469+
}
470+
471+
SILDeclRef callee = methodInst->getMember();
472+
if (!callee.hasDecl()) {
473+
return false;
362474
}
475+
auto *calleeFn = dyn_cast<FuncDecl>(callee.getDecl());
476+
if (!calleeFn)
477+
return false;
478+
479+
KeyPathInst *kp = KeyPathProjector::getLiteralKeyPath(AI->getArgument(0));
480+
if (!kp || !kp->hasPattern())
481+
return false;
363482

483+
if (tryOptimizeKeypathOffsetOf(AI, calleeFn, kp))
484+
return true;
485+
486+
if (tryOptimizeKeypathKVCString(AI, calleeFn, kp))
487+
return true;
488+
364489
return false;
365490
}
366491

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)