Skip to content

Commit

Permalink
Emit BOOL properties as bitfields (#208)
Browse files Browse the repository at this point in the history
* Emit BOOL properties as bitfields

This will make Pinterest models smaller in the heap, which is useful since they live for the life of the application. This changes only the model, not the short lived builders, to use bitfields. It requires special handling of boolean properites throughout the process.

In Pinterest, PIPin has 25 BOOLs (currently stored as 200 bytes, which takes 4 bytes afterwards), & PIUser has 40 (currently stored as 320 bytes, and takes 8 bytes afterwards). There may be even more wins, simply by grouping all the BOOLs near each other. If the layout of a class is a pointer, follwed by a BOOL, followed by a pointer, the 64 bit alignment of fields will cause that BOOL to actually be 8 bytes!

* compare with 1 when assigning from builder to pass build warnings in app

* set the bitfield to the decoded BOOL & 0x1 to suppress compiler warnings in the app. copied from the dirty options code

* swift format it

* remove local path in scheme file

* use bitwise AND, as per review

* regenerate test code after last commit

* fix up empty newlines added

* get rid of more added blank lines

* make format

* add extension on Schema to indicate if it is a .boolean or not and use it where applicable.

* make format

* fix typo and re-run format

* re-instate implementing equality hash and getters so that old ivars are not generated. lost in a merge conflict along the way.
  • Loading branch information
bolsinga authored and rahul-malik committed Jun 11, 2019
1 parent 47fb542 commit d4a714d
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 100 deletions.
33 changes: 21 additions & 12 deletions Examples/Cocoa/Sources/Objective_C/Everything.m
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,10 @@ - (void)encodeWithCoder:(NSCoder *)aCoder
unsigned int EverythingDirtyPropertyUriProp:1;
};

struct EverythingBooleanProperties {
unsigned int EverythingBooleanBooleanProp:1;
};

@interface Everything ()
{
EverythingCharEnum _charEnum;
Expand All @@ -630,6 +634,7 @@ @interface Everything ()
EverythingUnsignedShortEnum _unsignedShortEnum;
}
@property (nonatomic, assign, readwrite) struct EverythingDirtyProperties everythingDirtyProperties;
@property (nonatomic, assign, readwrite) struct EverythingBooleanProperties everythingBooleanProperties;
@end

