OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2016 The Chromium Authors. All rights reserved. | 2 # Copyright 2016 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 import math | 6 import math |
7 import sys | 7 import sys |
8 | 8 |
9 import json5_generator | 9 import json5_generator |
10 import template_expander | 10 import template_expander |
11 import make_style_builder | 11 import make_style_builder |
12 | 12 |
13 from name_utilities import ( | 13 from name_utilities import ( |
14 enum_for_css_keyword, enum_type_name, enum_value_name, class_member_name, me
thod_name, | 14 enum_for_css_keyword, enum_type_name, enum_value_name, class_member_name, me
thod_name, |
15 join_name | 15 class_name, join_name |
16 ) | 16 ) |
17 from collections import OrderedDict | 17 from collections import defaultdict, OrderedDict |
| 18 from itertools import chain |
| 19 |
| 20 # TODO(shend): Improve documentation and add docstrings. |
| 21 |
| 22 |
| 23 def _flatten_list(x): |
| 24 """Flattens a list of lists into a single list.""" |
| 25 return list(chain.from_iterable(x)) |
| 26 |
| 27 |
| 28 def _num_32_bit_words_for_bit_fields(bit_fields): |
| 29 """Gets the number of 32 bit unsigned integers needed store a list of bit fi
elds.""" |
| 30 num_buckets, cur_bucket = 0, 0 |
| 31 for field in bit_fields: |
| 32 if field.size + cur_bucket > 32: |
| 33 num_buckets += 1 |
| 34 cur_bucket = 0 |
| 35 cur_bucket += field.size |
| 36 return num_buckets + (cur_bucket > 0) |
| 37 |
| 38 |
| 39 class Group(object): |
| 40 """Represents a group of fields stored together in a class. |
| 41 |
| 42 Attributes: |
| 43 name: The name of the group as a string. |
| 44 subgroups: List of Group instances that are stored as subgroups under th
is group. |
| 45 fields: List of Field instances stored directly under this group. |
| 46 """ |
| 47 def __init__(self, name, subgroups, fields): |
| 48 self.name = name |
| 49 self.subgroups = subgroups |
| 50 self.fields = fields |
| 51 self.type_name = class_name(join_name('style', name, 'data')) |
| 52 self.member_name = class_member_name(join_name(name, 'data')) |
| 53 self.num_32_bit_words_for_bit_fields = _num_32_bit_words_for_bit_fields( |
| 54 field for field in fields if field.is_bit_field |
| 55 ) |
| 56 |
| 57 # Recursively get all the fields in the subgroups as well |
| 58 self.all_fields = _flatten_list(subgroup.all_fields for subgroup in subg
roups) + fields |
18 | 59 |
19 | 60 |
20 class Field(object): | 61 class Field(object): |
21 """ | 62 """ |
22 The generated ComputedStyle object is made up of a series of Fields. | 63 The generated ComputedStyle object is made up of a series of Fields. |
23 Each Field has a name, size, type, etc, and a bunch of attributes to | 64 Each Field has a name, size, type, etc, and a bunch of attributes to |
24 determine which methods it will be used in. | 65 determine which methods it will be used in. |
25 | 66 |
26 A Field also has enough information to use any storage type in C++, such as | 67 A Field also has enough information to use any storage type in C++, such as |
27 regular member variables, or more complex storage like vectors or hashmaps. | 68 regular member variables, or more complex storage like vectors or hashmaps. |
28 Almost all properties will have at least one Field, often more than one. | 69 Almost all properties will have at least one Field, often more than one. |
29 | 70 |
30 Most attributes in this class correspond to parameters in CSSProperties.json
5. | 71 Most attributes in this class correspond to parameters in CSSProperties.json
5. |
31 See that file for a more detailed explanation of each attribute. | 72 See that file for a more detailed explanation of each attribute. |
32 | 73 |
33 Attributes: | 74 Attributes: |
34 field_role: The semantic role of the field. Can be: | 75 field_role: The semantic role of the field. Can be: |
35 - 'property': for fields that store CSS properties | 76 - 'property': for fields that store CSS properties |
36 - 'inherited_flag': for single-bit flags that store whether a proper
ty is | 77 - 'inherited_flag': for single-bit flags that store whether a proper
ty is |
37 inherited by this style or set explicitly | 78 inherited by this style or set explicitly |
38 - 'nonproperty': for fields that are not CSS properties | 79 - 'nonproperty': for fields that are not CSS properties |
39 name_for_methods: String used to form the names of getters and setters. | 80 name_for_methods: String used to form the names of getters and setters. |
40 Should be in upper camel case. | 81 Should be in upper camel case. |
41 property_name: Name of the property that the field is part of. | 82 property_name: Name of the property that the field is part of. |
42 type_name: Name of the C++ type exposed by the generated interface (e.g.
EClear, int). | 83 type_name: Name of the C++ type exposed by the generated interface (e.g.
EClear, int). |
43 field_template: Determines the interface generated for the field. Can be
one of: | 84 field_template: Determines the interface generated for the field. Can be
one of: |
44 keyword, flag, or monotonic_flag. | 85 keyword, flag, or monotonic_flag. |
| 86 field_group: The name of the group that this field is inside. |
45 size: Number of bits needed for storage. | 87 size: Number of bits needed for storage. |
46 default_value: Default value for this field when it is first initialized
. | 88 default_value: Default value for this field when it is first initialized
. |
47 """ | 89 """ |
48 | 90 |
49 def __init__(self, field_role, name_for_methods, property_name, type_name, | 91 def __init__(self, field_role, name_for_methods, property_name, type_name, |
50 field_template, size, default_value, getter_method_name, setter
_method_name, | 92 field_template, field_group, size, default_value, |
51 initial_method_name, **kwargs): | 93 getter_method_name, setter_method_name, initial_method_name, **
kwargs): |
52 """Creates a new field.""" | 94 """Creates a new field.""" |
53 self.name = class_member_name(name_for_methods) | 95 self.name = class_member_name(name_for_methods) |
54 self.property_name = property_name | 96 self.property_name = property_name |
55 self.type_name = type_name | 97 self.type_name = type_name |
56 self.field_template = field_template | 98 self.field_template = field_template |
| 99 self.group_name = field_group |
| 100 self.group_member_name = class_member_name(join_name(field_group, 'data'
)) if field_group else None |
57 self.size = size | 101 self.size = size |
58 self.default_value = default_value | 102 self.default_value = default_value |
59 | 103 |
60 # Field role: one of these must be true | 104 # Field role: one of these must be true |
61 self.is_property = field_role == 'property' | 105 self.is_property = field_role == 'property' |
62 self.is_inherited_flag = field_role == 'inherited_flag' | 106 self.is_inherited_flag = field_role == 'inherited_flag' |
63 self.is_nonproperty = field_role == 'nonproperty' | 107 self.is_nonproperty = field_role == 'nonproperty' |
64 assert (self.is_property, self.is_inherited_flag, self.is_nonproperty).c
ount(True) == 1, \ | 108 assert (self.is_property, self.is_inherited_flag, self.is_nonproperty).c
ount(True) == 1, \ |
65 'Field role has to be exactly one of: property, inherited_flag, nonp
roperty' | 109 'Field role has to be exactly one of: property, inherited_flag, nonp
roperty' |
66 | 110 |
(...skipping 24 matching lines...) Expand all Loading... |
91 """ | 135 """ |
92 Get a list of paths that need to be included for ComputedStyleBase. | 136 Get a list of paths that need to be included for ComputedStyleBase. |
93 """ | 137 """ |
94 include_paths = set() | 138 include_paths = set() |
95 for property_ in properties: | 139 for property_ in properties: |
96 if property_['field_type_path'] is not None: | 140 if property_['field_type_path'] is not None: |
97 include_paths.add(property_['field_type_path'] + '.h') | 141 include_paths.add(property_['field_type_path'] + '.h') |
98 return list(sorted(include_paths)) | 142 return list(sorted(include_paths)) |
99 | 143 |
100 | 144 |
| 145 def _group_fields(fields): |
| 146 """Groups a list of fields by their group_name and returns the root group.""
" |
| 147 groups = defaultdict(list) |
| 148 for field in fields: |
| 149 groups[field.group_name].append(field) |
| 150 |
| 151 no_group = groups.pop(None) |
| 152 subgroups = [Group(group_name, [], _reorder_fields(fields)) for group_name,
fields in groups.items()] |
| 153 return Group('', subgroups=subgroups, fields=_reorder_fields(no_group)) |
| 154 |
| 155 |
101 def _create_enums(properties): | 156 def _create_enums(properties): |
102 """ | 157 """ |
103 Returns an OrderedDict of enums to be generated, enum name -> [list of enum
values] | 158 Returns an OrderedDict of enums to be generated, enum name -> [list of enum
values] |
104 """ | 159 """ |
105 enums = {} | 160 enums = {} |
106 for property_ in properties: | 161 for property_ in properties: |
107 # Only generate enums for keyword properties that use the default field_
type_path. | 162 # Only generate enums for keyword properties that use the default field_
type_path. |
108 if property_['field_template'] == 'keyword' and property_['field_type_pa
th'] is None: | 163 if property_['field_template'] == 'keyword' and property_['field_type_pa
th'] is None: |
109 enum_name = property_['type_name'] | 164 enum_name = property_['type_name'] |
110 enum_values = [enum_value_name(k) for k in property_['keywords']] | 165 enum_values = [enum_value_name(k) for k in property_['keywords']] |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
158 size = 1 | 213 size = 1 |
159 | 214 |
160 return Field( | 215 return Field( |
161 field_role, | 216 field_role, |
162 name_for_methods, | 217 name_for_methods, |
163 property_name=property_['name'], | 218 property_name=property_['name'], |
164 inherited=property_['inherited'], | 219 inherited=property_['inherited'], |
165 independent=property_['independent'], | 220 independent=property_['independent'], |
166 type_name=type_name, | 221 type_name=type_name, |
167 field_template=property_['field_template'], | 222 field_template=property_['field_template'], |
| 223 field_group=property_['field_group'], |
168 size=size, | 224 size=size, |
169 default_value=default_value, | 225 default_value=default_value, |
170 getter_method_name=property_['getter'], | 226 getter_method_name=property_['getter'], |
171 setter_method_name=property_['setter'], | 227 setter_method_name=property_['setter'], |
172 initial_method_name=property_['initial'], | 228 initial_method_name=property_['initial'], |
173 ) | 229 ) |
174 | 230 |
175 | 231 |
176 def _create_inherited_flag_field(property_): | 232 def _create_inherited_flag_field(property_): |
177 """ | 233 """ |
178 Create the field used for an inheritance fast path from an independent CSS p
roperty, | 234 Create the field used for an inheritance fast path from an independent CSS p
roperty, |
179 and return the Field object. | 235 and return the Field object. |
180 """ | 236 """ |
181 name_for_methods = join_name(property_['name_for_methods'], 'is inherited') | 237 name_for_methods = join_name(property_['name_for_methods'], 'is inherited') |
182 return Field( | 238 return Field( |
183 'inherited_flag', | 239 'inherited_flag', |
184 name_for_methods, | 240 name_for_methods, |
185 property_name=property_['name'], | 241 property_name=property_['name'], |
186 type_name='bool', | 242 type_name='bool', |
187 field_template='primitive', | 243 field_template='primitive', |
| 244 field_group=property_['field_group'], |
188 size=1, | 245 size=1, |
189 default_value='true', | 246 default_value='true', |
190 getter_method_name=method_name(name_for_methods), | 247 getter_method_name=method_name(name_for_methods), |
191 setter_method_name=method_name(join_name('set', name_for_methods)), | 248 setter_method_name=method_name(join_name('set', name_for_methods)), |
192 initial_method_name=method_name(join_name('initial', name_for_methods)), | 249 initial_method_name=method_name(join_name('initial', name_for_methods)), |
193 ) | 250 ) |
194 | 251 |
195 | 252 |
196 def _create_fields(properties): | 253 def _create_fields(properties): |
197 """ | 254 """ |
198 Create ComputedStyle fields from properties or nonproperties and return a li
st of Field objects. | 255 Create ComputedStyle fields from properties or nonproperties and return a li
st of Field objects. |
199 """ | 256 """ |
200 fields = [] | 257 fields = [] |
201 for property_ in properties: | 258 for property_ in properties: |
202 # Only generate properties that have a field template | 259 # Only generate properties that have a field template |
203 if property_['field_template'] is not None: | 260 if property_['field_template'] is not None: |
204 # If the property is independent, add the single-bit sized isInherit
ed flag | 261 # If the property is independent, add the single-bit sized isInherit
ed flag |
205 # to the list of Fields as well. | 262 # to the list of Fields as well. |
206 if property_['independent']: | 263 if property_['independent']: |
207 fields.append(_create_inherited_flag_field(property_)) | 264 fields.append(_create_inherited_flag_field(property_)) |
208 | 265 |
209 # TODO(shend): Get rid of the property/nonproperty field roles. | 266 # TODO(shend): Get rid of the property/nonproperty field roles. |
| 267 # If the field has_custom_compare_and_copy, then it does not appear
in |
| 268 # ComputedStyle::operator== and ComputedStyle::CopyNonInheritedFromC
ached. |
210 field_role = 'nonproperty' if property_['has_custom_compare_and_copy
'] else 'property' | 269 field_role = 'nonproperty' if property_['has_custom_compare_and_copy
'] else 'property' |
211 fields.append(_create_field(field_role, property_)) | 270 fields.append(_create_field(field_role, property_)) |
212 | 271 |
213 return fields | 272 return fields |
214 | 273 |
215 | 274 |
216 def _pack_fields(fields): | 275 def _reorder_fields(fields): |
217 """ | 276 """ |
218 Group a list of fields into buckets to minimise padding. | 277 Returns a list of fields ordered to minimise padding. |
219 Returns a list of buckets, where each bucket is a list of Field objects. | |
220 """ | 278 """ |
| 279 # Separate out bit fields from non bit fields |
| 280 bit_fields = [field for field in fields if field.is_bit_field] |
| 281 non_bit_fields = [field for field in fields if not field.is_bit_field] |
| 282 |
221 # Since fields cannot cross word boundaries, in order to minimize | 283 # Since fields cannot cross word boundaries, in order to minimize |
222 # padding, group fields into buckets so that as many buckets as possible | 284 # padding, group fields into buckets so that as many buckets as possible |
223 # are exactly 32 bits. Although this greedy approach may not always | 285 # are exactly 32 bits. Although this greedy approach may not always |
224 # produce the optimal solution, we add a static_assert to the code to | 286 # produce the optimal solution, we add a static_assert to the code to |
225 # ensure ComputedStyleBase results in the expected size. If that | 287 # ensure ComputedStyleBase results in the expected size. If that |
226 # static_assert fails, this code is falling into the small number of | 288 # static_assert fails, this code is falling into the small number of |
227 # cases that are suboptimal, and may need to be rethought. | 289 # cases that are suboptimal, and may need to be rethought. |
228 # For more details on packing bit fields to reduce padding, see: | 290 # For more details on packing bit fields to reduce padding, see: |
229 # http://www.catb.org/esr/structure-packing/#_bitfields | 291 # http://www.catb.org/esr/structure-packing/#_bitfields |
230 field_buckets = [] | 292 field_buckets = [] |
231 # Consider fields in descending order of size to reduce fragmentation | 293 # Consider fields in descending order of size to reduce fragmentation |
232 # when they are selected. Ties broken in alphabetical order by name. | 294 # when they are selected. Ties broken in alphabetical order by name. |
233 for field in sorted(fields, key=lambda f: (-f.size, f.name)): | 295 for field in sorted(bit_fields, key=lambda f: (-f.size, f.name)): |
234 added_to_bucket = False | 296 added_to_bucket = False |
235 # Go through each bucket and add this field if it will not increase | 297 # Go through each bucket and add this field if it will not increase |
236 # the bucket's size to larger than 32 bits. Otherwise, make a new | 298 # the bucket's size to larger than 32 bits. Otherwise, make a new |
237 # bucket containing only this field. | 299 # bucket containing only this field. |
238 for bucket in field_buckets: | 300 for bucket in field_buckets: |
239 if sum(f.size for f in bucket) + field.size <= 32: | 301 if sum(f.size for f in bucket) + field.size <= 32: |
240 bucket.append(field) | 302 bucket.append(field) |
241 added_to_bucket = True | 303 added_to_bucket = True |
242 break | 304 break |
243 if not added_to_bucket: | 305 if not added_to_bucket: |
244 field_buckets.append([field]) | 306 field_buckets.append([field]) |
245 | 307 |
246 return field_buckets | 308 # Non bit fields go first, then the bit fields. |
| 309 return list(non_bit_fields) + _flatten_list(field_buckets) |
247 | 310 |
248 | 311 |
249 class ComputedStyleBaseWriter(make_style_builder.StyleBuilderWriter): | 312 class ComputedStyleBaseWriter(make_style_builder.StyleBuilderWriter): |
250 def __init__(self, json5_file_paths): | 313 def __init__(self, json5_file_paths): |
251 # Read CSS properties | 314 # Read CSS properties |
252 super(ComputedStyleBaseWriter, self).__init__([json5_file_paths[0]]) | 315 super(ComputedStyleBaseWriter, self).__init__([json5_file_paths[0]]) |
253 | 316 |
254 # Ignore shorthand properties | 317 # Ignore shorthand properties |
255 for property_ in self._properties.values(): | 318 for property_ in self._properties.values(): |
256 if property_['field_template'] is not None: | 319 if property_['field_template'] is not None: |
(...skipping 21 matching lines...) Expand all Loading... |
278 | 341 |
279 # Override the type name when field_type_path is specified | 342 # Override the type name when field_type_path is specified |
280 for property_ in all_properties: | 343 for property_ in all_properties: |
281 if property_['field_type_path']: | 344 if property_['field_type_path']: |
282 property_['type_name'] = property_['field_type_path'].split('/')
[-1] | 345 property_['type_name'] = property_['field_type_path'].split('/')
[-1] |
283 | 346 |
284 self._generated_enums = _create_enums(all_properties) | 347 self._generated_enums = _create_enums(all_properties) |
285 | 348 |
286 all_fields = _create_fields(all_properties) | 349 all_fields = _create_fields(all_properties) |
287 | 350 |
288 # Separate the normal fields from the bit fields | 351 # Organise fields into a tree structure where the root group |
289 bit_fields = [field for field in all_fields if field.is_bit_field] | 352 # is ComputedStyleBase. |
290 normal_fields = [field for field in all_fields if not field.is_bit_field
] | 353 self._root_group = _group_fields(all_fields) |
291 | |
292 # Pack bit fields into buckets | |
293 field_buckets = _pack_fields(bit_fields) | |
294 | |
295 # The expected size of ComputedStyleBase is equivalent to as many words | |
296 # as the total number of buckets. | |
297 self._expected_bit_field_bytes = len(field_buckets) | |
298 | |
299 # The most optimal size of ComputedStyleBase is the total sum of all the | |
300 # field sizes, rounded up to the nearest word. If this produces the | |
301 # incorrect value, either the packing algorithm is not optimal or there | |
302 # is no way to pack the fields such that excess padding space is not | |
303 # added. | |
304 # If this fails, increase extra_padding_bytes by 1, but be aware that | |
305 # this also increases ComputedStyleBase by 1 word. | |
306 # We should be able to bring extra_padding_bytes back to 0 from time to | |
307 # time. | |
308 extra_padding_bytes = 0 | |
309 optimal_bit_field_bytes = int(math.ceil(sum(f.size for f in bit_fields)
/ 32.0)) | |
310 real_bit_field_bytes = optimal_bit_field_bytes + extra_padding_bytes | |
311 assert self._expected_bit_field_bytes == real_bit_field_bytes, \ | |
312 ('The field packing algorithm produced %s bytes, optimal is %s bytes
' % | |
313 (self._expected_bit_field_bytes, real_bit_field_bytes)) | |
314 | |
315 # Normal fields go first, then the bit fields. | |
316 self._fields = list(normal_fields) | |
317 | |
318 # Order the fields so fields in each bucket are adjacent. | |
319 for bucket in field_buckets: | |
320 for field in bucket: | |
321 self._fields.append(field) | |
322 | 354 |
323 self._include_paths = _get_include_paths(all_properties) | 355 self._include_paths = _get_include_paths(all_properties) |
324 self._outputs = { | 356 self._outputs = { |
325 'ComputedStyleBase.h': self.generate_base_computed_style_h, | 357 'ComputedStyleBase.h': self.generate_base_computed_style_h, |
326 'ComputedStyleBase.cpp': self.generate_base_computed_style_cpp, | 358 'ComputedStyleBase.cpp': self.generate_base_computed_style_cpp, |
327 'ComputedStyleBaseConstants.h': self.generate_base_computed_style_co
nstants, | 359 'ComputedStyleBaseConstants.h': self.generate_base_computed_style_co
nstants, |
328 } | 360 } |
329 | 361 |
330 @template_expander.use_jinja('ComputedStyleBase.h.tmpl') | 362 @template_expander.use_jinja('ComputedStyleBase.h.tmpl') |
331 def generate_base_computed_style_h(self): | 363 def generate_base_computed_style_h(self): |
332 return { | 364 return { |
333 'properties': self._properties, | 365 'properties': self._properties, |
334 'enums': self._generated_enums, | 366 'enums': self._generated_enums, |
335 'include_paths': self._include_paths, | 367 'include_paths': self._include_paths, |
336 'fields': self._fields, | 368 'computed_style': self._root_group, |
337 } | 369 } |
338 | 370 |
339 @template_expander.use_jinja('ComputedStyleBase.cpp.tmpl') | 371 @template_expander.use_jinja('ComputedStyleBase.cpp.tmpl') |
340 def generate_base_computed_style_cpp(self): | 372 def generate_base_computed_style_cpp(self): |
341 return { | 373 return { |
342 'properties': self._properties, | 374 'properties': self._properties, |
343 'enums': self._generated_enums, | 375 'enums': self._generated_enums, |
344 'fields': self._fields, | 376 'computed_style': self._root_group, |
345 'expected_bit_field_bytes': self._expected_bit_field_bytes, | |
346 } | 377 } |
347 | 378 |
348 @template_expander.use_jinja('ComputedStyleBaseConstants.h.tmpl') | 379 @template_expander.use_jinja('ComputedStyleBaseConstants.h.tmpl') |
349 def generate_base_computed_style_constants(self): | 380 def generate_base_computed_style_constants(self): |
350 return { | 381 return { |
351 'properties': self._properties, | 382 'properties': self._properties, |
352 'enums': self._generated_enums, | 383 'enums': self._generated_enums, |
353 'fields': self._fields, | |
354 } | 384 } |
355 | 385 |
356 if __name__ == '__main__': | 386 if __name__ == '__main__': |
357 json5_generator.Maker(ComputedStyleBaseWriter).main() | 387 json5_generator.Maker(ComputedStyleBaseWriter).main() |
OLD | NEW |