Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SwiftCompilerSources/Sources/AST/Declarations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ final public class ClassDecl: NominalTypeDecl {

final public class ProtocolDecl: NominalTypeDecl {
public var requiresClass: Bool { bridged.ProtocolDecl_requiresClass() }
public var isMarkerProtocol: Bool { bridged.ProtocolDecl_isMarkerProtocol() }
}

final public class BuiltinTupleDecl: NominalTypeDecl {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ private struct FunctionChecker {
for conf in ie.conformances {
try checkConformance(conf, location: ie.location)
}
} else if instruction is OpenExistentialAddrInst {
// okay in embedded with exitentials
} else {
// not supported even in embedded with exitentials
throw Diagnostic(.embedded_swift_existential_type, instruction.operands[0].value.type, at: instruction.location)
}

case let aeb as AllocExistentialBoxInst:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ private func optimize(function: Function, _ context: FunctionPassContext, _ modu
}
}

case let initExAddr as InitExistentialAddrInst:
if context.options.enableEmbeddedSwift {
for c in initExAddr.conformances where c.isConcrete && !c.protocol.isMarkerProtocol {
specializeWitnessTable(for: c, moduleContext)
worklist.addWitnessMethods(of: c, moduleContext)
}
}

case let bi as BuiltinInst:
switch bi.id {
case .BuildOrdinaryTaskExecutorRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func specializeWitnessTable(for conformance: Conformance, _ context: ModulePassC
guard !methodSubs.conformances.contains(where: {!$0.isValid}),
context.loadFunction(function: origMethod, loadCalleesRecursively: true),
let specializedMethod = context.specialize(function: origMethod, for: methodSubs,
convertIndirectToDirect: true, isMandatory: true)
convertIndirectToDirect: false, isMandatory: true)
else {
return origEntry
}
Expand Down Expand Up @@ -215,7 +215,7 @@ private func specializeDefaultMethods(for conformance: Conformance,
guard !methodSubs.conformances.contains(where: {!$0.isValid}),
context.loadFunction(function: origMethod, loadCalleesRecursively: true),
let specializedMethod = context.specialize(function: origMethod, for: methodSubs,
convertIndirectToDirect: true, isMandatory: true)
convertIndirectToDirect: false, isMandatory: true)
else {
return origEntry
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1010,8 +1010,7 @@ func isCastSupportedInEmbeddedSwift(from sourceType: Type,
return false
}

// Tuple?
if !destType.isStruct && !destType.isClass && !destType.isEnum {
if !destType.isStruct && !destType.isClass && !destType.isEnum && !destType.isTuple {
return false
}

Expand Down
4 changes: 4 additions & 0 deletions SwiftCompilerSources/Sources/SIL/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,10 @@ class InitExistentialAddrInst : SingleValueInstruction, UnaryInstruction {
public var conformances: ConformanceArray {
ConformanceArray(bridged: bridged.InitExistentialAddrInst_getConformances())
}

public var formalConcreteType: CanonicalType {
CanonicalType(bridged: bridged.InitExistentialAddrInst_getFormalConcreteType())
}
}

final public
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/ASTBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ struct BridgedDeclObj {
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedASTType Class_getSuperclass() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedDeclObj Class_getDestructor() const;
BRIDGED_INLINE bool ProtocolDecl_requiresClass() const;
BRIDGED_INLINE bool ProtocolDecl_isMarkerProtocol() const;
BRIDGED_INLINE bool AbstractFunction_isOverridden() const;
BRIDGED_INLINE bool Destructor_isIsolated() const;
BRIDGED_INLINE bool EnumElementDecl_hasAssociatedValues() const;
Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/ASTBridgingImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ bool BridgedDeclObj::ProtocolDecl_requiresClass() const {
return getAs<swift::ProtocolDecl>()->requiresClass();
}

bool BridgedDeclObj::ProtocolDecl_isMarkerProtocol() const {
return getAs<swift::ProtocolDecl>()->isMarkerProtocol();
}

bool BridgedDeclObj::AbstractFunction_isOverridden() const {
return getAs<swift::AbstractFunctionDecl>()->isOverridden();
}
Expand Down
1 change: 1 addition & 0 deletions include/swift/SIL/SILBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@ struct BridgedInstruction {
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedConformanceArray InitExistentialRefInst_getConformances() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedCanType InitExistentialRefInst_getFormalConcreteType() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedConformanceArray InitExistentialAddrInst_getConformances() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedCanType InitExistentialAddrInst_getFormalConcreteType() const;
BRIDGED_INLINE bool OpenExistentialAddr_isImmutable() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedGlobalVar GlobalAccessInst_getGlobal() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedGlobalVar AllocGlobalInst_getGlobal() const;
Expand Down
5 changes: 5 additions & 0 deletions include/swift/SIL/SILBridgingImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1252,6 +1252,11 @@ BridgedCanType BridgedInstruction::InitExistentialRefInst_getFormalConcreteType(
BridgedConformanceArray BridgedInstruction::InitExistentialAddrInst_getConformances() const {
return {getAs<swift::InitExistentialAddrInst>()->getConformances()};
}

BridgedCanType BridgedInstruction::InitExistentialAddrInst_getFormalConcreteType() const {
return getAs<swift::InitExistentialAddrInst>()->getFormalConcreteType();
}

bool BridgedInstruction::OpenExistentialAddr_isImmutable() const {
switch (getAs<swift::OpenExistentialAddrInst>()->getAccessKind()) {
case swift::OpenedExistentialAccess::Immutable: return true;
Expand Down
2 changes: 2 additions & 0 deletions include/swift/SILOptimizer/Utils/Generics.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,9 @@ class ReabstractionInfo {

ReabstractionInfo(CanSILFunctionType substitutedType,
SILDeclRef methodDecl,
bool convertIndirectToDirect,
SILModule &M) :
ConvertIndirectToDirect(convertIndirectToDirect),
SubstitutedType(substitutedType),
methodDecl(methodDecl),
M(&M), isWholeModule(M.isWholeModule()) {}
Expand Down
4 changes: 4 additions & 0 deletions lib/IRGen/GenProto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1751,6 +1751,10 @@ class AccessorConformanceInfo : public ConformanceInfo {
#endif

auto typeWitness = Conformance.getTypeWitness(assocType);
if (langOpts.hasFeature(Feature::EmbeddedExistentials) &&
SILWT->isSpecialized()) {
typeWitness = entry.getAssociatedTypeWitness().Witness;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't make sense, why is Conformance.getTypeWitness() returning the wrong thing? Is Conformance is not a SpecializedProtocolConformance? If not, it should be fixed to match up and the conditional removed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use either Conformance.getTypeWitness() or entry.getAssociatedTypeWitness().Witness here, but they should be equal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conformance.getTypeWitness returns the wrong thing because Conformance is the root conformance not the specialized_conformance.

Example:

protocol PWithAssoc {
  associatedtype Assoc
  func a() -> Assoc
}

struct Conformer<T> {
  var t: T
  init(_ t: T) {
    self.t = t
  }
}

struct Container<T> : PWithAssoc {
  var x: Conformer<T>

  init(_ t: T) {
    self.x = Conformer(t)
  }

  func a() -> Conformer<T> {
    return x
  }
}

func test(_ p: PWithAssoc) {
  let _ = p.a()
}

test(Container(1))

In this example, we have a specialized SILWitness table:

sil_witness_table shared [specialized] Container<Int>: specialize <Int> (<T> Container<T>: PWithAssoc module t3) {
  associated_type Assoc: Conformer<Int>
  method #PWithAssoc.a: <Self where Self : PWithAssoc> (Self) -> () -> Self.Assoc : @$e2t39ContainerVyxGAA10PWithAssocA2aEP1a0D0QzyFTWSi_Tg5    // specialized protocol witness for PWithAssoc.a() in conformance Container<A>
}

In the witnesstablebuilder code Conformance is initialized to the root conformance SILWT->getConformance()->getRootConformance():

  class WitnessTableBuilderBase {                                               
  protected:                                                                    
    IRGenModule &IGM;                                                           
    SILWitnessTable *SILWT;                                                     
    const RootProtocolConformance &Conformance;                                 
    const ProtocolConformance &ConformanceInContext;                            
    CanType ConcreteType;                                                       
                                                                                
    std::optional<FulfillmentMap> Fulfillments;                                 
                                                                                
    WitnessTableBuilderBase(IRGenModule &IGM, SILWitnessTable *SILWT)           
        : IGM(IGM), SILWT(SILWT),                                               
          Conformance(*SILWT->getConformance()->getRootConformance()),          
          ConformanceInContext(*mapConformanceIntoContext(SILWT->getConformance()->getRootConformance())),
          ConcreteType(Conformance.getDeclContext()                             
                         ->mapTypeIntoEnvironment(Conformance.getType())        
                         ->getCanonicalType()) {}    

And that ends up being the normal conformance:

(normal_conformance type="Container<T>" protocol="PWithAssoc" explicit nonisolated
  (assoc_type req="Assoc" type="Conformer<T>")
  (value req="a()" witness="t3.(file).Container.a()@t3.swift:20:8")
  (assoc_conformance type="Self" proto="Copyable"
    (builtin_conformance type="Container<T>" protocol="Copyable" synthesized nonisolated))
  (assoc_conformance type="Self" proto="Escapable"
    (builtin_conformance type="Container<T>" protocol="Escapable" synthesized nonisolated))
  (assoc_conformance type="Self.Assoc" proto="Copyable"
    (builtin_conformance type="Conformer<T>" protocol="Copyable" synthesized nonisolated))
  (assoc_conformance type="Self.Assoc" proto="Escapable"
    (builtin_conformance type="Conformer<T>" protocol="Escapable" synthesized nonisolated)))

And then we end up with: (bound_generic_struct_type decl="t3.(file)[email protected]:6:8" (generic_type_param_type depth=0 index=0 name="T" param_kind=type)) Instead of Conformer<Int> for

auto typeWitness = Conformance.getTypeWitness(assocType); 

If we used SILWT->getConformance().getTypeWitness(assocType) we would get the the right type (bound_generic_struct_type decl="t3.(file)[email protected]:6:8" (struct_type decl="Swift.(file).Int"))

Copy link
Contributor

@slavapestov slavapestov Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thanks for the investigation. I think the right fix is to not unwrap to get the root conformance there at all. If the witness table is not specialized, it should already be the root conformance. But that might be tricky to untangle so you don't have to do it if you don't want to.

Two more questions. Does this need to be conditional on langOpts.hasFeature(Feature::EmbeddedExistentials) then? Also there's another place where getAssociatedConformance() gets called on the unspecialized root conformance. Is that also wrong?

I'll clean this up some day. ConformanceInContext could probably be removed as well.

}

if (IGM.Context.LangOpts.hasFeature(Feature::EmbeddedExistentials)) {
// In Embedded Swift associated type witness point to the metadata.
Expand Down
6 changes: 4 additions & 2 deletions lib/SILOptimizer/Utils/Generics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2413,7 +2413,8 @@ bool swift::specializeClassMethodInst(ClassMethodInst *cm) {
SILType substitutedType =
funcTy.substGenericArgs(m, subs, TypeExpansionContext::minimal());

ReabstractionInfo reInfo(substitutedType.getAs<SILFunctionType>(), cm->getMember(), m);
ReabstractionInfo reInfo(substitutedType.getAs<SILFunctionType>(), cm->getMember(),
/*convertIndirectToDirect=*/ true, m);
reInfo.createSubstitutedAndSpecializedTypes();
CanSILFunctionType finalFuncTy = reInfo.getSpecializedType();
SILType finalSILTy = SILType::getPrimitiveObjectType(finalFuncTy);
Expand Down Expand Up @@ -2465,7 +2466,8 @@ bool swift::specializeWitnessMethodInst(WitnessMethodInst *wm) {
SILType substitutedType =
funcTy.substGenericArgs(m, subs, TypeExpansionContext::minimal());

ReabstractionInfo reInfo(substitutedType.getAs<SILFunctionType>(), wm->getMember(), m);
ReabstractionInfo reInfo(substitutedType.getAs<SILFunctionType>(), wm->getMember(),
/*convertIndirectToDirect=*/ false, m);
reInfo.createSubstitutedAndSpecializedTypes();
CanSILFunctionType finalFuncTy = reInfo.getSpecializedType();
SILType finalSILTy = SILType::getPrimitiveObjectType(finalFuncTy);
Expand Down
185 changes: 184 additions & 1 deletion test/embedded/existential.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,133 @@ func test7(_ p: any Any) {
c.a()
}

class BaseClass {
func foo() { print("BaseClass.foo") }
deinit {
print("BaseClass.deinit")
}
}

class SubClass : BaseClass {
override func foo() { print("SubClass.foo") }
}

func test8(_ p: any Any) {
print("test any as? SubClass")
if let c = p as? SubClass {
print("success")
c.foo()
} else {
print("cast failed")
}
}

func test9(_ p: any Any) {
print("test any as? BaseClass")
if let c = p as? BaseClass {
print("success")
c.foo()
} else {
print("cast failed")
}
}

func test10(_ p: any Any) {
print("test any as! BaseClass")
let c = p as! BaseClass
c.foo()
}

func test11(_ p: any Any) {
print("test any as! SubClass")
let c = p as! SubClass
c.foo()
}

func test12(_ p: any Any) {
print("test any as! (Int, Int, Int, Int)")
if let c = p as? (Int, Int, Int, Int) {
print("success")
print("tuple: \(c.0)")
} else {
print("cast failed")
}
}

protocol Q {
func printit()
}

protocol P4<T> {
associatedtype T: Q

var t: T { get }
}

struct QConformer : Q {
var x = (0, 1, 2,3)

func printit() {
print("QConformer \(x.3)")
}
}

struct P4Conformer : P4 {
var q = QConformer()

var t : QConformer {
get {
return q
}
}
}

func test13(_ p: any P4) {
print("test13")
p.t.printit()
}

struct GenericConformer<T> : ValuePrinter {
var t: T?
var l = (0, 1, 2, 3)

init(_ t: T) { self.t = t }

func printValue() {
print("GenericConformer \(l.0) \(l.1) \(l.2) \(l.3)")
}

mutating func mutate() {
l = (4, 5, 6, 7)
}
}

struct GenericConformerWithAssoc<T> : WithAssoc {
var g : GenericConformer<T>

init( _ g: T) {
self.g = GenericConformer(g)
}

func a() -> GenericConformer<T> {
return g
}
}

func test14(_ p: any ValuePrinter) {
print("test any ValuePrinter")
p.printValue()
var p2 = p
p2.mutate()
p2.printValue()
}

func test15(_ p: any WithAssoc) {
print("test any WithAssoc")
let l = p.a()
l.printValue()
}

@main
struct Main {
static func main() {
Expand Down Expand Up @@ -262,7 +389,7 @@ struct Main {
// OUTPUT: deinit called
// OUTPUT: cast failed
// OUTPUT-NOT: deinit called
test5(GenericStructWithClass<Int>())
test5(GenericStructWithClass<Int>())
// OUTPUT: test any as? MyStruct
// OUTPUT: cast failed
// OUTPUT: deinit called
Expand All @@ -289,5 +416,61 @@ struct Main {
// OUTPUT: a LargeMyStruct 5
// OUTPUT: deinit called
// OUTPUT-NOT: deinit called
test8(SubClass())
// OUTPUT: success
// OUTPUT: SubClass.foo
// OUTPUT: BaseClass.deinit
// OUTPUT-NOT: deinit
test8(BaseClass())
// OUTPUT: test any as? SubClass
// OUTPUT: cast failed
// OUTPUT: BaseClass.deinit
// OUTPUT-NOT: deinit
test9(SubClass())
// OUTPUT: test any as? BaseClass
// OUTPUT: success
// OUTPUT: SubClass.foo
// OUTPUT: BaseClass.deinit
// OUTPUT-NOT: deinit
test9(BaseClass())
// OUTPUT: test any as? BaseClass
// OUTPUT: success
// OUTPUT: BaseClass.foo
// OUTPUT: BaseClass.deinit
// OUTPUT-NOT: deinit
test9(C())
// OUTPUT: test any as? BaseClass
// OUTPUT: cast failed
// OUTPUT-NOT: deinit
test10(BaseClass())
// OUTPUT: test any as! BaseClass
// OUTPUT: BaseClass.foo
// OUTPUT: BaseClass.deinit
// OUTPUT-NOT: deinit
test10(SubClass())
// OUTPUT: test any as! BaseClass
// OUTPUT: SubClass.foo
// OUTPUT: BaseClass.deinit
// OUTPUT-NOT: deinit
test11(SubClass())
// OUTPUT: test any as! SubClass
// OUTPUT: SubClass.foo
// OUTPUT: BaseClass.deinit
// OUTPUT-NOT: deinit
test12((0, 1, 2, 3))
// OUTPUT: test any as! (Int, Int, Int, Int)
// OUTPUT: success
// OUTPUT: tuple: 0
test13(P4Conformer())
// OUTPUT: test13
// OUTPUT: QConformer 3
// OUTPUT-NOT: deinit
test14(GenericConformer(1))
// OUTPUT: test any ValuePrinter
// OUTPUT: GenericConformer 0 1 2 3
// OUTPUT: GenericConformer 4 5 6 7
test15(GenericConformerWithAssoc(1))
// OUTPUT: test any WithAssoc
// OUTPUT: GenericConformer 0 1 2 3
}
}
Loading