@interface EverythingBuilder ()
Expand Down Expand Up @@ -704,7 +709,7 @@ - (instancetype)initWithModelDictionary:(NS_VALID_UNTIL_END_OF_SCOPE NSDictionar
__unsafe_unretained id value = modelDictionary[@"boolean_prop"]; // Collection will retain.
if (value != nil) {
if (value != (id)kCFNull) {
self->_booleanProp = [value boolValue];
self->_everythingBooleanProperties.EverythingBooleanBooleanProp = [value boolValue] & 0x1;
}
self->_everythingDirtyProperties.EverythingDirtyPropertyBooleanProp = 1;
}
Expand Down Expand Up @@ -1049,7 +1054,7 @@ - (instancetype)initWithModelDictionary:(NS_VALID_UNTIL_END_OF_SCOPE NSDictionar
self->_polymorphicProp = [EverythingPolymorphicProp objectWithString:[value copy]];
}
if ([value isKindOfClass:[NSNumber class]] && strcmp([value objCType], @encode(BOOL)) == 0) {
self->_polymorphicProp = [EverythingPolymorphicProp objectWithBoolean:[value boolValue]];
self->_polymorphicProp = [EverythingPolymorphicProp objectWithBoolean:[value boolValue] & 0x1];
}
if ([value isKindOfClass:[NSNumber class]] && (strcmp([value objCType], @encode(int)) == 0 ||
strcmp([value objCType], @encode(unsigned int)) == 0 ||
Expand Down Expand Up @@ -1223,7 +1228,6 @@ - (instancetype)initWithBuilder:(EverythingBuilder *)builder initType:(PlankMode
return self;
}
_arrayProp = builder.arrayProp;
_booleanProp = builder.booleanProp;
_charEnum = builder.charEnum;
_dateProp = builder.dateProp;
_intEnum = builder.intEnum;
Expand Down Expand Up @@ -1258,6 +1262,7 @@ - (instancetype)initWithBuilder:(EverythingBuilder *)builder initType:(PlankMode
_unsignedIntEnum = builder.unsignedIntEnum;
_unsignedShortEnum = builder.unsignedShortEnum;
_uriProp = builder.uriProp;
_everythingBooleanProperties.EverythingBooleanBooleanProp = builder.booleanProp == 1;
_everythingDirtyProperties = builder.everythingDirtyProperties;
if ([self class] == [Everything class]) {
[[NSNotificationCenter defaultCenter] postNotificationName:kPlankDidInitializeNotification object:self userInfo:@{ kPlankInitTypeKey : @(initType) }];
Expand All @@ -1273,9 +1278,6 @@ - (NSString *)debugDescription
if (props.EverythingDirtyPropertyArrayProp) {
[descriptionFields addObject:[@"_arrayProp = " stringByAppendingFormat:@"%@", _arrayProp]];
}
if (props.EverythingDirtyPropertyBooleanProp) {
[descriptionFields addObject:[@"_booleanProp = " stringByAppendingFormat:@"%@", @(_booleanProp)]];
}
if (props.EverythingDirtyPropertyCharEnum) {
[descriptionFields addObject:[@"_charEnum = " stringByAppendingFormat:@"%@", @(_charEnum)]];
}
Expand Down Expand Up @@ -1378,6 +1380,9 @@ - (NSString *)debugDescription
if (props.EverythingDirtyPropertyUriProp) {
[descriptionFields addObject:[@"_uriProp = " stringByAppendingFormat:@"%@", _uriProp]];
}
if (props.EverythingDirtyPropertyBooleanProp) {
[descriptionFields addObject:[@"_everythingBooleanProperties.EverythingBooleanBooleanProp = " stringByAppendingFormat:@"%@", @(_everythingBooleanProperties.EverythingBooleanBooleanProp)]];
}
return [NSString stringWithFormat:@"Everything = {\n%@\n}", debugDescriptionForFields(descriptionFields)];
}
- (instancetype)copyWithBlock:(PLANK_NOESCAPE void (^)(EverythingBuilder *builder))block
Expand Down Expand Up @@ -1412,7 +1417,7 @@ - (BOOL)isEqualToEverything:(Everything *)anObject
(_intProp == anObject.intProp) &&
(_intEnum == anObject.intEnum) &&
(_charEnum == anObject.charEnum) &&
(_booleanProp == anObject.booleanProp) &&
(_everythingBooleanProperties.EverythingBooleanBooleanProp == anObject.booleanProp) &&
(_arrayProp == anObject.arrayProp || [_arrayProp isEqualToArray:anObject.arrayProp]) &&
(_dateProp == anObject.dateProp || [_dateProp isEqualToDate:anObject.dateProp]) &&
(_listPolymorphicValues == anObject.listPolymorphicValues || [_listPolymorphicValues isEqualToArray:anObject.listPolymorphicValues]) &&
Expand Down Expand Up @@ -1444,7 +1449,7 @@ - (NSUInteger)hash
NSUInteger subhashes[] = {
17,
[_arrayProp hash],
(_booleanProp ? 1231 : 1237),
(_everythingBooleanProperties.EverythingBooleanBooleanProp ? 1231 : 1237),
(NSUInteger)_charEnum,
[_dateProp hash],
(NSUInteger)_intEnum,
Expand Down Expand Up @@ -1503,9 +1508,6 @@ - (NSDictionary *)dictionaryObjectRepresentation
[dict setObject:[NSNull null] forKey:@"array_prop"];
}
}
if (_everythingDirtyProperties.EverythingDirtyPropertyBooleanProp) {
[dict setObject:@(_booleanProp) forKey: @"boolean_prop"];
}
if (_everythingDirtyProperties.EverythingDirtyPropertyCharEnum) {
[dict setObject:@(_charEnum) forKey:@"char_enum"];
}
Expand Down Expand Up @@ -1791,6 +1793,9 @@ - (NSDictionary *)dictionaryObjectRepresentation
[dict setObject:[NSNull null] forKey:@"uri_prop"];
}
}
if (_everythingDirtyProperties.EverythingDirtyPropertyBooleanProp) {
[dict setObject:@(_everythingBooleanProperties.EverythingBooleanBooleanProp) forKey: @"boolean_prop"];
}
return dict;
}
- (BOOL)isArrayPropSet
Expand Down Expand Up @@ -1937,6 +1942,10 @@ - (BOOL)isUriPropSet
{
return _everythingDirtyProperties.EverythingDirtyPropertyUriProp == 1;
}
- (BOOL)booleanProp
{
return _everythingBooleanProperties.EverythingBooleanBooleanProp;
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
Expand All @@ -1953,7 +1962,6 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder
return self;
}
_arrayProp = [aDecoder decodeObjectOfClass:[NSArray class] forKey:@"array_prop"];
_booleanProp = [aDecoder decodeBoolForKey:@"boolean_prop"];
_charEnum = (EverythingCharEnum)[aDecoder decodeIntegerForKey:@"char_enum"];
_dateProp = [aDecoder decodeObjectOfClass:[NSDate class] forKey:@"date_prop"];
_intEnum = (EverythingIntEnum)[aDecoder decodeIntegerForKey:@"int_enum"];
Expand Down Expand Up @@ -1988,6 +1996,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder
_unsignedIntEnum = (EverythingUnsignedIntEnum)[aDecoder decodeIntegerForKey:@"unsigned_int_enum"];
_unsignedShortEnum = (EverythingUnsignedShortEnum)[aDecoder decodeIntegerForKey:@"unsigned_short_enum"];
_uriProp = [aDecoder decodeObjectOfClass:[NSURL class] forKey:@"uri_prop"];
_everythingBooleanProperties.EverythingBooleanBooleanProp = [aDecoder decodeBoolForKey:@"boolean_prop"] & 0x1;
_everythingDirtyProperties.EverythingDirtyPropertyArrayProp = [aDecoder decodeIntForKey:@"array_prop_dirty_property"] & 0x1;
_everythingDirtyProperties.EverythingDirtyPropertyBooleanProp = [aDecoder decodeIntForKey:@"boolean_prop_dirty_property"] & 0x1;
_everythingDirtyProperties.EverythingDirtyPropertyCharEnum = [aDecoder decodeIntForKey:@"char_enum_dirty_property"] & 0x1;
Expand Down
1 change: 1 addition & 0 deletions Examples/Cocoa/Sources/Objective_C/include/Everything.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isUnsignedIntEnumSet;
- (BOOL)isUnsignedShortEnumSet;
- (BOOL)isUriPropSet;
- (BOOL)booleanProp;
@end

