Skip to content

Class stubs fix and tests [5.1] #24189

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
3 changes: 1 addition & 2 deletions lib/IRGen/GenCast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ FailableCastResult irgen::emitClassIdenticalCast(IRGenFunction &IGF,
llvm::Value *targetMetadata;
if ((targetMetadata =
tryEmitConstantHeapMetadataRef(IGF.IGM, toType.getASTType(),
/*allowUninitialized*/ false,
/*allowStub*/ false))) {
/*allowUninitialized*/ false))) {
// ok
} else {
targetMetadata
Expand Down
40 changes: 27 additions & 13 deletions lib/IRGen/GenClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1194,7 +1194,32 @@ namespace {
if (categoryCount > 0)
os << categoryCount;
}


llvm::Constant *getClassMetadataRef() {
auto *theClass = getClass();

if (theClass->hasClangNode())
return IGM.getAddrOfObjCClass(theClass, NotForDefinition);

// Note that getClassMetadataStrategy() will return
// ClassMetadataStrategy::Resilient if the class is
// from another resilience domain, even if inside that
// resilience domain the class has fixed metadata
// layout.
//
// Since a class only has a class stub if its class
// hierarchy crosses resilience domains, we use a
// slightly different query here.
if (theClass->checkAncestry(AncestryFlags::ResilientOther)) {
assert(IGM.Context.LangOpts.EnableObjCResilientClassStubs);
return IGM.getAddrOfObjCResilientClassStub(theClass, NotForDefinition,
TypeMetadataAddress::AddressPoint);
}

auto type = getSelfType(theClass).getASTType();
return tryEmitConstantHeapMetadataRef(IGM, type, /*allowUninit*/ true);
}

public:
llvm::Constant *emitCategory() {
assert(TheExtension && "can't emit category data for a class");
Expand All @@ -1205,18 +1230,7 @@ namespace {
// char const *name;
fields.add(IGM.getAddrOfGlobalString(CategoryName));
// const class_t *theClass;
if (getClass()->hasClangNode())
fields.add(IGM.getAddrOfObjCClass(getClass(), NotForDefinition));
else {
auto type = getSelfType(getClass()).getASTType();
llvm::Constant *metadata =
tryEmitConstantHeapMetadataRef(IGM, type,
/*allowUninit*/ true,
/*allowStub*/ true);
assert(metadata &&
"extended objc class doesn't have constant metadata?");
fields.add(metadata);
}
fields.add(getClassMetadataRef());
// const method_list_t *instanceMethods;
fields.add(buildInstanceMethodList());
// const method_list_t *classMethods;
Expand Down
16 changes: 15 additions & 1 deletion lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,12 @@ void IRGenModule::emitGlobalLists() {
"regular,no_dead_strip"),
llvm::GlobalValue::InternalLinkage, Int8PtrTy, false);

// And categories on class stubs.
emitGlobalList(*this, ObjCCategoriesOnStubs, "objc_categories_stubs",
GetObjCSectionName("__objc_catlist2",
"regular,no_dead_strip"),
llvm::GlobalValue::InternalLinkage, Int8PtrTy, false);

// Emit nonlazily realized class references in a second magic section to make
// sure they are realized by the Objective-C runtime before any instances
// are allocated.
Expand Down Expand Up @@ -3740,7 +3746,15 @@ void IRGenModule::emitExtension(ExtensionDecl *ext) {
"foreign types cannot have categories emitted");
llvm::Constant *category = emitCategoryData(*this, ext);
category = llvm::ConstantExpr::getBitCast(category, Int8PtrTy);
ObjCCategories.push_back(category);

auto *theClass = ext->getSelfClassDecl();

// Categories on class stubs are added to a separate list.
if (theClass->checkAncestry(AncestryFlags::ResilientOther))
ObjCCategoriesOnStubs.push_back(category);
else
ObjCCategories.push_back(category);

ObjCCategoryDecls.push_back(ext);
}
}
Expand Down
24 changes: 16 additions & 8 deletions lib/IRGen/GenMeta.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1420,6 +1420,7 @@ namespace {
super::layout();
addVTable();
addOverrideTable();
addObjCResilientClassStubInfo();
}

void addIncompleteMetadataOrRelocationFunction() {
Expand Down Expand Up @@ -1653,6 +1654,20 @@ namespace {
// uint32_t FieldOffsetVectorOffset;
B.addInt32(getFieldVectorOffset() / IGM.getPointerSize());
}

void addObjCResilientClassStubInfo() {
if (IGM.getClassMetadataStrategy(getType()) !=
ClassMetadataStrategy::Resilient)
return;

if (!hasObjCResilientClassStub(IGM, getType()))
return;

B.addRelativeAddress(
IGM.getAddrOfObjCResilientClassStub(
getType(), NotForDefinition,
TypeMetadataAddress::AddressPoint));
}
};
} // end anonymous namespace

