Skip to content

Commit 48f7502

Browse files
committed
Runtime support for the NSArchiver class attributes.
Register class names for NSKeyedArchiver and NSKeyedUnarchiver based on the @NSKeyedArchiveLegacy and @_staticInitializeObjCMetadata class attributes. @NSKeyedArchiveLegacy registers a class name translation. @_staticInitializeObjCMetadata just makes sure that the metadata of a class is instantiated. This registration code is executed as a static initializer, like a C++ global constructor.
1 parent 1b459c1 commit 48f7502

File tree

9 files changed

+292
-1
lines changed

9 files changed

+292
-1
lines changed

include/swift/Runtime/RuntimeFunctions.def

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,14 @@ FUNCTION(GetKeyPath, swift_getKeyPath, C_CC,
12171217
ARGS(Int8PtrTy, Int8PtrTy),
12181218
ATTRS(NoUnwind))
12191219

1220+
// func _registerClassNameForArchiving(_ nameOrNull: UnsafePointer<CChar>?,
1221+
// _ c: AnyClass)
1222+
FUNCTION(RegisterClassNameForArchiving, swift_registerClassNameForArchiving,
1223+
SwiftCC,
1224+
RETURNS(VoidTy),
1225+
ARGS(Int8PtrTy, TypeMetadataPtrTy),
1226+
ATTRS(NoUnwind))
1227+
12201228
#if SWIFT_OBJC_INTEROP || !defined(SWIFT_RUNTIME_GENERATE_GLOBAL_SYMBOLS)
12211229

12221230
// Put here all definitions of runtime functions which are:

lib/IRGen/GenClass.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,9 @@ void IRGenModule::emitClassDecl(ClassDecl *D) {
960960
emitClassMetadata(*this, D,
961961
classTI.getLayout(*this, selfType),
962962
classTI.getClassLayout(*this, selfType));
963+
964+
IRGen.addClassForArchiveNameRegistration(D);
965+
963966
emitNestedTypeDecls(D->getMembers());
964967
emitFieldMetadataRecord(D);
965968
}

lib/IRGen/GenDecl.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@
4141
#include "llvm/IR/Module.h"
4242
#include "llvm/IR/TypeBuilder.h"
4343
#include "llvm/IR/Value.h"
44+
#include "llvm/IR/InlineAsm.h"
4445
#include "llvm/Support/Compiler.h"
4546
#include "llvm/Support/ConvertUTF.h"
4647
#include "llvm/Support/Path.h"
48+
#include "llvm/Transforms/Utils/ModuleUtils.h"
4749

4850
#include "ConstantBuilder.h"
4951
#include "Explosion.h"
@@ -947,6 +949,56 @@ void IRGenerator::emitLazyDefinitions() {
947949
}
948950
}
949951

952+
void IRGenerator::emitNSArchiveClassNameRegistration() {
953+
if (ClassesForArchiveNameRegistration.empty())
954+
return;
955+
956+
// Emit the register function in the primary module.
957+
IRGenModule *IGM = getPrimaryIGM();
958+
959+
llvm::Function *RegisterFn = llvm::Function::Create(
960+
llvm::FunctionType::get(IGM->VoidTy, false),
961+
llvm::GlobalValue::InternalLinkage,
962+
"_swift_register_class_names_for_archives");
963+
IRGenFunction RegisterIGF(*IGM, RegisterFn);
964+
RegisterFn->setAttributes(IGM->constructInitialAttributes());
965+
IGM->Module.getFunctionList().push_back(RegisterFn);
966+
RegisterFn->setCallingConv(IGM->DefaultCC);
967+
968+
for (ClassDecl *CD : ClassesForArchiveNameRegistration) {
969+
Type Ty = CD->getDeclaredType();
970+
llvm::Value *MetaData = RegisterIGF.emitTypeMetadataRef(getAsCanType(Ty));
971+
if (auto *LegacyAttr = CD->getAttrs().
972+
getAttribute<NSKeyedArchiveLegacyAttr>()) {
973+
// Register the name for the class in the NSKeyed(Un)Archiver.
974+
llvm::Value *NameStr = IGM->getAddrOfGlobalString(LegacyAttr->Name);
975+
RegisterIGF.Builder.CreateCall(IGM->getRegisterClassNameForArchivingFn(),
976+
{NameStr, MetaData});
977+
} else {
978+
assert(CD->getAttrs().hasAttribute<StaticInitializeObjCMetadataAttr>());
979+
980+
// In this case we don't add a name mapping, but just get the metadata
981+
// to make sure that the class is registered. But: we need to add a use
982+
// (empty inline asm instruction) for the metadata. Otherwise
983+
// llvm would optimize the metadata accessor call away because it's
984+
// defined as "readnone".
985+
llvm::FunctionType *asmFnTy =
986+
llvm::FunctionType::get(IGM->VoidTy, {MetaData->getType()},
987+
false /* = isVarArg */);
988+
llvm::InlineAsm *inlineAsm =
989+
llvm::InlineAsm::get(asmFnTy, "", "r", true /* = SideEffects */);
990+
RegisterIGF.Builder.CreateCall(inlineAsm, MetaData);
991+
}
992+
}
993+
RegisterIGF.Builder.CreateRetVoid();
994+
995+
// Add the registration function as a static initializer. We use a priority
996+
// slightly lower than used for C++ global constructors, so that the code is
997+
// executed before C++ global constructors (in case someone uses archives
998+
// from a C++ global constructor).
999+
llvm::appendToGlobalCtors(IGM->Module, RegisterFn, 60000, nullptr);
1000+
}
1001+
9501002
/// Emit symbols for eliminated dead methods, which can still be referenced
9511003
/// from other modules. This happens e.g. if a public class contains a (dead)
9521004
/// private method.

