Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(748)

Side by Side Diff: third_party/WebKit/Source/build/scripts/make_computed_style_base.py

Issue 2786883002: Generate subgroup StyleSurroundData in ComputedStyle. (Closed)
Patch Set: Rebase Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
18 19
19 20
20 # Temporary hard-coded list of fields that are not CSS properties. 21 # Temporary hard-coded list of fields that are not CSS properties.
21 # TODO(shend): Put this into its own JSON5 file. 22 # TODO(shend): Put this into its own JSON5 file.
22 NONPROPERTIES = [ 23 NONPROPERTIES = [
23 {'name': 'IsLink', 'field_template': 'monotonic_flag', 24 {'name': 'IsLink', 'field_template': 'monotonic_flag',
24 'inherited': False, 'independent': False, 'default_value': False}, 25 'inherited': False, 'independent': False, 'default_value': False},
25 {'name': 'OriginalDisplay', 'field_template': 'keyword', 'default_value': 'i nline', 26 {'name': 'OriginalDisplay', 'field_template': 'keyword', 'default_value': 'i nline',
26 'type_name': 'EDisplay', 'inherited': False, 'independent': False, 27 'type_name': 'EDisplay', 'inherited': False, 'independent': False,
27 'keywords': [ 28 'keywords': [
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
62 {'name': 'HasSimpleUnderline', 'field_template': 'storage_only', 'field_size ': 1, 'default_value': 'false', 63 {'name': 'HasSimpleUnderline', 'field_template': 'storage_only', 'field_size ': 1, 'default_value': 'false',
63 'type_name': 'bool', 'inherited': True, 'independent': False}, 64 'type_name': 'bool', 'inherited': True, 'independent': False},
64 # TODO(shend): vertical align is actually a CSS property, but since we don't support union fields 65 # TODO(shend): vertical align is actually a CSS property, but since we don't support union fields
65 # which can be either a keyword or Length, this is generated as a nonpropert y for now. Remove this 66 # which can be either a keyword or Length, this is generated as a nonpropert y for now. Remove this
66 # once we can support union fields and groups. 67 # once we can support union fields and groups.
67 {'name': 'VerticalAlign', 'field_template': 'storage_only', 'field_size': 4, 'default_value': 'EVerticalAlign::kBaseline', 68 {'name': 'VerticalAlign', 'field_template': 'storage_only', 'field_size': 4, 'default_value': 'EVerticalAlign::kBaseline',
68 'type_name': 'EVerticalAlign', 'inherited': False, 'independent': False}, 69 'type_name': 'EVerticalAlign', 'inherited': False, 'independent': False},
69 ] 70 ]
70 71
71 72
73 def _flatten_list(x):
74 """Flattens a list of lists into a single list."""
75 return list(chain.from_iterable(x))
76
77
78 def _num_bit_field_buckets(bit_fields):
meade_UTC10 2017/04/06 03:29:57 would this be better as something like _num_32_bi
shend 2017/04/06 04:39:39 Done.
79 """Gets the number of 32 bit unsigned integers needed store a list of bit fi elds."""
80 num_buckets, cur_bucket = 0, 0
81 for field in bit_fields:
82 if field.size + cur_bucket > 32:
83 num_buckets += 1
84 cur_bucket = 0
85 cur_bucket += field.size
86 return num_buckets + (cur_bucket > 0)
87
88
89 class Group(object):
90 def __init__(self, name, subgroups, fields):
91 """Represents a group of fields, which may contain subgroups that are dy namically allocated."""
meade_UTC10 2017/04/06 03:29:57 Could you please document the types of the paramet
shend 2017/04/06 04:39:39 Agreed, the documentation in this file could be si
92 self.name = name
93 self.subgroups = subgroups
94 self.fields = fields
95 self.type_name = class_name(join_name('style', name, ' data'))
96 self.member_name = class_member_name(name)
97 self.num_bit_field_buckets = _num_bit_field_buckets(field for field in f ields if field.is_bit_field)
98
99 # Recursively get all the fields in the subgroups as well
100 self.all_fields = _flatten_list(subgroup.all_fields for subgroup in subg roups) + fields
101
102
72 class Field(object): 103 class Field(object):
73 """ 104 """
74 The generated ComputedStyle object is made up of a series of Fields. 105 The generated ComputedStyle object is made up of a series of Fields.
75 Each Field has a name, size, type, etc, and a bunch of attributes to 106 Each Field has a name, size, type, etc, and a bunch of attributes to
76 determine which methods it will be used in. 107 determine which methods it will be used in.
77 108
78 A Field also has enough information to use any storage type in C++, such as 109 A Field also has enough information to use any storage type in C++, such as
79 regular member variables, or more complex storage like vectors or hashmaps. 110 regular member variables, or more complex storage like vectors or hashmaps.
80 Almost all properties will have at least one Field, often more than one. 111 Almost all properties will have at least one Field, often more than one.
81 112
(...skipping 10 matching lines...) Expand all
92 Should be in upper camel case. 123 Should be in upper camel case.
93 property_name: Name of the property that the field is part of. 124 property_name: Name of the property that the field is part of.
94 type_name: Name of the C++ type exposed by the generated interface (e.g. EClear, int). 125 type_name: Name of the C++ type exposed by the generated interface (e.g. EClear, int).
95 field_template: Determines the interface generated for the field. Can be one of: 126 field_template: Determines the interface generated for the field. Can be one of:
96 keyword, flag, or monotonic_flag. 127 keyword, flag, or monotonic_flag.
97 size: Number of bits needed for storage. 128 size: Number of bits needed for storage.
98 default_value: Default value for this field when it is first initialized . 129 default_value: Default value for this field when it is first initialized .
99 """ 130 """
100 131
101 def __init__(self, field_role, name_for_methods, property_name, type_name, 132 def __init__(self, field_role, name_for_methods, property_name, type_name,
102 field_template, size, default_value, **kwargs): 133 field_template, field_group, size, default_value, **kwargs):
103 """Creates a new field.""" 134 """Creates a new field."""
104 self.name = class_member_name(name_for_methods) 135 self.name = class_member_name(name_for_methods)
105 self.property_name = property_name 136 self.property_name = property_name
106 self.type_name = type_name 137 self.type_name = type_name
107 self.field_template = field_template 138 self.field_template = field_template
139 self.field_group = field_group
108 self.size = size 140 self.size = size
109 self.default_value = default_value 141 self.default_value = default_value
110 142
111 # Field role: one of these must be true 143 # Field role: one of these must be true
112 self.is_property = field_role == 'property' 144 self.is_property = field_role == 'property'
113 self.is_inherited_flag = field_role == 'inherited_flag' 145 self.is_inherited_flag = field_role == 'inherited_flag'
114 self.is_nonproperty = field_role == 'nonproperty' 146 self.is_nonproperty = field_role == 'nonproperty'
115 assert (self.is_property, self.is_inherited_flag, self.is_nonproperty).c ount(True) == 1, \ 147 assert (self.is_property, self.is_inherited_flag, self.is_nonproperty).c ount(True) == 1, \
116 'Field role has to be exactly one of: property, inherited_flag, nonp roperty' 148 'Field role has to be exactly one of: property, inherited_flag, nonp roperty'
117 149
(...skipping 21 matching lines...) Expand all
139 """ 171 """
140 Get a list of paths that need to be included for ComputedStyleBase. 172 Get a list of paths that need to be included for ComputedStyleBase.
141 """ 173 """
142 include_paths = set() 174 include_paths = set()
143 for property_ in properties: 175 for property_ in properties:
144 if property_['field_type_path'] is not None: 176 if property_['field_type_path'] is not None:
145 include_paths.add(property_['field_type_path'] + '.h') 177 include_paths.add(property_['field_type_path'] + '.h')
146 return list(sorted(include_paths)) 178 return list(sorted(include_paths))
147 179
148 180
181 def _group_fields(fields):
182 """Groups a list of fields by their field_group and returns the root group." ""
183 groups = defaultdict(list)
184 for field in fields:
185 groups[field.field_group].append(field)
186
187 no_group = groups.pop(None)
188 subgroups = [Group(field_group, [], _reorder_fields(fields)) for field_group , fields in groups.items()]
189 return Group('', subgroups=subgroups, fields=_reorder_fields(no_group))
190
191
149 def _create_enums(properties): 192 def _create_enums(properties):
150 """ 193 """
151 Returns an OrderedDict of enums to be generated, enum name -> [list of enum values] 194 Returns an OrderedDict of enums to be generated, enum name -> [list of enum values]
152 """ 195 """
153 enums = {} 196 enums = {}
154 for property_ in properties: 197 for property_ in properties:
155 # Only generate enums for keyword properties that use the default field_ type_path. 198 # Only generate enums for keyword properties that use the default field_ type_path.
156 if property_['field_template'] == 'keyword' and property_['field_type_pa th'] is None: 199 if property_['field_template'] == 'keyword' and property_['field_type_pa th'] is None:
157 enum_name = property_['type_name'] 200 enum_name = property_['type_name']
158 enum_values = [enum_value_name(k) for k in property_['keywords']] 201 enum_values = [enum_value_name(k) for k in property_['keywords']]
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
202 size = 1 245 size = 1
203 246
204 return Field( 247 return Field(
205 field_role, 248 field_role,
206 name_for_methods, 249 name_for_methods,
207 property_name=property_['name'], 250 property_name=property_['name'],
208 inherited=property_['inherited'], 251 inherited=property_['inherited'],
209 independent=property_['independent'], 252 independent=property_['independent'],
210 type_name=type_name, 253 type_name=type_name,
211 field_template=property_['field_template'], 254 field_template=property_['field_template'],
255 field_group=property_['field_group'],
212 size=size, 256 size=size,
213 default_value=default_value, 257 default_value=default_value,
214 ) 258 )
215 259
216 260
217 def _create_inherited_flag_field(property_): 261 def _create_inherited_flag_field(property_):
218 """ 262 """
219 Create the field used for an inheritance fast path from an independent CSS p roperty, 263 Create the field used for an inheritance fast path from an independent CSS p roperty,
220 and return the Field object. 264 and return the Field object.
221 """ 265 """
222 return Field( 266 return Field(
223 'inherited_flag', 267 'inherited_flag',
224 join_name(property_['name_for_methods'], 'is inherited'), 268 join_name(property_['name_for_methods'], 'is inherited'),
225 property_name=property_['name'], 269 property_name=property_['name'],
226 type_name='bool', 270 type_name='bool',
227 field_template='flag', 271 field_template='flag',
272 field_group=property_['field_group'],
228 size=1, 273 size=1,
229 default_value='true', 274 default_value='true',
230 ) 275 )
231 276
232 277
233 def _create_fields(field_role, properties): 278 def _create_fields(field_role, properties):
234 """ 279 """
235 Create ComputedStyle fields from properties or nonproperties and return a li st of Field objects. 280 Create ComputedStyle fields from properties or nonproperties and return a li st of Field objects.
236 """ 281 """
237 fields = [] 282 fields = []
238 for property_ in properties: 283 for property_ in properties:
239 # Only generate properties that have a field template 284 # Only generate properties that have a field template
240 if property_['field_template'] is not None: 285 if property_['field_template'] is not None:
241 # If the property is independent, add the single-bit sized isInherit ed flag 286 # If the property is independent, add the single-bit sized isInherit ed flag
242 # to the list of Fields as well. 287 # to the list of Fields as well.
243 if property_['independent']: 288 if property_['independent']:
244 fields.append(_create_inherited_flag_field(property_)) 289 fields.append(_create_inherited_flag_field(property_))
245 290
246 fields.append(_create_field(field_role, property_)) 291 fields.append(_create_field(field_role, property_))
247 292
248 return fields 293 return fields
249 294
250 295
251 def _pack_fields(fields): 296 def _reorder_fields(fields):
meade_UTC10 2017/04/06 03:29:57 You're still doing packing though? I don't think t
shend 2017/04/06 04:39:39 For me, packing means the compiler "packing" the s
252 """ 297 """
253 Group a list of fields into buckets to minimise padding. 298 Returns a list of fields ordered to minimise padding.
254 Returns a list of buckets, where each bucket is a list of Field objects.
255 """ 299 """
300 # Separate out bit fields from normal fields
301 bit_fields = [field for field in fields if field.is_bit_field]
302 normal_fields = [field for field in fields if not field.is_bit_field]
303
256 # Since fields cannot cross word boundaries, in order to minimize 304 # Since fields cannot cross word boundaries, in order to minimize
257 # padding, group fields into buckets so that as many buckets as possible 305 # padding, group fields into buckets so that as many buckets as possible
258 # are exactly 32 bits. Although this greedy approach may not always 306 # are exactly 32 bits. Although this greedy approach may not always
259 # produce the optimal solution, we add a static_assert to the code to 307 # produce the optimal solution, we add a static_assert to the code to
260 # ensure ComputedStyleBase results in the expected size. If that 308 # ensure ComputedStyleBase results in the expected size. If that
261 # static_assert fails, this code is falling into the small number of 309 # static_assert fails, this code is falling into the small number of
262 # cases that are suboptimal, and may need to be rethought. 310 # cases that are suboptimal, and may need to be rethought.
263 # For more details on packing bit fields to reduce padding, see: 311 # For more details on packing bit fields to reduce padding, see:
264 # http://www.catb.org/esr/structure-packing/#_bitfields 312 # http://www.catb.org/esr/structure-packing/#_bitfields
265 field_buckets = [] 313 field_buckets = []
266 # Consider fields in descending order of size to reduce fragmentation 314 # Consider fields in descending order of size to reduce fragmentation
267 # when they are selected. Ties broken in alphabetical order by name. 315 # when they are selected. Ties broken in alphabetical order by name.
268 for field in sorted(fields, key=lambda f: (-f.size, f.name)): 316 for field in sorted(bit_fields, key=lambda f: (-f.size, f.name)):
269 added_to_bucket = False 317 added_to_bucket = False
270 # Go through each bucket and add this field if it will not increase 318 # Go through each bucket and add this field if it will not increase
271 # the bucket's size to larger than 32 bits. Otherwise, make a new 319 # the bucket's size to larger than 32 bits. Otherwise, make a new
272 # bucket containing only this field. 320 # bucket containing only this field.
273 for bucket in field_buckets: 321 for bucket in field_buckets:
274 if sum(f.size for f in bucket) + field.size <= 32: 322 if sum(f.size for f in bucket) + field.size <= 32:
275 bucket.append(field) 323 bucket.append(field)
276 added_to_bucket = True 324 added_to_bucket = True
277 break 325 break
278 if not added_to_bucket: 326 if not added_to_bucket:
279 field_buckets.append([field]) 327 field_buckets.append([field])
280 328
281 return field_buckets 329 # Normal fields go first, then the bit fields.
330 return list(normal_fields) + _flatten_list(field_buckets)
282 331
283 332
284 class ComputedStyleBaseWriter(make_style_builder.StyleBuilderWriter): 333 class ComputedStyleBaseWriter(make_style_builder.StyleBuilderWriter):
285 def __init__(self, json5_file_path): 334 def __init__(self, json5_file_path):
286 super(ComputedStyleBaseWriter, self).__init__(json5_file_path) 335 super(ComputedStyleBaseWriter, self).__init__(json5_file_path)
287 self._outputs = { 336 self._outputs = {
288 'ComputedStyleBase.h': self.generate_base_computed_style_h, 337 'ComputedStyleBase.h': self.generate_base_computed_style_h,
289 'ComputedStyleBase.cpp': self.generate_base_computed_style_cpp, 338 'ComputedStyleBase.cpp': self.generate_base_computed_style_cpp,
290 'ComputedStyleBaseConstants.h': self.generate_base_computed_style_co nstants, 339 'ComputedStyleBaseConstants.h': self.generate_base_computed_style_co nstants,
291 'CSSValueIDMappingsGenerated.h': self.generate_css_value_mappings, 340 'CSSValueIDMappingsGenerated.h': self.generate_css_value_mappings,
292 } 341 }
293 342
294 # TODO(shend): Remove this once we move NONPROPERTIES to its own JSON fi le, 343 # TODO(shend): Remove this once we move NONPROPERTIES to its own JSON fi le,
295 # since the JSON5 reader will handle missing fields and defaults. 344 # since the JSON5 reader will handle missing fields and defaults.
296 for property_ in NONPROPERTIES: 345 for property_ in NONPROPERTIES:
297 property_['name_for_methods'] = property_['name'] 346 property_['name_for_methods'] = property_['name']
298 if 'field_type_path' not in property_: 347 if 'field_type_path' not in property_:
299 property_['field_type_path'] = None 348 property_['field_type_path'] = None
300 if 'type_name' not in property_: 349 if 'type_name' not in property_:
301 property_['type_name'] = 'E' + enum_type_name(property_['name_fo r_methods']) 350 property_['type_name'] = 'E' + enum_type_name(property_['name_fo r_methods'])
351 if 'field_group' not in property_:
352 property_['field_group'] = None
302 353
303 property_values = self._properties.values() 354 property_values = self._properties.values()
304 355
305 # Override the type name when field_type_path is specified 356 # Override the type name when field_type_path is specified
306 for property_ in property_values: 357 for property_ in property_values:
307 if property_['field_type_path']: 358 if property_['field_type_path']:
308 property_['type_name'] = property_['field_type_path'].split('/') [-1] 359 property_['type_name'] = property_['field_type_path'].split('/') [-1]
309 360
310 self._generated_enums = _create_enums(property_values + NONPROPERTIES) 361 self._generated_enums = _create_enums(property_values + NONPROPERTIES)
311 362
312 all_fields = (_create_fields('property', property_values) + 363 fields = (_create_fields('property', property_values) +
313 _create_fields('nonproperty', NONPROPERTIES)) 364 _create_fields('nonproperty', NONPROPERTIES))
314 365
315 # Separate the normal fields from the bit fields 366 # Separate fields into groups
316 bit_fields = [field for field in all_fields if field.is_bit_field] 367 self._base_group = _group_fields(fields)
317 normal_fields = [field for field in all_fields if not field.is_bit_field ]
318 368
319 # Pack bit fields into buckets 369 # Get include paths
meade_UTC10 2017/04/06 03:29:57 Is this really a helpful comment? :p
shend 2017/04/06 04:39:40 lol, removed.
320 field_buckets = _pack_fields(bit_fields) 370 self._include_paths = _get_include_paths(property_values)
321
322 # The expected size of ComputedStyleBase is equivalent to as many words
323 # as the total number of buckets.
324 self._expected_bit_field_bytes = len(field_buckets)
325
326 # The most optimal size of ComputedStyleBase is the total sum of all the
327 # field sizes, rounded up to the nearest word. If this produces the
328 # incorrect value, either the packing algorithm is not optimal or there
329 # is no way to pack the fields such that excess padding space is not
330 # added.
331 # If this fails, increase extra_padding_bytes by 1, but be aware that
332 # this also increases ComputedStyleBase by 1 word.
333 # We should be able to bring extra_padding_bytes back to 0 from time to
334 # time.
335 extra_padding_bytes = 0
336 optimal_bit_field_bytes = int(math.ceil(sum(f.size for f in bit_fields) / 32.0))
337 real_bit_field_bytes = optimal_bit_field_bytes + extra_padding_bytes
338 assert self._expected_bit_field_bytes == real_bit_field_bytes, \
339 ('The field packing algorithm produced %s bytes, optimal is %s bytes ' %
340 (self._expected_bit_field_bytes, real_bit_field_bytes))
341
342 # Normal fields go first, then the bit fields.
343 self._fields = list(normal_fields)
344
345 # Order the fields so fields in each bucket are adjacent.
346 for bucket in field_buckets:
347 for field in bucket:
348 self._fields.append(field)
349
350 self._include_paths = _get_include_paths(property_values + NONPROPERTIES )
351
352 371
353 @template_expander.use_jinja('ComputedStyleBase.h.tmpl') 372 @template_expander.use_jinja('ComputedStyleBase.h.tmpl')
354 def generate_base_computed_style_h(self): 373 def generate_base_computed_style_h(self):
355 return { 374 return {
356 'properties': self._properties, 375 'properties': self._properties,
357 'enums': self._generated_enums, 376 'enums': self._generated_enums,
358 'include_paths': self._include_paths, 377 'include_paths': self._include_paths,
359 'fields': self._fields, 378 'computed_style': self._base_group,
360 } 379 }
361 380
362 @template_expander.use_jinja('ComputedStyleBase.cpp.tmpl') 381 @template_expander.use_jinja('ComputedStyleBase.cpp.tmpl')
363 def generate_base_computed_style_cpp(self): 382 def generate_base_computed_style_cpp(self):
364 return { 383 return {
365 'properties': self._properties, 384 'properties': self._properties,
366 'enums': self._generated_enums, 385 'enums': self._generated_enums,
367 'fields': self._fields, 386 'computed_style': self._base_group,
368 'expected_bit_field_bytes': self._expected_bit_field_bytes,
369 } 387 }
370 388
371 @template_expander.use_jinja('ComputedStyleBaseConstants.h.tmpl') 389 @template_expander.use_jinja('ComputedStyleBaseConstants.h.tmpl')
372 def generate_base_computed_style_constants(self): 390 def generate_base_computed_style_constants(self):
373 return { 391 return {
374 'properties': self._properties, 392 'properties': self._properties,
375 'enums': self._generated_enums, 393 'enums': self._generated_enums,
376 'fields': self._fields,
377 } 394 }
378 395
379 @template_expander.use_jinja('CSSValueIDMappingsGenerated.h.tmpl') 396 @template_expander.use_jinja('CSSValueIDMappingsGenerated.h.tmpl')
380 def generate_css_value_mappings(self): 397 def generate_css_value_mappings(self):
381 mappings = {} 398 mappings = {}
382 399
383 for property_ in self._properties.values(): 400 for property_ in self._properties.values():
384 if property_['field_template'] == 'keyword': 401 if property_['field_template'] == 'keyword':
385 mappings[property_['type_name']] = { 402 mappings[property_['type_name']] = {
386 'default_value': enum_value_name(property_['default_value']) , 403 'default_value': enum_value_name(property_['default_value']) ,
387 'mapping': [(enum_value_name(k), enum_for_css_keyword(k)) fo r k in property_['keywords']], 404 'mapping': [(enum_value_name(k), enum_for_css_keyword(k)) fo r k in property_['keywords']],
388 } 405 }
389 406
390 return { 407 return {
391 'include_paths': self._include_paths, 408 'include_paths': self._include_paths,
392 'mappings': mappings, 409 'mappings': mappings,
393 } 410 }
394 411
395 if __name__ == '__main__': 412 if __name__ == '__main__':
396 json5_generator.Maker(ComputedStyleBaseWriter).main() 413 json5_generator.Maker(ComputedStyleBaseWriter).main()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698