mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
When replacing an opened existential type with the concrete type, we didn't consider that the existential archetype can also be a "dependent" type of the root archetype. For now, just bail in this case. In future we can support dependent archetypes as well. Fixes a compiler crash. rdar://158594365
293 lines
12 KiB
Swift
293 lines
12 KiB
Swift
//===--- SimplifyApply.swift ----------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2023 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 SIL
|
|
import AST
|
|
|
|
extension ApplyInst : OnoneSimplifiable, SILCombineSimplifiable {
|
|
func simplify(_ context: SimplifyContext) {
|
|
if tryTransformThickToThinCallee(of: self, context) {
|
|
return
|
|
}
|
|
if context.tryOptimizeKeypath(apply: self) {
|
|
context.erase(instruction: self)
|
|
return
|
|
}
|
|
if context.tryDevirtualize(apply: self, isMandatory: false) != nil {
|
|
return
|
|
}
|
|
if tryRemoveArrayCast(apply: self, context) {
|
|
return
|
|
}
|
|
if tryOptimizeEnumComparison(apply: self, context) {
|
|
return
|
|
}
|
|
if !context.preserveDebugInfo {
|
|
_ = tryReplaceExistentialArchetype(of: self, context)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension TryApplyInst : OnoneSimplifiable, SILCombineSimplifiable {
|
|
func simplify(_ context: SimplifyContext) {
|
|
if context.tryDevirtualize(apply: self, isMandatory: false) != nil {
|
|
return
|
|
}
|
|
if !context.preserveDebugInfo {
|
|
_ = tryReplaceExistentialArchetype(of: self, context)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension BeginApplyInst : OnoneSimplifiable {
|
|
func simplify(_ context: SimplifyContext) {
|
|
_ = context.tryDevirtualize(apply: self, isMandatory: false)
|
|
}
|
|
}
|
|
|
|
/// Optimizes a thick function call if the callee is a `thin_to_thick_function` instruction:
|
|
///
|
|
/// %2 = thin_to_thick_function %1
|
|
/// %3 = apply %2(...) : @callee_guaranteed
|
|
/// ->
|
|
/// %2 = thin_to_thick_function %1
|
|
/// %3 = apply %1(...): @convention(thin)
|
|
///
|
|
private func tryTransformThickToThinCallee(of apply: ApplyInst, _ context: SimplifyContext) -> Bool {
|
|
if let tttf = apply.callee as? ThinToThickFunctionInst,
|
|
!apply.callee.type.isCalleeConsumedFunction
|
|
{
|
|
let builder = Builder(before: apply, context)
|
|
let newApply = builder.createApply(function: tttf.operand.value,
|
|
apply.substitutionMap,
|
|
arguments: Array(apply.arguments),
|
|
isNonThrowing: apply.isNonThrowing,
|
|
isNonAsync: apply.isNonAsync,
|
|
specializationInfo: apply.specializationInfo)
|
|
apply.replace(with: newApply, context)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/// Removes casts between arrays of the same type.
|
|
///
|
|
/// %1 = function_ref @_arrayConditionalCast : (@guaranteed Array<Int>) -> @owned Optional<Array<Int>>
|
|
/// %2 = apply %1(%0) : (@guaranteed Array<Int>) -> @owned Optional<Array<Int>>
|
|
/// ->
|
|
/// %1 = copy_value %0
|
|
/// %2 = enum $Optional<Array<Int>>, #Optional.some!enumelt, %1
|
|
///
|
|
private func tryRemoveArrayCast(apply: ApplyInst, _ context: SimplifyContext) -> Bool {
|
|
guard let callee = apply.referencedFunction,
|
|
callee.hasSemanticsAttribute("array.conditional_cast"),
|
|
apply.parentFunction.hasOwnership,
|
|
|
|
// Check if the cast function has the expected calling convention
|
|
apply.arguments.count == 1,
|
|
apply.convention(of: apply.argumentOperands[0]) == .directGuaranteed,
|
|
apply.functionConvention.results[0].convention == .owned,
|
|
apply.type.isOptional,
|
|
|
|
// Check if the source and target type of the cast is identical.
|
|
// Note that we are checking the _formal_ element types and not the lowered types, because
|
|
// the element types are replacement type in the Array's substitution map and this is a formal type.
|
|
apply.arguments[0].type == apply.type.optionalPayloadType(in: apply.parentFunction)
|
|
else {
|
|
return false
|
|
}
|
|
|
|
let builder = Builder(after: apply, context)
|
|
let copiedArray = builder.createCopyValue(operand: apply.arguments[0])
|
|
let optional = builder.createEnum(caseIndex: 1, payload: copiedArray, enumType: apply.type)
|
|
apply.replace(with: optional, context)
|
|
return true
|
|
}
|
|
|
|
/// Optimize (the very inefficient) RawRepresentable comparison to a simple compare of enum tags.
|
|
/// For example,
|
|
/// ```
|
|
/// enum E: String {
|
|
/// case a, b, c
|
|
/// }
|
|
/// ```
|
|
/// is compared by getting the raw values of both operands and doing a string compare.
|
|
/// This peephole optimizations replaces the call to such a comparison function with a direct compare of
|
|
/// the enum tags, which boils down to a single integer comparison instruction.
|
|
///
|
|
private func tryOptimizeEnumComparison(apply: ApplyInst, _ context: SimplifyContext) -> Bool {
|
|
guard let callee = apply.referencedFunction,
|
|
apply.arguments.count == 2,
|
|
callee.hasSemanticsAttribute("rawrepresentable.is_equal"),
|
|
apply.type.isStruct
|
|
else {
|
|
return false
|
|
}
|
|
let lhs = apply.arguments[0]
|
|
let rhs = apply.arguments[1]
|
|
guard let enumDecl = lhs.type.nominal as? EnumDecl,
|
|
enumDecl.hasRawType,
|
|
!enumDecl.isResilient(in: apply.parentFunction),
|
|
!enumDecl.hasClangNode,
|
|
lhs.type.isAddress,
|
|
lhs.type == rhs.type
|
|
else {
|
|
return false
|
|
}
|
|
let builder = Builder(before: apply, context)
|
|
let tagType = context.getBuiltinIntegerType(bitWidth: 32)
|
|
let lhsTag = builder.createBuiltin(name: "getEnumTag", type: tagType,
|
|
substitutions: apply.substitutionMap, arguments: [lhs])
|
|
let rhsTag = builder.createBuiltin(name: "getEnumTag", type: tagType,
|
|
substitutions: apply.substitutionMap, arguments: [rhs])
|
|
let builtinBoolType = context.getBuiltinIntegerType(bitWidth: 1)
|
|
let cmp = builder.createBuiltin(name: "cmp_eq_Int32", type: builtinBoolType, arguments: [lhsTag, rhsTag])
|
|
let booleanResult = builder.createStruct(type: apply.type, elements: [cmp])
|
|
apply.replace(with: booleanResult, context)
|
|
return true
|
|
}
|
|
|
|
/// If the apply uses an existential archetype (`@opened("...")`) and the concrete type is known,
|
|
/// replace the existential archetype with the concrete type
|
|
/// 1. in the apply's substitution map
|
|
/// 2. in the arguments, e.g. by inserting address casts
|
|
/// For example:
|
|
/// ```
|
|
/// %5 = apply %1<@opend("...")>(%2) : <τ_0_0> (τ_0_0) -> ()
|
|
/// ```
|
|
/// ->
|
|
/// ```
|
|
/// %4 = unchecked_addr_cast %2 to $*ConcreteType
|
|
/// %5 = apply %1<ConcreteType>(%4) : <τ_0_0> (τ_0_0) -> ()
|
|
/// ```
|
|
private func tryReplaceExistentialArchetype(of apply: ApplyInst, _ context: SimplifyContext) -> Bool {
|
|
if let concreteType = apply.concreteTypeOfDependentExistentialArchetype,
|
|
apply.canReplaceExistentialArchetype()
|
|
{
|
|
let builder = Builder(after: apply, context)
|
|
|
|
let newApply = builder.createApply(
|
|
function: apply.callee,
|
|
apply.replaceOpenedArchetypeInSubstitutions(withConcreteType: concreteType, context),
|
|
arguments: apply.replaceExistentialArchetypeInArguments(withConcreteType: concreteType, context),
|
|
isNonThrowing: apply.isNonThrowing, isNonAsync: apply.isNonAsync,
|
|
specializationInfo: apply.specializationInfo)
|
|
apply.replace(with: newApply, context)
|
|
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// The same as the previous function, just for try_apply instructions.
|
|
private func tryReplaceExistentialArchetype(of tryApply: TryApplyInst, _ context: SimplifyContext) -> Bool {
|
|
if let concreteType = tryApply.concreteTypeOfDependentExistentialArchetype,
|
|
tryApply.canReplaceExistentialArchetype()
|
|
{
|
|
let builder = Builder(before: tryApply, context)
|
|
|
|
builder.createTryApply(
|
|
function: tryApply.callee,
|
|
tryApply.replaceOpenedArchetypeInSubstitutions(withConcreteType: concreteType, context),
|
|
arguments: tryApply.replaceExistentialArchetypeInArguments(withConcreteType: concreteType, context),
|
|
normalBlock: tryApply.normalBlock, errorBlock: tryApply.errorBlock,
|
|
isNonAsync: tryApply.isNonAsync,
|
|
specializationInfo: tryApply.specializationInfo)
|
|
context.erase(instruction: tryApply)
|
|
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
private extension FullApplySite {
|
|
// Precondition: the apply uses only a single existential archetype.
|
|
// This is checked in `concreteTypeOfDependentExistentialArchetype`
|
|
func canReplaceExistentialArchetype() -> Bool {
|
|
// Make sure that existential archetype _is_ a replacement type and not e.g. _contained_ in a
|
|
// replacement type, like
|
|
// apply %1<Array<@opened("...")>()
|
|
// TODO: support non-root existential archetypes
|
|
guard substitutionMap.replacementTypes.contains(where: { $0.isRootExistentialArchetype }),
|
|
substitutionMap.replacementTypes.allSatisfy({ $0.isRootExistentialArchetype || !$0.hasLocalArchetype })
|
|
else {
|
|
return false
|
|
}
|
|
|
|
// Don't allow existential archetypes in direct results and error results.
|
|
// Note that an opened existential value is address only, so it cannot be a direct result anyway
|
|
// (but it can be once we have opaque values).
|
|
// Also don't support things like direct `Array<@opened("...")>` return values.
|
|
if let singleDirectResult, singleDirectResult.type.hasLocalArchetype {
|
|
return false
|
|
}
|
|
if let singleDirectErrorResult, singleDirectErrorResult.type.hasLocalArchetype {
|
|
return false
|
|
}
|
|
|
|
return arguments.allSatisfy { value in
|
|
let type = value.type
|
|
// Allow three cases:
|
|
// case 1. the argument _is_ the existential archetype
|
|
return type.isRootExistentialArchetype ||
|
|
// case 2. the argument _is_ a metatype of the existential archetype
|
|
(type.isMetatype && type.canonicalType.instanceTypeOfMetatype.isRootExistentialArchetype) ||
|
|
// case 3. the argument has nothing to do with the existential archetype (or any other local archetype)
|
|
!type.hasLocalArchetype
|
|
}
|
|
}
|
|
|
|
func replaceExistentialArchetypeInArguments(
|
|
withConcreteType concreteType: CanonicalType,
|
|
_ context: SimplifyContext
|
|
) -> [Value] {
|
|
let newArgs = arguments.map { (arg) -> Value in
|
|
if arg.type.isExistentialArchetype {
|
|
// case 1. the argument _is_ the existential archetype:
|
|
// just insert an address cast to satisfy type equivalence.
|
|
let builder = Builder(before: self, context)
|
|
let concreteSILType = concreteType.loweredType(in: self.parentFunction)
|
|
return builder.createUncheckedAddrCast(from: arg, to: concreteSILType.addressType)
|
|
}
|
|
if arg.type.isMetatype, arg.type.canonicalType.instanceTypeOfMetatype.isExistentialArchetype {
|
|
// case 2. the argument _is_ a metatype of the existential archetype:
|
|
// re-create the metatype with the concrete type.
|
|
let builder = Builder(before: self, context)
|
|
return builder.createMetatype(ofInstanceType: concreteType, representation: arg.type.representationOfMetatype)
|
|
}
|
|
// case 3. the argument has nothing to do with the existential archetype (or any other local archetype)
|
|
return arg
|
|
}
|
|
return Array(newArgs)
|
|
}
|
|
|
|
func replaceOpenedArchetypeInSubstitutions(
|
|
withConcreteType concreteType: CanonicalType,
|
|
_ context: SimplifyContext
|
|
) -> SubstitutionMap {
|
|
let openedArcheType = substitutionMap.replacementTypes.first(where: { $0.isExistentialArchetype })!
|
|
|
|
let newReplacementTypes = substitutionMap.replacementTypes.map {
|
|
return $0 == openedArcheType ? concreteType.rawType : $0
|
|
}
|
|
let genSig = callee.type.invocationGenericSignatureOfFunction
|
|
return SubstitutionMap(genericSignature: genSig, replacementTypes: newReplacementTypes)
|
|
}
|
|
}
|
|
|
|
private extension Type {
|
|
func optionalPayloadType(in function: Function) -> Type {
|
|
let subs = contextSubstitutionMap
|
|
return subs.replacementTypes[0].loweredType(in: function)
|
|
}
|
|
}
|