lib/IRGen/IRGen.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,7 @@ static std::unique_ptr<llvm::Module> performIRGeneration(IRGenOptions &Opts,
715715
IGM.emitTypeMetadataRecords();
716716
IGM.emitBuiltinReflectionMetadata();
717717
IGM.emitReflectionMetadataVersion();
718+
irgen.emitNSArchiveClassNameRegistration();
718719
}
719720

720721
// Emit symbols for eliminated dead methods.
@@ -893,6 +894,8 @@ static void performParallelIRGeneration(IRGenOptions &Opts,
893894

894895
irgen.emitReflectionMetadataVersion();
895896

897+
irgen.emitNSArchiveClassNameRegistration();
898+
896899
// Emit reflection metadata for builtin and imported types.
897900
irgen.emitBuiltinReflectionMetadata();
898901

lib/IRGen/IRGenModule.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,9 @@ llvm::Constant *swift::getWrapperFn(llvm::Module &Module,
558558
#define FUNCTION_FOR_CONV_C_CC(ID, NAME, CC, RETURNS, ARGS, ATTRS) \
559559
FUNCTION_IMPL(ID, NAME, CC, QUOTE(RETURNS), QUOTE(ARGS), QUOTE(ATTRS))
560560

561+
#define FUNCTION_FOR_CONV_SwiftCC(ID, NAME, CC, RETURNS, ARGS, ATTRS) \
562+
FUNCTION_IMPL(ID, NAME, CC, QUOTE(RETURNS), QUOTE(ARGS), QUOTE(ATTRS))
563+
561564
#define FUNCTION_FOR_CONV_RegisterPreservingCC(ID, NAME, CC, RETURNS, ARGS, \
562565
ATTRS) \
563566
FUNCTION_WITH_GLOBAL_SYMBOL_IMPL(ID, NAME, SWIFT_RT_ENTRY_REF(NAME), CC, \
@@ -748,6 +751,26 @@ void IRGenerator::addLazyWitnessTable(const ProtocolConformance *Conf) {
748751
}
749752
}
750753

754+
void IRGenerator::addClassForArchiveNameRegistration(ClassDecl *ClassDecl) {
755+
756+
// Those two attributes are interesting to us
757+
if (!ClassDecl->getAttrs().hasAttribute<NSKeyedArchiveLegacyAttr>() &&
758+
!ClassDecl->getAttrs().hasAttribute<StaticInitializeObjCMetadataAttr>())
759+
return;
760+
761+
// Exclude some classes where those attributes make no sense but could be set
762+
// for some reason. Just to be on the safe side.
763+
Type ClassTy = ClassDecl->getDeclaredType();
764+
if (ClassTy->is<UnboundGenericType>())
765+
return;
766+
if (ClassTy->hasArchetype())
767+
return;
768+
if (ClassDecl->hasClangNode())
769+
return;
770+
771+
ClassesForArchiveNameRegistration.push_back(ClassDecl);
772+
}
773+
751774
llvm::AttributeSet IRGenModule::getAllocAttrs() {
752775
if (AllocAttrs.isEmpty()) {
753776
AllocAttrs = llvm::AttributeSet::get(LLVMContext,

lib/IRGen/IRGenModule.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ class IRGenerator {
222222
/// The queue of lazy witness tables to emit.
223223
llvm::SmallVector<SILWitnessTable *, 4> LazyWitnessTables;
224224

225+
llvm::SmallVector<ClassDecl *, 4> ClassesForArchiveNameRegistration;
226+
225227
/// The order in which all the SIL function definitions should
226228
/// appear in the translation unit.
227229
llvm::DenseMap<SILFunction*, unsigned> FunctionOrder;
@@ -296,6 +298,8 @@ class IRGenerator {
296298
/// Emit a symbol identifying the reflection metadata version.
297299
void emitReflectionMetadataVersion();
298300

301+
void emitNSArchiveClassNameRegistration();
302+
299303
/// Checks if the metadata of \p Nominal can be emitted lazily.
300304
///
301305
/// If yes, \p Nominal is added to eligibleLazyMetadata and true is returned.
@@ -333,7 +337,9 @@ class IRGenerator {
333337
{fieldTypes.begin(), fieldTypes.end()},
334338
fn, IGM});
335339
}
336-
340+
341+
void addClassForArchiveNameRegistration(ClassDecl *ClassDecl);
342+
337343
unsigned getFunctionOrder(SILFunction *F) {
338344
auto it = FunctionOrder.find(F);
339345
assert(it != FunctionOrder.end() &&

stdlib/public/SDK/Foundation/Foundation.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,20 @@ extension CVarArg where Self: _ObjectiveCBridgeable {
9595
return _encodeBitsAsWords(object)
9696
}
9797
}
98+
99+
//===----------------------------------------------------------------------===//
100+
// Runtime support for NSKeyedArchives
101+
//===----------------------------------------------------------------------===//
102+
103+
@_silgen_name("swift_registerClassNameForArchiving")
104+
public func _registerClassNameForArchiving(_ nameForClass: UnsafePointer<CChar>,
105+
_ classType: AnyClass) {
106+
// If it's not possible to create a String from the name, it should abort
107+
// and not fail silently.
108+
let nameStr = String(utf8String: nameForClass)!
109+
110+
// Register the class name mapping for archiving and unarchiving.
111+
NSKeyedArchiver.setClassName(nameStr, for: classType)
112+
NSKeyedUnarchiver.setClass(classType, forClassName: nameStr)
113+
}
114+

stdlib/public/runtime/RuntimeEntrySymbols.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
// implementations.
3737
#define FOR_CONV_DefaultCC(...)
3838
#define FOR_CONV_C_CC(...)
39+
#define FOR_CONV_SwiftCC(...)
3940
// Entry points using the new calling convention require global symbols
4041
// referring to their implementations.
4142
#define FOR_CONV_RegisterPreservingCC(x) x
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// RUN: rm -rf %t && mkdir -p %t
2+
// RUN: %target-build-swift %s -module-name=test -DENCODE -o %t/encode
3+
// RUN: %target-build-swift %s -module-name=test -o %t/decode
4+
// RUN: %target-run %t/encode %t/test.arc
5+
// RUN: %FileCheck -check-prefix=CHECK-ARCHIVE %s < %t/test.arc
6+
// RUN: %target-run %t/decode %t/test.arc | %FileCheck %s
7+
8+
// REQUIRES: executable_test
9+
// REQUIRES: objc_interop
10+
// UNSUPPORTED: OS=tvos
11+
// UNSUPPORTED: OS=watchos
12+
13+
import Foundation
14+
15+
struct ABC {
16+
// CHECK-ARCHIVE-DAG: nested_class_coding
17+
@NSKeyedArchiveLegacy("nested_class_coding")
18+
class NestedClass : NSObject, NSCoding {
19+
var i : Int
20+
21+
init(_ ii: Int) {
22+
i = ii
23+
}
24+
25+
required init(coder aDecoder: NSCoder) {
26+
i = aDecoder.decodeInteger(forKey: "i")
27+
}
28+
29+
func encode(with aCoder: NSCoder) {
30+
aCoder.encode(i, forKey: "i")
31+
}
32+
}
33+
}
34+
35+
// CHECK-ARCHIVE-DAG: private_class_coding
36+
@NSKeyedArchiveLegacy("private_class_coding")
37+
private class PrivateClass : NSObject, NSCoding {
38+
var pi : Int
39+
40+
init(_ ii: Int) {
41+
pi = ii
42+
}
43+
44+
required init(coder aDecoder: NSCoder) {
45+
pi = aDecoder.decodeInteger(forKey: "pi")
46+
}
47+
48+
func encode(with aCoder: NSCoder) {
49+
aCoder.encode(pi, forKey: "pi")
50+
}
51+
}
52+
53+
@NSKeyedArchiveSubclassesOnly
54+
class GenericClass<T> : NSObject, NSCoding {
55+
var gi : T? = nil
56+
57+
override init() {
58+
}
59+
60+
required init(coder aDecoder: NSCoder) {
61+
}
62+
63+
func encode(with aCoder: NSCoder) {
64+
}
65+
}
66+
67+
// CHECK-ARCHIVE-DAG: test.IntClass
68+
class IntClass : GenericClass<Int> {
69+
70+
init(ii: Int) {
71+
super.init()
72+
gi = ii
73+
}
74+
75+
required init(coder aDecoder: NSCoder) {
76+
super.init(coder: aDecoder)
77+
gi = aDecoder.decodeInteger(forKey: "gi")
78+
}
79+
80+
override func encode(with aCoder: NSCoder) {
81+
aCoder.encode(gi!, forKey: "gi")
82+
}
83+
}
84+
85+
// CHECK-ARCHIVE-DAG: double_class_coding
86+
@NSKeyedArchiveLegacy("double_class_coding")
87+
class DoubleClass : GenericClass<Double> {
88+
89+
init(dd: Double) {
90+
super.init()
91+
gi = dd
92+
}
93+
94+
required init(coder aDecoder: NSCoder) {
95+
super.init(coder: aDecoder)
96+
gi = aDecoder.decodeDouble(forKey: "gi")
97+
}
98+
99+
override func encode(with aCoder: NSCoder) {
100+
aCoder.encode(gi!, forKey: "gi")
101+
}
102+
}
103+
104+
// CHECK-ARCHIVE-DAG: top_level_coding
105+
@NSKeyedArchiveLegacy("top_level_coding")
106+
class TopLevel : NSObject, NSCoding {
107+
var tli : Int
108+
109+
var nested: ABC.NestedClass?
110+
fileprivate var priv: PrivateClass?
111+
var intc : IntClass?
112+
var doublec : DoubleClass?
113+
114+
init(_ ii: Int) {
115+
tli = ii
116+
}
117+
118+
required init(coder aDecoder: NSCoder) {
119+
tli = aDecoder.decodeInteger(forKey: "tli")
120+
nested = aDecoder.decodeObject(forKey: "nested") as? ABC.NestedClass
121+
priv = aDecoder.decodeObject(forKey: "priv") as? PrivateClass
122+
intc = aDecoder.decodeObject(forKey: "int") as? IntClass
123+
doublec = aDecoder.decodeObject(forKey: "double") as? DoubleClass
124+
}
125+
126+
func encode(with aCoder: NSCoder) {
127+
aCoder.encode(tli, forKey: "tli")
128+
aCoder.encode(nested, forKey: "nested")
129+
aCoder.encode(priv, forKey: "priv")
130+
aCoder.encode(intc, forKey: "int")
131+
aCoder.encode(doublec, forKey: "double")
132+
}
133+
}
134+
135+
func main() {
136+
137+
let args = CommandLine.arguments
138+
139+
#if ENCODE
140+
let c = TopLevel(27)
141+
c.nested = ABC.NestedClass(28)
142+
c.priv = PrivateClass(29)
143+
c.intc = IntClass(ii: 42)
144+
c.doublec = DoubleClass(dd: 3.14)
145+
146+
NSKeyedArchiver.archiveRootObject(c, toFile: args[1])
147+
#else
148+
if let u = NSKeyedUnarchiver.unarchiveObject(withFile: args[1]) {
149+
if let x = u as? TopLevel {
150+
// CHECK: top-level: 27
151+
print("top-level: \(x.tli)")
152+
if let n = x.nested {
153+
// CHECK: nested: 28
154+
print("nested: \(n.i)")
155+
}
156+
if let p = x.priv {
157+
// CHECK: private: 29
158+
print("private: \(p.pi)")
159+
}
160+
if let g = x.intc {
161+
// CHECK: int: 42
162+
print("int: \(g.gi!)")
163+
}
164+
if let d = x.doublec {
165+
// CHECK: double: 3.14
166+
print("double: \(d.gi!)")
167+
}
168+
} else {
169+
print(u)
170+
}
171+
} else {
172+
print("nil")
173+
}
174+
#endif
175+
}
176+
177+
main()
178+

0 commit comments

Comments
 (0)