-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Test that newer libobjcs let the Swift runtime set up a class's layout #21942
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import Resilient | ||
import Foundation | ||
import OneWordSuperclass | ||
|
||
public class StaticClass: OneWordSuperclass { | ||
@objc var first: Int32 = 0 | ||
var middle = GrowsToInt64() | ||
@objc var last: Int = 0 | ||
|
||
@objc public static var offsetOfFirst: Int { | ||
// IRGen lays out Swift classes that subclass Objective-C classes as if the | ||
// only superclass was NSObject, so the starting (offset % alignment) isn't | ||
// always 0. This means that on 32-bit platforms we'll have a gap *before* | ||
// 'first' when we need 8-byte alignment, rather than after as you'd see in | ||
// a struct (or base class). | ||
return max(MemoryLayout<Int>.size, MemoryLayout<GrowsToInt64>.alignment) + | ||
MemoryLayout<Int>.size | ||
} | ||
|
||
@objc public static var totalSize: Int { | ||
return (2 * MemoryLayout<Int>.size) + | ||
(2 * MemoryLayout<GrowsToInt64>.size) + // alignment | ||
MemoryLayout<Int>.size | ||
} | ||
} | ||
|
||
/// This class has the same layout as `StaticClass`, but will be accessed using | ||
/// `NSClassFromString` instead of `+class`. | ||
public class DynamicClass: OneWordSuperclass { | ||
@objc var first: Int32 = 0 | ||
var middle = GrowsToInt64() | ||
@objc var last: Int = 0 | ||
|
||
@objc public static var offsetOfFirst: Int { | ||
// See above. | ||
return max(MemoryLayout<Int>.size, MemoryLayout<GrowsToInt64>.alignment) + | ||
MemoryLayout<Int>.size | ||
} | ||
|
||
@objc public static var totalSize: Int { | ||
return (2 * MemoryLayout<Int>.size) + | ||
(2 * MemoryLayout<GrowsToInt64>.size) + // alignment | ||
MemoryLayout<Int>.size | ||
} | ||
} | ||
|
||
public class PureSwiftBaseClass { | ||
var word: Int64 = 0 | ||
} | ||
|
||
public class PureSwiftClass: PureSwiftBaseClass { | ||
@objc var first: Int32 = 0 | ||
var middle = GrowsToInt64() | ||
@objc var last: Int = 0 | ||
|
||
@objc public static var offsetOfFirst: Int { | ||
return (2 * MemoryLayout<Int>.size) + MemoryLayout<Int64>.size | ||
} | ||
|
||
@objc public static var totalSize: Int { | ||
return (2 * MemoryLayout<Int>.size) + MemoryLayout<Int64>.size + | ||
(2 * MemoryLayout<GrowsToInt64>.size) + // alignment | ||
MemoryLayout<Int>.size | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
@import Foundation; | ||
|
||
@interface OneWordSuperclass : NSObject | ||
@end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#import "OneWordSuperclass.h" | ||
|
||
@implementation OneWordSuperclass { | ||
intptr_t unused; | ||
} | ||
@end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
public struct GrowsToInt64 { | ||
#if SMALL | ||
var value: Int32 | ||
#elseif BIG | ||
var value: Int64 | ||
#else | ||
#error("Must define SMALL or BIG") | ||
#endif | ||
|
||
public init() { self.value = 0 } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module OneWordSuperclass { | ||
header "OneWordSuperclass.h" | ||
export * | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// Check that when Objective-C is first to touch a Swift class, it gives the | ||
// Swift runtime a chance to update instance size and ivar offset metadata. | ||
|
||
// RUN: %empty-directory(%t) | ||
// 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 | ||
|
||
// RUN: %target-clang -c %S/Inputs/class-layout-from-objc/OneWordSuperclass.m -fmodules -fobjc-arc -o %t/OneWordSuperclass.o | ||
// 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 | ||
// RUN: %target-clang %s -I %S/Inputs/class-layout-from-objc/ -I %t -fmodules -fobjc-arc -o %t/main -lResilient -lClasses -L %t | ||
// RUN: %target-codesign %t/main %t/libResilient.dylib %t/libClasses.dylib | ||
// RUN: %target-run %t/main OLD %t/libResilient.dylib %t/libClasses.dylib | ||
|
||
// 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 | ||
// RUN: %target-codesign %t/libResilient.dylib | ||
// RUN: %target-run %t/main NEW %t/libResilient.dylib %t/libClasses.dylib | ||
|
||
// Try again when the class itself is also resilient. | ||
// 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 | ||
// RUN: %target-codesign %t/libClasses.dylib | ||
// RUN: %target-run %t/main OLD %t/libResilient.dylib %t/libClasses.dylib | ||
|
||
// 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 | ||
// RUN: %target-codesign %t/libResilient.dylib | ||
// RUN: %target-run %t/main NEW %t/libResilient.dylib %t/libClasses.dylib | ||
|
||
// REQUIRES: executable_test | ||
// REQUIRES: objc_interop | ||
|
||
#import <objc/runtime.h> | ||
#import <assert.h> | ||
#import <dlfcn.h> | ||
#import <stdbool.h> | ||
#import <string.h> | ||
|
||
#import "Classes.h" | ||
|
||
void check(Class c) { | ||
assert(c); | ||
|
||
size_t expectedSize = [c totalSize]; | ||
size_t actualSize = class_getInstanceSize([c class]); | ||
NSLog(@"%@: expected size %zd, actual size %zd", c, expectedSize, actualSize); | ||
assert(expectedSize == actualSize); | ||
|
||
size_t expectedOffsetOfFirst = [c offsetOfFirst]; | ||
size_t offsetOfFirst = ivar_getOffset(class_getInstanceVariable(c, "first")); | ||
NSLog(@"expected offset of 'first' %zd, actual %zd", | ||
expectedOffsetOfFirst, offsetOfFirst); | ||
assert(offsetOfFirst == expectedOffsetOfFirst); | ||
|
||
size_t offsetOfLast = ivar_getOffset(class_getInstanceVariable(c, "last")); | ||
NSLog(@"offset of 'last' %zd", offsetOfLast); | ||
assert(offsetOfLast == actualSize - sizeof(intptr_t)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth creating an instance and checking anything there as well, maybe calling a Swift method too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I couldn't think of anything that wouldn't go through a Swift entry point except There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, the case I thought we could test is calling Swift code after Objective-C had already initialized the class metadata There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, we call static methods. Think an instance method is interesting enough to also be worth doing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably not. |
||
} | ||
|
||
int main(int argc, const char * const argv[]) { | ||
assert(argc > 1); | ||
|
||
if (!strcmp(argv[1], "OLD")) { | ||
; | ||
} else if (!strcmp(argv[1], "NEW")) { | ||
// Only test the new behavior on a new enough libobjc. | ||
if (!dlsym(RTLD_NEXT, "_objc_realizeClassFromSwift")) { | ||
fprintf(stderr, "skipping evolution tests; OS too old\n"); | ||
return EXIT_SUCCESS; | ||
} | ||
} else { | ||
fprintf(stderr, "usage: %s (OLD|NEW)\n", argv[0]); | ||
return EXIT_FAILURE; | ||
} | ||
|
||
@autoreleasepool { | ||
NSLog(@"%zd", class_getInstanceSize([OneWordSuperclass class])); | ||
check([StaticClass class]); | ||
check(objc_getClass("Classes.DynamicClass")); | ||
check(objc_getClass("Classes.PureSwiftClass")); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these (non-public) ivars @objc?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are the ones I'm testing the ivar offsets of. It doesn't really matter if they're public or not; Swift never exposes direct compile-time ivar access to Objective-C.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we not emit those records for non-@objc ivars too? I thought we had to so the objc runtime can slide them. In any case I'm fine with the @objc being here, just wondering if there was a specific reason
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we ever don't emit them for non-
@objc
ivars, I don't want the test to change. I think it's appropriate for something accessed through the ObjC runtime. (Besides, the non-@objc
ivars aren't representable in Objective-C, and it feels weird to rely on them being there.)