Home | History | Annotate | Download | only in generate
      1 # Copyright 2013 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import module as mojom
      6 
      7 # This module provides a mechanism for determining the packed order and offsets
      8 # of a mojom.Struct.
      9 #
     10 # ps = pack.PackedStruct(struct)
     11 # ps.packed_fields will access a list of PackedField objects, each of which
     12 # will have an offset, a size and a bit (for mojom.BOOLs).
     13 
     14 # Size of struct header in bytes: num_bytes [4B] + version [4B].
     15 HEADER_SIZE = 8
     16 
     17 class PackedField(object):
     18   kind_to_size = {
     19     mojom.BOOL:                  1,
     20     mojom.INT8:                  1,
     21     mojom.UINT8:                 1,
     22     mojom.INT16:                 2,
     23     mojom.UINT16:                2,
     24     mojom.INT32:                 4,
     25     mojom.UINT32:                4,
     26     mojom.FLOAT:                 4,
     27     mojom.HANDLE:                4,
     28     mojom.MSGPIPE:               4,
     29     mojom.SHAREDBUFFER:          4,
     30     mojom.DCPIPE:                4,
     31     mojom.DPPIPE:                4,
     32     mojom.NULLABLE_HANDLE:       4,
     33     mojom.NULLABLE_MSGPIPE:      4,
     34     mojom.NULLABLE_SHAREDBUFFER: 4,
     35     mojom.NULLABLE_DCPIPE:       4,
     36     mojom.NULLABLE_DPPIPE:       4,
     37     mojom.INT64:                 8,
     38     mojom.UINT64:                8,
     39     mojom.DOUBLE:                8,
     40     mojom.STRING:                8,
     41     mojom.NULLABLE_STRING:       8
     42   }
     43 
     44   @classmethod
     45   def GetSizeForKind(cls, kind):
     46     if isinstance(kind, (mojom.Array, mojom.Map, mojom.Struct,
     47                          mojom.Interface, mojom.AssociatedInterface)):
     48       return 8
     49     if isinstance(kind, mojom.Union):
     50       return 16
     51     if isinstance(kind, mojom.InterfaceRequest):
     52       kind = mojom.MSGPIPE
     53     if isinstance(kind, mojom.AssociatedInterfaceRequest):
     54       return 4
     55     if isinstance(kind, mojom.Enum):
     56       # TODO(mpcomplete): what about big enums?
     57       return cls.kind_to_size[mojom.INT32]
     58     if not kind in cls.kind_to_size:
     59       raise Exception("Undefined type: %s. Did you forget to import the file "
     60                       "containing the definition?" % kind.spec)
     61     return cls.kind_to_size[kind]
     62 
     63   @classmethod
     64   def GetAlignmentForKind(cls, kind):
     65     if isinstance(kind, (mojom.Interface, mojom.AssociatedInterface)):
     66       return 4
     67     if isinstance(kind, mojom.Union):
     68       return 8
     69     return cls.GetSizeForKind(kind)
     70 
     71   def __init__(self, field, index, ordinal):
     72     """
     73     Args:
     74       field: the original field.
     75       index: the position of the original field in the struct.
     76       ordinal: the ordinal of the field for serialization.
     77     """
     78     self.field = field
     79     self.index = index
     80     self.ordinal = ordinal
     81     self.size = self.GetSizeForKind(field.kind)
     82     self.alignment = self.GetAlignmentForKind(field.kind)
     83     self.offset = None
     84     self.bit = None
     85     self.min_version = None
     86 
     87 
     88 def GetPad(offset, alignment):
     89   """Returns the pad necessary to reserve space so that |offset + pad| equals to
     90   some multiple of |alignment|."""
     91   return (alignment - (offset % alignment)) % alignment
     92 
     93 
     94 def GetFieldOffset(field, last_field):
     95   """Returns a 2-tuple of the field offset and bit (for BOOLs)."""
     96   if (field.field.kind == mojom.BOOL and
     97       last_field.field.kind == mojom.BOOL and
     98       last_field.bit < 7):
     99     return (last_field.offset, last_field.bit + 1)
    100 
    101   offset = last_field.offset + last_field.size
    102   pad = GetPad(offset, field.alignment)
    103   return (offset + pad, 0)
    104 
    105 
    106 def GetPayloadSizeUpToField(field):
    107   """Returns the payload size (not including struct header) if |field| is the
    108   last field.
    109   """
    110   if not field:
    111     return 0
    112   offset = field.offset + field.size
    113   pad = GetPad(offset, 8)
    114   return offset + pad
    115 
    116 
    117 class PackedStruct(object):
    118   def __init__(self, struct):
    119     self.struct = struct
    120     # |packed_fields| contains all the fields, in increasing offset order.
    121     self.packed_fields = []
    122     # |packed_fields_in_ordinal_order| refers to the same fields as
    123     # |packed_fields|, but in ordinal order.
    124     self.packed_fields_in_ordinal_order = []
    125 
    126     # No fields.
    127     if (len(struct.fields) == 0):
    128       return
    129 
    130     # Start by sorting by ordinal.
    131     src_fields = self.packed_fields_in_ordinal_order
    132     ordinal = 0
    133     for index, field in enumerate(struct.fields):
    134       if field.ordinal is not None:
    135         ordinal = field.ordinal
    136       src_fields.append(PackedField(field, index, ordinal))
    137       ordinal += 1
    138     src_fields.sort(key=lambda field: field.ordinal)
    139 
    140     # Set |min_version| for each field.
    141     next_min_version = 0
    142     for packed_field in src_fields:
    143       if packed_field.field.min_version is None:
    144         assert next_min_version == 0
    145       else:
    146         assert packed_field.field.min_version >= next_min_version
    147         next_min_version = packed_field.field.min_version
    148       packed_field.min_version = next_min_version
    149 
    150       if (packed_field.min_version != 0 and
    151           mojom.IsReferenceKind(packed_field.field.kind) and
    152           not packed_field.field.kind.is_nullable):
    153         raise Exception("Non-nullable fields are only allowed in version 0 of "
    154                         "a struct. %s.%s is defined with [MinVersion=%d]."
    155                             % (self.struct.name, packed_field.field.name,
    156                                packed_field.min_version))
    157 
    158     src_field = src_fields[0]
    159     src_field.offset = 0
    160     src_field.bit = 0
    161     dst_fields = self.packed_fields
    162     dst_fields.append(src_field)
    163 
    164     # Then find first slot that each field will fit.
    165     for src_field in src_fields[1:]:
    166       last_field = dst_fields[0]
    167       for i in xrange(1, len(dst_fields)):
    168         next_field = dst_fields[i]
    169         offset, bit = GetFieldOffset(src_field, last_field)
    170         if offset + src_field.size <= next_field.offset:
    171           # Found hole.
    172           src_field.offset = offset
    173           src_field.bit = bit
    174           dst_fields.insert(i, src_field)
    175           break
    176         last_field = next_field
    177       if src_field.offset is None:
    178         # Add to end
    179         src_field.offset, src_field.bit = GetFieldOffset(src_field, last_field)
    180         dst_fields.append(src_field)
    181 
    182 
    183 class ByteInfo(object):
    184   def __init__(self):
    185     self.is_padding = False
    186     self.packed_fields = []
    187 
    188 
    189 def GetByteLayout(packed_struct):
    190   total_payload_size = GetPayloadSizeUpToField(
    191       packed_struct.packed_fields[-1] if packed_struct.packed_fields else None)
    192   bytes = [ByteInfo() for i in xrange(total_payload_size)]
    193 
    194   limit_of_previous_field = 0
    195   for packed_field in packed_struct.packed_fields:
    196     for i in xrange(limit_of_previous_field, packed_field.offset):
    197       bytes[i].is_padding = True
    198     bytes[packed_field.offset].packed_fields.append(packed_field)
    199     limit_of_previous_field = packed_field.offset + packed_field.size
    200 
    201   for i in xrange(limit_of_previous_field, len(bytes)):
    202     bytes[i].is_padding = True
    203 
    204   for byte in bytes:
    205     # A given byte cannot both be padding and have a fields packed into it.
    206     assert not (byte.is_padding and byte.packed_fields)
    207 
    208   return bytes
    209 
    210 
    211 class VersionInfo(object):
    212   def __init__(self, version, num_fields, num_bytes):
    213     self.version = version
    214     self.num_fields = num_fields
    215     self.num_bytes = num_bytes
    216 
    217 
    218 def GetVersionInfo(packed_struct):
    219   """Get version information for a struct.
    220 
    221   Args:
    222     packed_struct: A PackedStruct instance.
    223 
    224   Returns:
    225     A non-empty list of VersionInfo instances, sorted by version in increasing
    226     order.
    227     Note: The version numbers may not be consecutive.
    228   """
    229   versions = []
    230   last_version = 0
    231   last_num_fields = 0
    232   last_payload_size = 0
    233 
    234   for packed_field in packed_struct.packed_fields_in_ordinal_order:
    235     if packed_field.min_version != last_version:
    236       versions.append(
    237           VersionInfo(last_version, last_num_fields,
    238                       last_payload_size + HEADER_SIZE))
    239       last_version = packed_field.min_version
    240 
    241     last_num_fields += 1
    242     # The fields are iterated in ordinal order here. However, the size of a
    243     # version is determined by the last field of that version in pack order,
    244     # instead of ordinal order. Therefore, we need to calculate the max value.
    245     last_payload_size = max(GetPayloadSizeUpToField(packed_field),
    246                             last_payload_size)
    247 
    248   assert len(versions) == 0 or last_num_fields != versions[-1].num_fields
    249   versions.append(VersionInfo(last_version, last_num_fields,
    250                               last_payload_size + HEADER_SIZE))
    251   return versions
    252