Expand Down Expand Up @@ -2546,8 +2561,7 @@ namespace {
Type type = Target->mapTypeIntoContext(Target->getSuperclass());
auto *metadata = tryEmitConstantHeapMetadataRef(
IGM, type->getCanonicalType(),
/*allowUninit*/ false,
/*allowStub*/ false);
/*allowUninit*/ false);
assert(metadata != nullptr);
B.add(metadata);
}
Expand Down Expand Up @@ -3143,12 +3157,6 @@ void irgen::emitClassMetadata(IRGenModule &IGM, ClassDecl *classDecl,
classDecl, NotForDefinition,
TypeMetadataAddress::AddressPoint);
emitObjCClassSymbol(IGM, classDecl, stub);

// @_objc_non_lazy_realization is only for use by the standard
// library, and we cannot support it with Objective-C class
// stubs (which there are none of in the standard library).
assert(!classDecl->getAttrs().hasAttribute<ObjCNonLazyRealizationAttr>());
IGM.addObjCClass(stub, /*eagerInitialization=*/false);
}
}
break;
Expand Down
2 changes: 2 additions & 0 deletions lib/IRGen/IRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,8 @@ class IRGenModule {
SmallVector<llvm::WeakTrackingVH, 4> ObjCNonLazyClasses;
/// List of Objective-C categories, bitcast to i8*.
SmallVector<llvm::WeakTrackingVH, 4> ObjCCategories;
/// List of Objective-C categories on class stubs, bitcast to i8*.
SmallVector<llvm::WeakTrackingVH, 4> ObjCCategoriesOnStubs;
/// List of non-ObjC protocols described by this module.
SmallVector<ProtocolDecl *, 4> SwiftProtocols;
/// List of protocol conformances to generate descriptors for.
Expand Down
9 changes: 1 addition & 8 deletions lib/IRGen/MetadataRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -511,19 +511,12 @@ irgen::getRuntimeReifiedType(IRGenModule &IGM, CanType type) {
llvm::Constant *
irgen::tryEmitConstantHeapMetadataRef(IRGenModule &IGM,
CanType type,
bool allowDynamicUninitialized,
bool allowStub) {
bool allowDynamicUninitialized) {
auto theDecl = type->getClassOrBoundGenericClass();
assert(theDecl && "emitting constant heap metadata ref for non-class type?");

switch (IGM.getClassMetadataStrategy(theDecl)) {
case ClassMetadataStrategy::Resilient:
if (allowStub && IGM.Context.LangOpts.EnableObjCResilientClassStubs) {
return IGM.getAddrOfObjCResilientClassStub(theDecl, NotForDefinition,
TypeMetadataAddress::AddressPoint);
}
return nullptr;

case ClassMetadataStrategy::Singleton:
if (!allowDynamicUninitialized)
return nullptr;
Expand Down
3 changes: 1 addition & 2 deletions lib/IRGen/MetadataRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -592,8 +592,7 @@ CanType getRuntimeReifiedType(IRGenModule &IGM, CanType type);
/// by a constant.
llvm::Constant *tryEmitConstantHeapMetadataRef(IRGenModule &IGM,
CanType type,
bool allowUninitialized,
bool allowStub);
bool allowUninitialized);

enum class MetadataValueType { ObjCClass, TypeMetadata };

Expand Down
16 changes: 12 additions & 4 deletions stdlib/public/runtime/Metadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2673,12 +2673,19 @@ _swift_initClassMetadataImpl(ClassMetadata *self,
setUpObjCRuntimeGetImageNameFromClass();
}, nullptr);

#ifndef OBJC_REALIZECLASSFROMSWIFT_DEFINED
// Temporary workaround until _objc_realizeClassFromSwift is in the SDK.
static auto _objc_realizeClassFromSwift =
(Class (*)(Class _Nullable, const void *_Nullable))
(Class (*)(Class _Nullable, void *_Nullable))
dlsym(RTLD_NEXT, "_objc_realizeClassFromSwift");
#endif

// Temporary workaround until objc_loadClassref is in the SDK.
static auto objc_loadClassref =
(Class (*)(void *))
dlsym(RTLD_NEXT, "objc_loadClassref");
#endif

// Copy field offsets, generic arguments and (if necessary) vtable entries
// from our superclass.
copySuperclassMetadataToSubclass(self, layoutFlags);
Expand All @@ -2705,12 +2712,13 @@ _swift_initClassMetadataImpl(ClassMetadata *self,
// however we should not be using it for anything in a non-generic
// class.
if (auto *stub = description->getObjCResilientClassStub()) {
if (_objc_realizeClassFromSwift == nullptr) {
if (_objc_realizeClassFromSwift == nullptr ||
objc_loadClassref == nullptr) {
fatalError(0, "class %s requires missing Objective-C runtime feature; "
"the deployment target was newer than this OS\n",
self->getDescription()->Name.get());
}
_objc_realizeClassFromSwift((Class) self, stub);
_objc_realizeClassFromSwift((Class) self, const_cast<void *>(stub));
} else
swift_instantiateObjCClass(self);
}
Expand Down Expand Up @@ -2762,7 +2770,7 @@ _swift_updateClassMetadataImpl(ClassMetadata *self,
#ifndef OBJC_REALIZECLASSFROMSWIFT_DEFINED
// Temporary workaround until _objc_realizeClassFromSwift is in the SDK.
static auto _objc_realizeClassFromSwift =
(Class (*)(Class _Nullable, const void *_Nullable))
(Class (*)(Class _Nullable, void *_Nullable))
dlsym(RTLD_NEXT, "_objc_realizeClassFromSwift");
#endif

Expand Down
29 changes: 21 additions & 8 deletions test/IRGen/class_update_callback_with_stub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// RUN: %target-swift-frontend -emit-module -enable-library-evolution -emit-module-path=%t/resilient_struct.swiftmodule -I %t %S/../Inputs/resilient_struct.swift
// RUN: %target-swift-frontend(mock-sdk: -sdk %S/Inputs -I %t) -emit-module-path %t/resilient_class.swiftmodule -enable-library-evolution %S/../Inputs/resilient_class.swift
// RUN: %target-swift-frontend(mock-sdk: -sdk %S/Inputs -I %t) -emit-module-path %t/resilient_objc_class.swiftmodule -enable-library-evolution %S/../Inputs/resilient_objc_class.swift
// RUN: %target-swift-frontend(mock-sdk: -sdk %S/Inputs -I %t) -I %t -emit-ir -enable-library-evolution -enable-resilient-objc-class-stubs %s | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-%target-ptrsize --check-prefix=CHECK-%target-runtime -DINT=i%target-ptrsize
// RUN: %target-swift-frontend(mock-sdk: -sdk %S/Inputs -I %t) -I %t -emit-ir -enable-library-evolution -enable-resilient-objc-class-stubs %s > %t/out
// RUN: %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-%target-ptrsize --check-prefix=CHECK-%target-runtime -DINT=i%target-ptrsize < %t/out
// RUN: %FileCheck %s --check-prefix=NEGATIVE < %t/out

import Foundation
import resilient_class
Expand Down Expand Up @@ -49,6 +51,8 @@ import resilient_objc_class
// CHECK-SAME: @"got.$s15resilient_class22ResilientOutsideParentCMn"
// CHECK-SAME: @"got.$s15resilient_class22ResilientOutsideParentCACycfCTq"
// CHECK-SAME: @"$s31class_update_callback_with_stub17ResilientSubclassCACycfC"
// -- class stub
// CHECK-SAME: @"$s31class_update_callback_with_stub17ResilientSubclassCMt"
// CHECK-SAME: }>, section "__TEXT,__const", align 4


Expand Down Expand Up @@ -87,22 +91,27 @@ import resilient_objc_class
// CHECK-LABEL: @"_CATEGORY__TtC31class_update_callback_with_stub27FixedLayoutNSObjectSubclass_$_class_update_callback_with_stub" = private constant
// CHECK-SAME: @"$s31class_update_callback_with_stub27FixedLayoutNSObjectSubclassCMs"

// -- But not if the entire inheritance chain is in a single module

// -- The NSObject-derived class appears on the class list
// CHECK-LABEL: @"_CATEGORY__TtC15resilient_class22ResilientOutsideParent_$_class_update_callback_with_stub" = private constant
// CHECK-SAME: @"$s15resilient_class22ResilientOutsideParentCN"

// CHECK-LABEL: @objc_classes = internal global
// CHECK-SAME: @"$s31class_update_callback_with_stub25ResilientNSObjectSubclassCMs"
// CHECK-SAME: @"$s31class_update_callback_with_stub27FixedLayoutNSObjectSubclassCMs"
// CHECK-SAME: , section "__DATA,__objc_classlist,regular,no_dead_strip"

// -- Class stubs do not appear in the class list

// NEGATIVE-NOT: @objc_classes =

// -- The category list

// CHECK-LABEL: @objc_categories = internal global
// CHECK-SAME: @"_CATEGORY__TtC15resilient_class22ResilientOutsideParent_$_class_update_callback_with_stub"
// CHECK-SAME: , section "__DATA,__objc_catlist,regular,no_dead_strip"

// CHECK-LABEL: @objc_categories_stubs = internal global
// CHECK-SAME: @"_CATEGORY__TtC31class_update_callback_with_stub17ResilientSubclass_$_class_update_callback_with_stub"
// CHECK-SAME: @"_CATEGORY__TtC31class_update_callback_with_stub25ResilientNSObjectSubclass_$_class_update_callback_with_stub"
// CHECK-SAME: @"_CATEGORY__TtC31class_update_callback_with_stub27FixedLayoutNSObjectSubclass_$_class_update_callback_with_stub"
// CHECK-SAME: , section "__DATA,__objc_catlist,regular,no_dead_strip"
// CHECK-SAME: , section "__DATA,__objc_catlist2,regular,no_dead_strip"


// -- Address point for class stubs
Expand Down Expand Up @@ -152,4 +161,8 @@ extension ResilientNSObjectSubclass {

extension FixedLayoutNSObjectSubclass {
@objc public func objcMethod() {}
}
}

extension ResilientOutsideParent {
@objc public func anObjcMethod() {}
}
80 changes: 80 additions & 0 deletions test/Interpreter/objc_class_resilience_stubs.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// RUN: %empty-directory(%t)

// RUN: %target-build-swift-dylib(%t/%target-library-name(resilient_struct)) -enable-library-evolution %S/../Inputs/resilient_struct.swift -emit-module -emit-module-path %t/resilient_struct.swiftmodule
// RUN: %target-codesign %t/%target-library-name(resilient_struct)

// RUN: %target-build-swift-dylib(%t/%target-library-name(resilient_objc_class)) -I %t -L %t -lresilient_struct -enable-library-evolution %S/../Inputs/resilient_objc_class.swift -emit-module -emit-module-path %t/resilient_objc_class.swiftmodule -Xfrontend -enable-resilient-objc-class-stubs
// RUN: %target-codesign %t/%target-library-name(resilient_objc_class)

// RUN: %target-build-swift %s -L %t -I %t -lresilient_struct -lresilient_objc_class -o %t/main %target-rpath(%t) -Xfrontend -enable-resilient-objc-class-stubs
// RUN: %target-codesign %t/main

// RUN: %target-run %t/main %t/%target-library-name(resilient_struct) %t/%target-library-name(resilient_objc_class)

// REQUIRES: executable_test
// REQUIRES: objc_interop

import StdlibUnittest
import Foundation
import resilient_objc_class

// Old OS versions do not have this hook.
let loadClassrefMissing = {
nil == dlsym(UnsafeMutableRawPointer(bitPattern: -2),
"objc_loadClassref")
}()

var ResilientClassTestSuite = TestSuite("ResilientClass")

class ResilientNSObjectSubclass : ResilientNSObjectOutsideParent {}

@objc protocol MyProtocol {
func myMethod() -> Int
}

extension ResilientNSObjectSubclass : MyProtocol {
@objc func myMethod() -> Int { return 42 }
}

ResilientClassTestSuite.test("category on my class")
.skip(.custom({ loadClassrefMissing },
reason: "class stubs support not present"))
.code {
print(ResilientNSObjectSubclass.self)
let o = ResilientNSObjectSubclass()
expectEqual(42, (o as MyProtocol).myMethod())
}

@objc protocol AnotherProtocol {
func anotherMethod() -> Int
}

extension ResilientNSObjectOutsideParent : AnotherProtocol {
@objc func anotherMethod() -> Int { return 69 }
}

ResilientClassTestSuite.test("category on other class")
.skip(.custom({ loadClassrefMissing },
reason: "class stubs support not present"))
.code {
let o = ResilientNSObjectOutsideParent()
expectEqual(69, (o as AnotherProtocol).anotherMethod())
}

@_optimize(none) func blackHole<T>(_: T) {}

@_optimize(none) func forceMetadata() {
blackHole(ResilientNSObjectSubclass())
}

if loadClassrefMissing {
ResilientClassTestSuite.test("RealizeResilientClass")
.crashOutputMatches("class ResilientNSObjectSubclass requires missing Objective-C runtime feature")
.code {
expectCrashLater()
print("About to crash...")
forceMetadata()
}
}

runAllTests()
11 changes: 11 additions & 0 deletions validation-test/Runtime/Inputs/class-stubs-from-objc/first.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

open class BaseClass : NSObject {
@objc dynamic open func instanceMethod() -> Int {
return 42
}

@objc dynamic open class func classMethod() -> Int {
return 31337
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module first {
header "first.h"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import first

public class DerivedClass : BaseClass {}
Loading