Skip to content

Commit cd00a8c

Browse files
committed
Test that newer libobjcs let the Swift runtime set up a class's layout
Specifically, when the class is referenced from Objective-C first, before being referenced from Swift in any way. This is important for resilience, since up until now Objective-C isn't used to the size of a class changing based on anything other than a superclass's size changing. This is basically the test that would have gone with 9024768 had there been a build of libobjc to test it with. rdar://problem/45718008
1 parent 664e565 commit cd00a8c

File tree

6 files changed

+168
-0
lines changed

6 files changed

+168
-0
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import Resilient
2+
import Foundation
3+
import OneWordSuperclass
4+
5+
public class StaticClass: OneWordSuperclass {
6+
@objc var first: Int32 = 0
7+
var middle = GrowsToInt64()
8+
@objc var last: Int = 0
9+
10+
@objc public static var offsetOfFirst: Int {
11+
// IRGen lays out Swift classes that subclass Objective-C classes as if the
12+
// only superclass was NSObject, so the starting (offset % alignment) isn't
13+
// always 0. This means that on 32-bit platforms we'll have a gap *before*
14+
// 'first' when we need 8-byte alignment, rather than after as you'd see in
15+
// a struct (or base class).
16+
return max(MemoryLayout<Int>.size, MemoryLayout<GrowsToInt64>.alignment) +
17+
MemoryLayout<Int>.size
18+
}
19+
20+
@objc public static var totalSize: Int {
21+
return (2 * MemoryLayout<Int>.size) +
22+
(2 * MemoryLayout<GrowsToInt64>.size) + // alignment
23+
MemoryLayout<Int>.size
24+
}
25+
}
26+
27+
/// This class has the same layout as `StaticClass`, but will be accessed using
28+
/// `NSClassFromString` instead of `+class`.
29+
public class DynamicClass: OneWordSuperclass {
30+
@objc var first: Int32 = 0
31+
var middle = GrowsToInt64()
32+
@objc var last: Int = 0
33+
34+
@objc public static var offsetOfFirst: Int {
35+
// See above.
36+
return max(MemoryLayout<Int>.size, MemoryLayout<GrowsToInt64>.alignment) +
37+
MemoryLayout<Int>.size
38+
}
39+
40+
@objc public static var totalSize: Int {
41+
return (2 * MemoryLayout<Int>.size) +
42+
(2 * MemoryLayout<GrowsToInt64>.size) + // alignment
43+
MemoryLayout<Int>.size
44+
}
45+
}
46+
47+
public class PureSwiftBaseClass {
48+
var word: Int64 = 0
49+
}
50+
51+
public class PureSwiftClass: PureSwiftBaseClass {
52+
@objc var first: Int32 = 0
53+
var middle = GrowsToInt64()
54+
@objc var last: Int = 0
55+
56+
@objc public static var offsetOfFirst: Int {
57+
return (2 * MemoryLayout<Int>.size) + MemoryLayout<Int64>.size
58+
}
59+
60+
@objc public static var totalSize: Int {
61+
return (2 * MemoryLayout<Int>.size) + MemoryLayout<Int64>.size +
62+
(2 * MemoryLayout<GrowsToInt64>.size) + // alignment
63+
MemoryLayout<Int>.size
64+
}
65+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@import Foundation;
2+
3+
@interface OneWordSuperclass : NSObject
4+
@end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#import "OneWordSuperclass.h"
2+
3+
@implementation OneWordSuperclass {
4+
intptr_t unused;
5+
}
6+
@end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
public struct GrowsToInt64 {
2+
#if SMALL
3+
var value: Int32
4+
#elseif BIG
5+
var value: Int64
6+
#else
7+
#error("Must define SMALL or BIG")
8+
#endif
9+
10+
public init() { self.value = 0 }
11+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module OneWordSuperclass {
2+
header "OneWordSuperclass.h"
3+
export *
4+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Check that when Objective-C is first to touch a Swift class, it gives the
2+
// Swift runtime a chance to update instance size and ivar offset metadata.
3+
4+
// RUN: %empty-directory(%t)
5+
// RUN: %target-build-swift -emit-library -emit-module -o %t/libResilient.dylib %S/Inputs/class-layout-from-objc/Resilient.swift -Xlinker -install_name -Xlinker @executable_path/libResilient.dylib -Xfrontend -enable-resilience -DSMALL
6+
7+
// RUN: %target-clang -c %S/Inputs/class-layout-from-objc/OneWordSuperclass.m -fmodules -fobjc-arc -o %t/OneWordSuperclass.o
8+
// RUN: %target-build-swift -emit-library -o %t/libClasses.dylib -emit-objc-header-path %t/Classes.h -I %t -I %S/Inputs/class-layout-from-objc/ %S/Inputs/class-layout-from-objc/Classes.swift %t/OneWordSuperclass.o -Xlinker -install_name -Xlinker @executable_path/libClasses.dylib -lResilient -L %t
9+
// RUN: %target-clang %s -I %S/Inputs/class-layout-from-objc/ -I %t -fmodules -fobjc-arc -o %t/main -lResilient -lClasses -L %t
10+
// RUN: %target-codesign %t/main %t/libResilient.dylib %t/libClasses.dylib
11+
// RUN: %target-run %t/main OLD %t/libResilient.dylib %t/libClasses.dylib
12+
13+
// RUN: %target-build-swift -emit-library -emit-module -o %t/libResilient.dylib %S/Inputs/class-layout-from-objc/Resilient.swift -Xlinker -install_name -Xlinker @executable_path/libResilient.dylib -Xfrontend -enable-resilience -DBIG
14+
// RUN: %target-codesign %t/libResilient.dylib
15+
// RUN: %target-run %t/main NEW %t/libResilient.dylib %t/libClasses.dylib
16+
17+
// Try again when the class itself is also resilient.
18+
// RUN: %target-build-swift -emit-library -o %t/libClasses.dylib -emit-objc-header-path %t/Classes.h -I %S/Inputs/class-layout-from-objc/ -I %t %S/Inputs/class-layout-from-objc/Classes.swift %t/OneWordSuperclass.o -Xlinker -install_name -Xlinker @executable_path/libClasses.dylib -lResilient -L %t
19+
// RUN: %target-codesign %t/libClasses.dylib
20+
// RUN: %target-run %t/main OLD %t/libResilient.dylib %t/libClasses.dylib
21+
22+
// RUN: %target-build-swift -emit-library -emit-module -o %t/libResilient.dylib %S/Inputs/class-layout-from-objc/Resilient.swift -Xlinker -install_name -Xlinker @executable_path/libResilient.dylib -Xfrontend -enable-resilience -DSMALL
23+
// RUN: %target-codesign %t/libResilient.dylib
24+
// RUN: %target-run %t/main NEW %t/libResilient.dylib %t/libClasses.dylib
25+
26+
// REQUIRES: executable_test
27+
// REQUIRES: objc_interop
28+
29+
#import <objc/runtime.h>
30+
#import <assert.h>
31+
#import <dlfcn.h>
32+
#import <stdbool.h>
33+
#import <string.h>
34+
35+
#import "Classes.h"
36+
37+
void check(Class c) {
38+
assert(c);
39+
40+
size_t expectedSize = [c totalSize];
41+
size_t actualSize = class_getInstanceSize([c class]);
42+
NSLog(@"%@: expected size %zd, actual size %zd", c, expectedSize, actualSize);
43+
assert(expectedSize == actualSize);
44+
45+
size_t expectedOffsetOfFirst = [c offsetOfFirst];
46+
size_t offsetOfFirst = ivar_getOffset(class_getInstanceVariable(c, "first"));
47+
NSLog(@"expected offset of 'first' %zd, actual %zd",
48+
expectedOffsetOfFirst, offsetOfFirst);
49+
assert(offsetOfFirst == expectedOffsetOfFirst);
50+
51+
size_t offsetOfLast = ivar_getOffset(class_getInstanceVariable(c, "last"));
52+
NSLog(@"offset of 'last' %zd", offsetOfLast);
53+
assert(offsetOfLast == actualSize - sizeof(intptr_t));
54+
}
55+
56+
int main(int argc, const char * const argv[]) {
57+
assert(argc > 1);
58+
59+
if (!strcmp(argv[1], "OLD")) {
60+
;
61+
} else if (!strcmp(argv[1], "NEW")) {
62+
// Only test the new behavior on a new enough libobjc.
63+
if (!dlsym(RTLD_NEXT, "_objc_realizeClassFromSwift")) {
64+
fprintf(stderr, "skipping evolution tests; OS too old\n");
65+
return EXIT_SUCCESS;
66+
}
67+
} else {
68+
fprintf(stderr, "usage: %s (OLD|NEW)\n", argv[0]);
69+
return EXIT_FAILURE;
70+
}
71+
72+
@autoreleasepool {
73+
NSLog(@"%zd", class_getInstanceSize([OneWordSuperclass class]));
74+
check([StaticClass class]);
75+
check(objc_getClass("Classes.DynamicClass"));
76+
check(objc_getClass("Classes.PureSwiftClass"));
77+
}
78+
}

0 commit comments

Comments
 (0)