@interface EverythingBuilder : NSObject
Expand Down
139 changes: 86 additions & 53 deletions Sources/Core/ObjCModelRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,18 @@ public struct ObjCModelRenderer: ObjCFileRenderer {
return "\(className)DirtyProperties"
}

var booleanPropertiesStructName: String {
return "\(className)BooleanProperties"
}

var dirtyPropertiesIVarName: String {
return "\(Languages.objectiveC.snakeCaseToPropertyName(rootSchema.name))DirtyProperties"
}

var booleanPropertiesIVarName: String {
return "\(Languages.objectiveC.snakeCaseToPropertyName(rootSchema.name))BooleanProperties"
}

// MARK: Model methods

func renderClassName() -> ObjCIR.Method {
Expand Down Expand Up @@ -119,7 +127,30 @@ public struct ObjCModelRenderer: ObjCFileRenderer {
}
}

func renderBooleanPropertyAccessorMethods() -> [ObjCIR.Method] {
return properties.flatMap { (param, prop) -> [ObjCIR.Method] in
switch prop.schema {
case .boolean:
return [ObjCIR.method("- (BOOL)\(Languages.objectiveC.snakeCaseToPropertyName(param))") { ["return _\(self.booleanPropertiesIVarName).\(booleanPropertyOption(propertyName: param, className: self.className));"] }]
default:
return []
}
}
}

