From 67605553df29730b8a4154617f4e1cedc4308033 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Wed, 1 Jul 2020 15:03:39 +0200 Subject: [PATCH 1/2] SIL: a new instruction 'base_addr_for_offset' for field offset calculations. The ``base_addr_for_offset`` instruction creates a base address for offset calculations. The result can be used by address projections, like ``struct_element_addr``, which themselves return the offset of the projected fields. IR generation simply creates a null pointer for ``base_addr_for_offset``. --- docs/SIL.rst | 14 ++++++++++++++ include/swift/SIL/SILBuilder.h | 4 ++++ include/swift/SIL/SILCloner.h | 9 +++++++++ include/swift/SIL/SILInstruction.h | 14 ++++++++++++++ include/swift/SIL/SILNodes.def | 2 ++ lib/IRGen/IRGenSIL.cpp | 7 +++++++ lib/SIL/IR/OperandOwnership.cpp | 1 + lib/SIL/IR/SILPrinter.cpp | 4 ++++ lib/SIL/IR/ValueOwnership.cpp | 1 + lib/SIL/Parser/ParseSIL.cpp | 9 +++++++++ .../UtilityPasses/SerializeSILPass.cpp | 1 + lib/SILOptimizer/Utils/SILInliner.cpp | 1 + lib/Serialization/DeserializeSIL.cpp | 5 +++++ lib/Serialization/SerializeSIL.cpp | 6 ++++++ test/SIL/Parser/basic.sil | 9 +++++++++ test/Serialization/Inputs/def_basic.sil | 12 +++++++++++- 16 files changed, 98 insertions(+), 1 deletion(-) diff --git a/docs/SIL.rst b/docs/SIL.rst index 947f166123d33..34143b4724f97 100644 --- a/docs/SIL.rst +++ b/docs/SIL.rst @@ -3573,6 +3573,20 @@ the encoding is ``objc_selector``, the string literal produces a reference to a UTF-8-encoded Objective-C selector in the Objective-C method name segment. +base_addr_for_offset +```````````````````` +:: + + sil-instruction ::= 'base_addr_for_offset' sil-type + + %1 = base_addr_for_offset $*S + // %1 has type $*S + +Creates a base address for offset calculations. The result can be used by +address projections, like ``struct_element_addr``, which themselves return the +offset of the projected fields. +IR generation simply creates a null pointer for ``base_addr_for_offset``. + Dynamic Dispatch ~~~~~~~~~~~~~~~~ diff --git a/include/swift/SIL/SILBuilder.h b/include/swift/SIL/SILBuilder.h index 3d8dbbf19be1d..a2b220d06b8b5 100644 --- a/include/swift/SIL/SILBuilder.h +++ b/include/swift/SIL/SILBuilder.h @@ -631,6 +631,10 @@ class SILBuilder { return insert(new (getModule()) GlobalValueInst(getSILDebugLocation(Loc), g, getTypeExpansionContext())); } + BaseAddrForOffsetInst *createBaseAddrForOffset(SILLocation Loc, SILType Ty) { + return insert(new (F->getModule()) + BaseAddrForOffsetInst(getSILDebugLocation(Loc), Ty)); + } IntegerLiteralInst *createIntegerLiteral(IntegerLiteralExpr *E); IntegerLiteralInst *createIntegerLiteral(SILLocation Loc, SILType Ty, diff --git a/include/swift/SIL/SILCloner.h b/include/swift/SIL/SILCloner.h index 331ec7fb1ac61..24d4fadca8dbe 100644 --- a/include/swift/SIL/SILCloner.h +++ b/include/swift/SIL/SILCloner.h @@ -1030,6 +1030,15 @@ SILCloner::visitGlobalValueInst(GlobalValueInst *Inst) { Inst->getReferencedGlobal())); } +template +void +SILCloner::visitBaseAddrForOffsetInst(BaseAddrForOffsetInst *Inst) { + getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope())); + recordClonedInstruction( + Inst, getBuilder().createBaseAddrForOffset(getOpLocation(Inst->getLoc()), + getOpType(Inst->getType()))); +} + template void SILCloner::visitIntegerLiteralInst(IntegerLiteralInst *Inst) { diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 0e811b94a46eb..d57e8454a096e 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -3213,6 +3213,20 @@ class GlobalAddrInst : InstructionBase(DebugLoc, Ty, nullptr) {} }; +/// Creates a base address for offset calculations. +class BaseAddrForOffsetInst + : public InstructionBase { + friend SILBuilder; + + BaseAddrForOffsetInst(SILDebugLocation DebugLoc, SILType Ty) + : InstructionBase(DebugLoc, Ty) {} + +public: + ArrayRef getAllOperands() const { return {}; } + MutableArrayRef getAllOperands() { return {}; } +}; + /// Gives the value of a global variable. /// /// The referenced global variable must be a statically initialized object. diff --git a/include/swift/SIL/SILNodes.def b/include/swift/SIL/SILNodes.def index 44adf50b7aea8..352589fde1a81 100644 --- a/include/swift/SIL/SILNodes.def +++ b/include/swift/SIL/SILNodes.def @@ -458,6 +458,8 @@ ABSTRACT_VALUE_AND_INST(SingleValueInstruction, ValueBase, SILInstruction) LiteralInst, None, DoesNotRelease) SINGLE_VALUE_INST(GlobalAddrInst, global_addr, LiteralInst, None, DoesNotRelease) + SINGLE_VALUE_INST(BaseAddrForOffsetInst, base_addr_for_offset, + LiteralInst, None, DoesNotRelease) SINGLE_VALUE_INST(GlobalValueInst, global_value, LiteralInst, None, DoesNotRelease) SINGLE_VALUE_INST(IntegerLiteralInst, integer_literal, diff --git a/lib/IRGen/IRGenSIL.cpp b/lib/IRGen/IRGenSIL.cpp index 1942a358c7779..8f489458e455d 100644 --- a/lib/IRGen/IRGenSIL.cpp +++ b/lib/IRGen/IRGenSIL.cpp @@ -868,6 +868,7 @@ class IRGenSILFunction : void visitAllocGlobalInst(AllocGlobalInst *i); void visitGlobalAddrInst(GlobalAddrInst *i); void visitGlobalValueInst(GlobalValueInst *i); + void visitBaseAddrForOffsetInst(BaseAddrForOffsetInst *i); void visitIntegerLiteralInst(IntegerLiteralInst *i); void visitFloatLiteralInst(FloatLiteralInst *i); @@ -2074,6 +2075,12 @@ void IRGenSILFunction::visitGlobalValueInst(GlobalValueInst *i) { setLoweredExplosion(i, e); } +void IRGenSILFunction::visitBaseAddrForOffsetInst(BaseAddrForOffsetInst *i) { + auto storagePtrTy = IGM.getStoragePointerType(i->getType()); + llvm::Value *addr = llvm::ConstantPointerNull::get(storagePtrTy); + setLoweredAddress(i, Address(addr, Alignment())); +} + void IRGenSILFunction::visitMetatypeInst(swift::MetatypeInst *i) { auto metaTy = i->getType().castTo(); Explosion e; diff --git a/lib/SIL/IR/OperandOwnership.cpp b/lib/SIL/IR/OperandOwnership.cpp index 4befbd0596293..0246ff0d18219 100644 --- a/lib/SIL/IR/OperandOwnership.cpp +++ b/lib/SIL/IR/OperandOwnership.cpp @@ -123,6 +123,7 @@ SHOULD_NEVER_VISIT_INST(DynamicFunctionRef) SHOULD_NEVER_VISIT_INST(PreviousDynamicFunctionRef) SHOULD_NEVER_VISIT_INST(GlobalAddr) SHOULD_NEVER_VISIT_INST(GlobalValue) +SHOULD_NEVER_VISIT_INST(BaseAddrForOffset) SHOULD_NEVER_VISIT_INST(IntegerLiteral) SHOULD_NEVER_VISIT_INST(Metatype) SHOULD_NEVER_VISIT_INST(ObjCProtocol) diff --git a/lib/SIL/IR/SILPrinter.cpp b/lib/SIL/IR/SILPrinter.cpp index 47f38422cf207..763c0122779b0 100644 --- a/lib/SIL/IR/SILPrinter.cpp +++ b/lib/SIL/IR/SILPrinter.cpp @@ -1319,6 +1319,10 @@ class SILPrinter : public SILInstructionVisitor { *this << " : " << GVI->getType(); } + void visitBaseAddrForOffsetInst(BaseAddrForOffsetInst *BAI) { + *this << BAI->getType(); + } + void visitIntegerLiteralInst(IntegerLiteralInst *ILI) { const auto &lit = ILI->getValue(); *this << ILI->getType() << ", " << lit; diff --git a/lib/SIL/IR/ValueOwnership.cpp b/lib/SIL/IR/ValueOwnership.cpp index 88981eddc91f0..4acf9d278c6f5 100644 --- a/lib/SIL/IR/ValueOwnership.cpp +++ b/lib/SIL/IR/ValueOwnership.cpp @@ -103,6 +103,7 @@ CONSTANT_OWNERSHIP_INST(None, FunctionRef) CONSTANT_OWNERSHIP_INST(None, DynamicFunctionRef) CONSTANT_OWNERSHIP_INST(None, PreviousDynamicFunctionRef) CONSTANT_OWNERSHIP_INST(None, GlobalAddr) +CONSTANT_OWNERSHIP_INST(None, BaseAddrForOffset) CONSTANT_OWNERSHIP_INST(None, IndexAddr) CONSTANT_OWNERSHIP_INST(None, IndexRawPointer) CONSTANT_OWNERSHIP_INST(None, InitEnumDataAddr) diff --git a/lib/SIL/Parser/ParseSIL.cpp b/lib/SIL/Parser/ParseSIL.cpp index 90a8545a159d5..8a9df9d667734 100644 --- a/lib/SIL/Parser/ParseSIL.cpp +++ b/lib/SIL/Parser/ParseSIL.cpp @@ -4583,6 +4583,15 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B, } break; } + case SILInstructionKind::BaseAddrForOffsetInst: { + SILType Ty; + if (parseSILType(Ty)) + return true; + if (parseSILDebugLocation(InstLoc, B)) + return true; + ResultVal = B.createBaseAddrForOffset(InstLoc, Ty); + break; + } case SILInstructionKind::SelectEnumInst: case SILInstructionKind::SelectEnumAddrInst: { if (parseTypedValueRef(Val, B)) diff --git a/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp b/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp index 1965a825175c8..9d9f77d39ae28 100644 --- a/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp +++ b/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp @@ -161,6 +161,7 @@ static bool hasOpaqueArchetype(TypeExpansionContext context, case SILInstructionKind::PreviousDynamicFunctionRefInst: case SILInstructionKind::GlobalAddrInst: case SILInstructionKind::GlobalValueInst: + case SILInstructionKind::BaseAddrForOffsetInst: case SILInstructionKind::IntegerLiteralInst: case SILInstructionKind::FloatLiteralInst: case SILInstructionKind::StringLiteralInst: diff --git a/lib/SILOptimizer/Utils/SILInliner.cpp b/lib/SILOptimizer/Utils/SILInliner.cpp index ded5b22a009b0..c8fc5e583298e 100644 --- a/lib/SILOptimizer/Utils/SILInliner.cpp +++ b/lib/SILOptimizer/Utils/SILInliner.cpp @@ -685,6 +685,7 @@ InlineCost swift::instructionInlineCost(SILInstruction &I) { case SILInstructionKind::FunctionRefInst: case SILInstructionKind::AllocGlobalInst: case SILInstructionKind::GlobalAddrInst: + case SILInstructionKind::BaseAddrForOffsetInst: case SILInstructionKind::EndLifetimeInst: case SILInstructionKind::UncheckedOwnershipConversionInst: return InlineCost::Free; diff --git a/lib/Serialization/DeserializeSIL.cpp b/lib/Serialization/DeserializeSIL.cpp index 65bd1ec30b028..6cc6d6f7e8a15 100644 --- a/lib/Serialization/DeserializeSIL.cpp +++ b/lib/Serialization/DeserializeSIL.cpp @@ -1592,6 +1592,11 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn, } break; } + case SILInstructionKind::BaseAddrForOffsetInst: + assert(RecordKind == SIL_ONE_TYPE && "Layout should be OneType."); + ResultVal = Builder.createBaseAddrForOffset(Loc, + getSILType(MF->getType(TyID), (SILValueCategory)TyCategory, Fn)); + break; case SILInstructionKind::DeallocStackInst: { auto Ty = MF->getType(TyID); ResultVal = Builder.createDeallocStack( diff --git a/lib/Serialization/SerializeSIL.cpp b/lib/Serialization/SerializeSIL.cpp index 75e47c8b2decc..af20a44e2c83a 100644 --- a/lib/Serialization/SerializeSIL.cpp +++ b/lib/Serialization/SerializeSIL.cpp @@ -1091,6 +1091,12 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) { S.addUniquedStringRef(G->getName())); break; } + case SILInstructionKind::BaseAddrForOffsetInst: { + const BaseAddrForOffsetInst *BAI = cast(&SI); + writeOneTypeLayout(BAI->getKind(), /*attrs*/ 0, BAI->getType()); + break; + } + case SILInstructionKind::BranchInst: { // Format: destination basic block ID, a list of arguments. Use // SILOneTypeValuesLayout. diff --git a/test/SIL/Parser/basic.sil b/test/SIL/Parser/basic.sil index b74b3ec0a262d..f23816c7cde69 100644 --- a/test/SIL/Parser/basic.sil +++ b/test/SIL/Parser/basic.sil @@ -572,6 +572,15 @@ bb0: return %6 : $() } +// CHECK-LABEL: @base_addr : $@convention(thin) () -> () +sil private @base_addr : $() -> () { +bb0: + // CHECK: base_addr_for_offset $*Int + %0 = base_addr_for_offset $*Int + %1 = tuple () + return %1 : $() +} + protocol SomeProtocol { } class SomeClass : SomeProtocol { diff --git a/test/Serialization/Inputs/def_basic.sil b/test/Serialization/Inputs/def_basic.sil index d9aeaa7ee4b0d..9e8b718768dfb 100644 --- a/test/Serialization/Inputs/def_basic.sil +++ b/test/Serialization/Inputs/def_basic.sil @@ -498,6 +498,15 @@ bb0: return %6 : $() } +// CHECK-LABEL: @base_addr : $@convention(thin) () -> () +sil [transparent] [serialized] @base_addr : $@convention(thin) () -> () { +bb0: + // CHECK: base_addr_for_offset $*Int + %0 = base_addr_for_offset $*Int + %1 = tuple () + return %1 : $() +} + class GlobalObject { } sil_global @static_global_object : $GlobalObject = { @@ -1491,7 +1500,8 @@ bb0: %57 = function_ref @$s7literal8literalsyyF : $@convention(thin) () -> () %59 = function_ref @$s5index5gep641p1iBpBp_Bi64_tF : $@convention(thin) (Builtin.RawPointer, Builtin.Word) -> Builtin.RawPointer %60 = function_ref @global_code : $@convention(thin) () -> () - %61 = function_ref @global_object : $@convention(thin) () -> GlobalObject + %61 = function_ref @base_addr : $@convention(thin) () -> () + %62 = function_ref @global_object : $@convention(thin) () -> GlobalObject %63 = function_ref @test_class_metatype : $@convention(thin) (SomeClass, SomeSubclass) -> (@thick SomeClass.Type, @thick SomeClass.Type) %67 = function_ref @test_existential_metatype : $@convention(thin) (@in SomeProtocol) -> @thick SomeProtocol.Type %69 = function_ref @test_unreachable : $@convention(thin) () -> () From d7d829c0596ea376a0c19acbc4689b28bb984da9 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 25 Jun 2020 14:32:47 +0200 Subject: [PATCH 2/2] SILCombine: Constant-fold MemoryLayout.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, #Optional.some!enumelt, %offset_int rdar://problem/53309403 --- lib/SILOptimizer/SILCombiner/SILCombiner.h | 7 +- .../SILCombiner/SILCombinerApplyVisitors.cpp | 161 ++++++++++++-- .../Inputs/struct_with_fields.swift | 5 + test/SILOptimizer/keypath_offset.swift | 197 ++++++++++++++++++ 4 files changed, 350 insertions(+), 20 deletions(-) create mode 100644 test/SILOptimizer/Inputs/struct_with_fields.swift create mode 100644 test/SILOptimizer/keypath_offset.swift diff --git a/lib/SILOptimizer/SILCombiner/SILCombiner.h b/lib/SILOptimizer/SILCombiner/SILCombiner.h index cfd8207d089fe..6630010e5b7ae 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombiner.h +++ b/lib/SILOptimizer/SILCombiner/SILCombiner.h @@ -246,8 +246,11 @@ class SILCombiner : bool tryOptimizeKeypath(ApplyInst *AI); bool tryOptimizeInoutKeypath(BeginApplyInst *AI); bool tryOptimizeKeypathApplication(ApplyInst *AI, SILFunction *callee); - bool tryOptimizeKeypathKVCString(ApplyInst *AI, SILDeclRef callee); - + bool tryOptimizeKeypathOffsetOf(ApplyInst *AI, FuncDecl *calleeFn, + KeyPathInst *kp); + bool tryOptimizeKeypathKVCString(ApplyInst *AI, FuncDecl *calleeFn, + KeyPathInst *kp); + // Optimize concatenation of string literals. // Constant-fold concatenation of string literals known at compile-time. SILInstruction *optimizeConcatenationOfStringLiterals(ApplyInst *AI); diff --git a/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp b/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp index 67090d1886a97..1a83f5b459417 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp +++ b/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp @@ -271,6 +271,122 @@ bool SILCombiner::tryOptimizeKeypathApplication(ApplyInst *AI, return true; } +/// Replaces 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. +/// +/// Replaces: +/// %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, #Optional.some!enumelt, %offset_int +bool SILCombiner::tryOptimizeKeypathOffsetOf(ApplyInst *AI, + FuncDecl *calleeFn, + KeyPathInst *kp) { + auto *accessor = dyn_cast(calleeFn); + if (!accessor || !accessor->isGetter()) + return false; + + AbstractStorageDecl *storage = accessor->getStorage(); + DeclName name = storage->getName(); + if (!name.isSimpleName() || + (name.getBaseIdentifier().str() != "_storedInlineOffset")) + return false; + + KeyPathPattern *pattern = kp->getPattern(); + SubstitutionMap patternSubs = kp->getSubstitutions(); + CanType rootTy = pattern->getRootType().subst(patternSubs)->getCanonicalType(); + CanType parentTy = rootTy; + + // First check if _storedInlineOffset would return an offset or nil. Basically + // only stored struct and tuple elements produce an offset. Everything else + // (e.g. computed properties, class properties) result in nil. + bool hasOffset = true; + for (const KeyPathPatternComponent &component : pattern->getComponents()) { + switch (component.getKind()) { + case KeyPathPatternComponent::Kind::StoredProperty: { + if (!parentTy.getStructOrBoundGenericStruct()) + hasOffset = false; + break; + } + case KeyPathPatternComponent::Kind::TupleElement: + break; + case KeyPathPatternComponent::Kind::GettableProperty: + case KeyPathPatternComponent::Kind::SettableProperty: + // We cannot predict the offset of fields in resilient types, because it's + // unknown if a resilient field is a computed or stored property. + if (component.getExternalDecl()) + return false; + hasOffset = false; + break; + case KeyPathPatternComponent::Kind::OptionalChain: + case KeyPathPatternComponent::Kind::OptionalForce: + case KeyPathPatternComponent::Kind::OptionalWrap: + hasOffset = false; + break; + } + parentTy = component.getComponentType(); + } + + SILLocation loc = AI->getLoc(); + SILValue result; + + if (hasOffset) { + SILType rootAddrTy = SILType::getPrimitiveAddressType(rootTy); + SILValue rootAddr = Builder.createBaseAddrForOffset(loc, rootAddrTy); + + auto projector = KeyPathProjector::create(kp, rootAddr, loc, Builder); + if (!projector) + return false; + + // Create the address projections of the keypath. + SILType ptrType = SILType::getRawPointerType(Builder.getASTContext()); + SILValue offsetPtr; + projector->project(KeyPathProjector::AccessType::Get, [&](SILValue addr) { + offsetPtr = Builder.createAddressToPointer(loc, addr, ptrType); + }); + + // The result of the _storedInlineOffset call should be Optional. If + // not, something is wrong with the stdlib. Anyway, if it's not like we + // expect, bail. + SILType intType = AI->getType().getOptionalObjectType(); + if (!intType) + return false; + StructDecl *intDecl = intType.getStructOrBoundGenericStruct(); + if (!intDecl || intDecl->getStoredProperties().size() != 1) + return false; + VarDecl *member = intDecl->getStoredProperties()[0]; + CanType builtinIntTy = member->getType()->getCanonicalType(); + if (!isa(builtinIntTy)) + return false; + + // Convert the projected address back to an optional integer. + SILValue offset = Builder.createUncheckedBitCast(loc, offsetPtr, + SILType::getPrimitiveObjectType(builtinIntTy)); + SILValue offsetInt = Builder.createStruct(loc, intType, { offset }); + result = Builder.createOptionalSome(loc, offsetInt, AI->getType()); + } else { + // The keypath has no offset. + result = Builder.createOptionalNone(loc, AI->getType()); + } + AI->replaceAllUsesWith(result); + eraseInstFromFunction(*AI); + ++NumOptimizedKeypaths; + return true; +} + /// Try to optimize a keypath KVC string access on a literal key path. /// /// Replace: @@ -279,17 +395,8 @@ bool SILCombiner::tryOptimizeKeypathApplication(ApplyInst *AI, /// With: /// %string = string_literal "blah" bool SILCombiner::tryOptimizeKeypathKVCString(ApplyInst *AI, - SILDeclRef callee) { - if (AI->getNumArguments() != 1) { - return false; - } - if (!callee.hasDecl()) { - return false; - } - auto calleeFn = dyn_cast(callee.getDecl()); - if (!calleeFn) - return false; - + FuncDecl *calleeFn, + KeyPathInst *kp) { if (!calleeFn->getAttrs() .hasSemanticsAttr(semantics::KEYPATH_KVC_KEY_PATH_STRING)) return false; @@ -300,11 +407,6 @@ bool SILCombiner::tryOptimizeKeypathKVCString(ApplyInst *AI, if (!objTy || objTy.getStructOrBoundGenericStruct() != C.getStringDecl()) return false; - KeyPathInst *kp - = KeyPathProjector::getLiteralKeyPath(AI->getArgument(0)); - if (!kp || !kp->hasPattern()) - return false; - auto objcString = kp->getPattern()->getObjCString(); SILValue literalValue; @@ -357,10 +459,33 @@ bool SILCombiner::tryOptimizeKeypath(ApplyInst *AI) { return tryOptimizeKeypathApplication(AI, callee); } - if (auto method = dyn_cast(AI->getCallee())) { - return tryOptimizeKeypathKVCString(AI, method->getMember()); + // Try optimize keypath method calls. + auto *methodInst = dyn_cast(AI->getCallee()); + if (!methodInst) + return false; + + if (AI->getNumArguments() != 1) { + return false; + } + + SILDeclRef callee = methodInst->getMember(); + if (!callee.hasDecl()) { + return false; } + auto *calleeFn = dyn_cast(callee.getDecl()); + if (!calleeFn) + return false; + + KeyPathInst *kp = KeyPathProjector::getLiteralKeyPath(AI->getArgument(0)); + if (!kp || !kp->hasPattern()) + return false; + if (tryOptimizeKeypathOffsetOf(AI, calleeFn, kp)) + return true; + + if (tryOptimizeKeypathKVCString(AI, calleeFn, kp)) + return true; + return false; } diff --git a/test/SILOptimizer/Inputs/struct_with_fields.swift b/test/SILOptimizer/Inputs/struct_with_fields.swift new file mode 100644 index 0000000000000..7652d39100139 --- /dev/null +++ b/test/SILOptimizer/Inputs/struct_with_fields.swift @@ -0,0 +1,5 @@ + +public struct TestStruct { + public var x: Int + public var y: Int +} diff --git a/test/SILOptimizer/keypath_offset.swift b/test/SILOptimizer/keypath_offset.swift new file mode 100644 index 0000000000000..bb0ffca7249f1 --- /dev/null +++ b/test/SILOptimizer/keypath_offset.swift @@ -0,0 +1,197 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %S/Inputs/struct_with_fields.swift -parse-as-library -wmo -enable-library-evolution -module-name=Test -emit-module -emit-module-path=%t/Test.swiftmodule -c -o %t/test.o + +// RUN: %target-build-swift -O %s -module-name=test -Xfrontend -sil-verify-all -I%t -emit-sil | %FileCheck %s + +// RUN: %target-build-swift -Onone %s -I%t %t/test.o -o %t/Onone.out +// RUN: %target-build-swift -O %s -I%t %t/test.o -o %t/O.out +// RUN: %target-run %t/Onone.out > %t/Onone.txt +// RUN: %target-run %t/O.out > %t/O.txt +// RUN: diff %t/Onone.txt %t/O.txt + +// REQUIRES: executable_test,swift_stdlib_no_asserts,optimized_stdlib + +import Test + +final class C { + var x: Int + var z: T + let immutable: String + private(set) var secretlyMutable: String + + init(x: Int, z: T) { + self.x = x + self.z = z + self.immutable = "somestring" + self.secretlyMutable = immutable + } +} + +struct Point { + var x: Double + var y: Double +} + +struct S { + var x: Int + var y: Int? + var z: T + var p: Point + var op: Point? + var c: C +} + +struct NonOffsetableProperties { + // observers + var x: Int { didSet {} } + // reabstracted + var y: () -> () + // computed + var z: Int { return 0 } +} + +struct TupleProperties { + // unlabeled + var a: (Int, String) + // labeled + let b: (x: String, y: Int) + // reference writable + let c: (m: C, n: C) +} + +typealias Tuple = (S, C) + +func getIdentityKeyPathOfType(_: T.Type) -> KeyPath { + return \.self +} + + +@inline(never) +func printOffset(_ o: Int?) { + print(o as Any) +} + +// CHECK-LABEL: sil {{.*}} @$s4test0A13StructOffsetsyyF +// CHECK-NOT: _storedInlineOffset +// CHECK-NOT: class_method +// CHECK: } // end sil function '$s4test0A13StructOffsetsyyF' +@inline(never) +func testStructOffsets() { + let SLayout = MemoryLayout>.self + printOffset(SLayout.offset(of: \S.x)) + printOffset(SLayout.offset(of: \S.y)) + printOffset(SLayout.offset(of: \S.z)) + printOffset(SLayout.offset(of: \S.p)) + printOffset(SLayout.offset(of: \S.p.x)) + printOffset(SLayout.offset(of: \S.p.y)) + printOffset(SLayout.offset(of: \S.c)) +} + +// CHECK-LABEL: sil {{.*}} @$s4test0A20GenericStructOffsetsyyxmlF +// CHECK-NOT: _storedInlineOffset +// CHECK-NOT: class_method +// CHECK: } // end sil function '$s4test0A20GenericStructOffsetsyyxmlF' +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testGenericStructOffsets(_ t: T.Type) { + let SLayout = MemoryLayout>.self + printOffset(SLayout.offset(of: \S.x)) + printOffset(SLayout.offset(of: \S.y)) + printOffset(SLayout.offset(of: \S.z)) + printOffset(SLayout.offset(of: \S.p)) + printOffset(SLayout.offset(of: \S.p.x)) + printOffset(SLayout.offset(of: \S.p.y)) + printOffset(SLayout.offset(of: \S.c)) +} + +// CHECK-LABEL: sil {{.*}} @$s4test0A10NonOffsetsyyF +// CHECK-NOT: _storedInlineOffset +// CHECK-NOT: class_method +// CHECK: } // end sil function '$s4test0A10NonOffsetsyyF' +@inline(never) +func testNonOffsets() { + let NOPLayout = MemoryLayout.self + printOffset(NOPLayout.offset(of: \NonOffsetableProperties.x)) + printOffset(NOPLayout.offset(of: \NonOffsetableProperties.y)) + printOffset(NOPLayout.offset(of: \NonOffsetableProperties.z)) + printOffset(MemoryLayout>.offset(of: \C.x)) + let SLayout = MemoryLayout>.self + printOffset(SLayout.offset(of: \S.c.x)) + printOffset(SLayout.offset(of: \S.op!.x)) + printOffset(SLayout.offset(of: \S.op?.x)) +} + +// CHECK-LABEL: sil {{.*}} @$s4test0A11SelfOffsetsyyF +// CHECK-NOT: _storedInlineOffset +// CHECK-NOT: class_method +// CHECK: } // end sil function '$s4test0A11SelfOffsetsyyF' +@inline(never) +func testSelfOffsets() { + let SLayout = MemoryLayout>.self + printOffset(SLayout.offset(of: \.self)) + printOffset(SLayout.offset(of: getIdentityKeyPathOfType(S.self))) +} + +// CHECK-LABEL: sil {{.*}} @$s4test0A12TupleOffsetsyyF +// CHECK-NOT: _storedInlineOffset +// CHECK-NOT: class_method +// CHECK: } // end sil function '$s4test0A12TupleOffsetsyyF' +@inline(never) +func testTupleOffsets() { + let TPLayout = MemoryLayout.self + printOffset(TPLayout.offset(of: \TupleProperties.self)) + printOffset(TPLayout.offset(of: \TupleProperties.a)) + printOffset(TPLayout.offset(of: \TupleProperties.a.0)) + printOffset(TPLayout.offset(of: \TupleProperties.a.1)) + printOffset(TPLayout.offset(of: \TupleProperties.b)) + printOffset(TPLayout.offset(of: \TupleProperties.b.x)) + printOffset(TPLayout.offset(of: \TupleProperties.b.y)) + printOffset(TPLayout.offset(of: \TupleProperties.c)) + printOffset(TPLayout.offset(of: \TupleProperties.c.m)) + printOffset(TPLayout.offset(of: \TupleProperties.c.n)) + + let TLayout = MemoryLayout>.self + printOffset(TLayout.offset(of: \Tuple.self)) + printOffset(TLayout.offset(of: \Tuple.0)) + printOffset(TLayout.offset(of: \Tuple.0.x)) + printOffset(TLayout.offset(of: \Tuple.1)) +} + +// CHECK-LABEL: sil {{.*}} @$s4test0A19GenericTupleOffsetsyyxmlF +// CHECK-NOT: _storedInlineOffset +// CHECK-NOT: class_method +// CHECK: } // end sil function '$s4test0A19GenericTupleOffsetsyyxmlF' +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testGenericTupleOffsets(_ t: T.Type) { + let TLayout = MemoryLayout>.self + printOffset(TLayout.offset(of: \Tuple.self)) + printOffset(TLayout.offset(of: \Tuple.0)) + printOffset(TLayout.offset(of: \Tuple.0.x)) + printOffset(TLayout.offset(of: \Tuple.1)) +} + +// CHECK-LABEL: sil {{.*}} @$s4test0A16ResilientOffsetsyyF +// CHECK: class_method {{.*}}_storedInlineOffset +// CHECK: } // end sil function '$s4test0A16ResilientOffsetsyyF' +@inline(never) +func testResilientOffsets() { + let TLayout = MemoryLayout.self + printOffset(TLayout.offset(of: \TestStruct.x)) +} + +print("### testStructOffsets") +testStructOffsets() +print("### testGenericStructOffsets") +testGenericStructOffsets(Int.self) +print("### testNonOffsets") +testNonOffsets() +print("### testSelfOffsets") +testSelfOffsets() +print("### testTupleOffsets") +testTupleOffsets() +print("### testGenericTupleOffsets") +testGenericTupleOffsets(Int.self) +print("### testResilientOffsets") +testResilientOffsets() +