func renderRoots() -> [ObjCIR.Root] {
let booleanProperties = rootSchema.properties.filter { (arg) -> Bool in
let (_, schema) = arg
return schema.schema.isBoolean()
}

let booleanStructDeclaration = !booleanProperties.isEmpty ? [ObjCIR.Root.structDecl(name: self.booleanPropertiesStructName,
fields: booleanProperties.keys.map { "unsigned int \(booleanPropertyOption(propertyName: $0, className: self.className)):1;" })] : []

let booleanIvarDeclaration: [SimpleProperty] = !booleanProperties.isEmpty ? [(self.booleanPropertiesIVarName, "struct \(self.booleanPropertiesStructName)",
SchemaObjectProperty(schema: .integer, nullability: nil),
.readwrite)] : []

let enumProperties = properties.filter { (arg) -> Bool in
let (_, schema) = arg
switch schema.schema {
Expand All @@ -129,6 +160,7 @@ public struct ObjCModelRenderer: ObjCFileRenderer {
return false
}
}

let enumIVarDeclarations: [(Parameter, TypeName)] = enumProperties.compactMap { (arg) -> (Parameter, TypeName) in
let (param, prop) = arg
return (param, enumTypeName(propertyName: param, className: self.className))
Expand Down Expand Up @@ -199,59 +231,60 @@ public struct ObjCModelRenderer: ObjCFileRenderer {
ObjCIR.Root.structDecl(name: self.dirtyPropertyOptionName,
fields: rootSchema.properties.keys
.map { "unsigned int \(dirtyPropertyOption(propertyName: $0, className: self.className)):1;" }),
ObjCIR.Root.category(className: self.className,
categoryName: nil,
methods: [],
properties: [(self.dirtyPropertiesIVarName, "struct \(self.dirtyPropertyOptionName)",
SchemaObjectProperty(schema: .integer, nullability: nil),
.readwrite)],
variables: enumIVarDeclarations),
ObjCIR.Root.category(className: self.builderClassName,
categoryName: nil,
methods: [],
properties: [(self.dirtyPropertiesIVarName, "struct \(self.dirtyPropertyOptionName)",
SchemaObjectProperty(schema: .integer, nullability: nil),
.readwrite)],
variables: []),
] + renderStringEnumerationMethods().map { ObjCIR.Root.function($0) } + [
ObjCIR.Root.macro("NS_ASSUME_NONNULL_BEGIN"),
ObjCIR.Root.classDecl(
name: self.className,
extends: parentName,
methods: [
(self.isBaseClass ? .publicM : .privateM, self.renderClassName()),
(self.isBaseClass ? .publicM : .privateM, self.renderPolymorphicTypeIdentifier()),
(self.isBaseClass ? .publicM : .privateM, self.renderModelObjectWithDictionary()),
(.privateM, self.renderDesignatedInit()),
(self.isBaseClass ? .publicM : .privateM, self.renderInitWithModelDictionary()),
(.publicM, self.renderInitWithBuilder()),
(self.isBaseClass ? .publicM : .privateM, self.renderInitWithBuilderWithInitType()),
(.privateM, self.renderDebugDescription()),
(.publicM, self.renderCopyWithBlock()),
(.privateM, self.renderIsEqual()),
(.publicM, self.renderIsEqualToClass()),
(.privateM, self.renderHash()),
(.publicM, self.renderMergeWithModel()),
(.publicM, self.renderMergeWithModelWithInitType()),
(self.isBaseClass ? .publicM : .privateM, self.renderGenerateDictionary()),
] + self.renderIsSetMethods().map { (.publicM, $0) },
properties: properties.map { param, prop in (param, typeFromSchema(param, prop), prop, .readonly) }.sorted { $0.0 < $1.0 },
protocols: protocols
),
ObjCIR.Root.classDecl(
name: self.builderClassName,
extends: resolveClassName(self.parentDescriptor).map { "\($0)Builder" },
methods: [
(.publicM, self.renderBuilderInitWithModel()),
(.publicM, ObjCIR.method("- (\(self.className) *)build") {
["return [[\(self.className) alloc] initWithBuilder:self];"]
}),
(.publicM, self.renderBuilderMergeWithModel()),
] + self.renderBuilderPropertySetters().map { (.privateM, $0) },
properties: properties.map { param, prop in (param, typeFromSchema(param, prop), prop, .readwrite) },
protocols: [:]
),
ObjCIR.Root.macro("NS_ASSUME_NONNULL_END"),
]
+ booleanStructDeclaration +
[ObjCIR.Root.category(className: self.className,
categoryName: nil,
methods: [],
properties: [(self.dirtyPropertiesIVarName, "struct \(self.dirtyPropertyOptionName)",
SchemaObjectProperty(schema: .integer, nullability: nil),
.readwrite)] + booleanIvarDeclaration,
variables: enumIVarDeclarations),
ObjCIR.Root.category(className: self.builderClassName,
categoryName: nil,
methods: [],
properties: [(self.dirtyPropertiesIVarName, "struct \(self.dirtyPropertyOptionName)",
SchemaObjectProperty(schema: .integer, nullability: nil),
.readwrite)],
variables: [])] + renderStringEnumerationMethods().map { ObjCIR.Root.function($0) } + [
ObjCIR.Root.macro("NS_ASSUME_NONNULL_BEGIN"),
ObjCIR.Root.classDecl(
name: self.className,
extends: parentName,
methods: [
(self.isBaseClass ? .publicM : .privateM, self.renderClassName()),
(self.isBaseClass ? .publicM : .privateM, self.renderPolymorphicTypeIdentifier()),
(self.isBaseClass ? .publicM : .privateM, self.renderModelObjectWithDictionary()),
(.privateM, self.renderDesignatedInit()),
(self.isBaseClass ? .publicM : .privateM, self.renderInitWithModelDictionary()),
(.publicM, self.renderInitWithBuilder()),
(self.isBaseClass ? .publicM : .privateM, self.renderInitWithBuilderWithInitType()),
(.privateM, self.renderDebugDescription()),
(.publicM, self.renderCopyWithBlock()),
(.privateM, self.renderIsEqual()),
(.publicM, self.renderIsEqualToClass(self.booleanPropertiesIVarName)),
(.privateM, self.renderHash(self.booleanPropertiesIVarName)),
(.publicM, self.renderMergeWithModel()),
(.publicM, self.renderMergeWithModelWithInitType()),
(self.isBaseClass ? .publicM : .privateM, self.renderGenerateDictionary()),
] + self.renderIsSetMethods().map { (.publicM, $0) } + self.renderBooleanPropertyAccessorMethods().map { (.publicM, $0) },
properties: properties.map { param, prop in (param, typeFromSchema(param, prop), prop, .readonly) }.sorted { $0.0 < $1.0 },
protocols: protocols
),
ObjCIR.Root.classDecl(
name: self.builderClassName,
extends: resolveClassName(self.parentDescriptor).map { "\($0)Builder" },
methods: [
(.publicM, self.renderBuilderInitWithModel()),
(.publicM, ObjCIR.method("- (\(self.className) *)build") {
["return [[\(self.className) alloc] initWithBuilder:self];"]
}),
(.publicM, self.renderBuilderMergeWithModel()),
] + self.renderBuilderPropertySetters().map { (.privateM, $0) },
properties: properties.map { param, prop in (param, typeFromSchema(param, prop), prop, .readwrite) },
protocols: [:]
),
ObjCIR.Root.macro("NS_ASSUME_NONNULL_END"),
]
}
}
Loading

0 comments on commit d4a714d

Please sign in to comment.