# Copyright 2013 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import mojom.generate.module as mojom # This module provides a mechanism for determining the packed order and offsets # of a mojom.Struct. # # ps = pack.PackedStruct(struct) # ps.packed_fields will access a list of PackedField objects, each of which # will have an offset, a size and a bit (for mojom.BOOLs). # Size of struct header in bytes: num_bytes [4B] + version [4B]. HEADER_SIZE = 8 class PackedField(object): kind_to_size = { mojom.BOOL: 1, mojom.INT8: 1, mojom.UINT8: 1, mojom.INT16: 2, mojom.UINT16: 2, mojom.INT32: 4, mojom.UINT32: 4, mojom.FLOAT: 4, mojom.HANDLE: 4, mojom.MSGPIPE: 4, mojom.SHAREDBUFFER: 4, mojom.DCPIPE: 4, mojom.DPPIPE: 4, mojom.NULLABLE_HANDLE: 4, mojom.NULLABLE_MSGPIPE: 4, mojom.NULLABLE_SHAREDBUFFER: 4, mojom.NULLABLE_DCPIPE: 4, mojom.NULLABLE_DPPIPE: 4, mojom.INT64: 8, mojom.UINT64: 8, mojom.DOUBLE: 8, mojom.STRING: 8, mojom.NULLABLE_STRING: 8 } @classmethod def GetSizeForKind(cls, kind): if isinstance(kind, (mojom.Array, mojom.Map, mojom.Struct, mojom.Interface, mojom.AssociatedInterface)): return 8 if isinstance(kind, mojom.Union): return 16 if isinstance(kind, mojom.InterfaceRequest): kind = mojom.MSGPIPE if isinstance(kind, mojom.AssociatedInterfaceRequest): return 4 if isinstance(kind, mojom.Enum): # TODO(mpcomplete): what about big enums? return cls.kind_to_size[mojom.INT32] if not kind in cls.kind_to_size: raise Exception("Undefined type: %s. Did you forget to import the file " "containing the definition?" % kind.spec) return cls.kind_to_size[kind] @classmethod def GetAlignmentForKind(cls, kind): if isinstance(kind, (mojom.Interface, mojom.AssociatedInterface)): return 4 if isinstance(kind, mojom.Union): return 8 return cls.GetSizeForKind(kind) def __init__(self, field, index, ordinal): """ Args: field: the original field. index: the position of the original field in the struct. ordinal: the ordinal of the field for serialization. """ self.field = field self.index = index self.ordinal = ordinal self.size = self.GetSizeForKind(field.kind) self.alignment = self.GetAlignmentForKind(field.kind) self.offset = None self.bit = None self.min_version = None def GetPad(offset, alignment): """Returns the pad necessary to reserve space so that |offset + pad| equals to some multiple of |alignment|.""" return (alignment - (offset % alignment)) % alignment def GetFieldOffset(field, last_field): """Returns a 2-tuple of the field offset and bit (for BOOLs).""" if (field.field.kind == mojom.BOOL and last_field.field.kind == mojom.BOOL and last_field.bit < 7): return (last_field.offset, last_field.bit + 1) offset = last_field.offset + last_field.size pad = GetPad(offset, field.alignment) return (offset + pad, 0) def GetPayloadSizeUpToField(field): """Returns the payload size (not including struct header) if |field| is the last field. """ if not field: return 0 offset = field.offset + field.size pad = GetPad(offset, 8) return offset + pad class PackedStruct(object): def __init__(self, struct): self.struct = struct # |packed_fields| contains all the fields, in increasing offset order. self.packed_fields = [] # |packed_fields_in_ordinal_order| refers to the same fields as # |packed_fields|, but in ordinal order. self.packed_fields_in_ordinal_order = [] # No fields. if (len(struct.fields) == 0): return # Start by sorting by ordinal. src_fields = self.packed_fields_in_ordinal_order ordinal = 0 for index, field in enumerate(struct.fields): if field.ordinal is not None: ordinal = field.ordinal src_fields.append(PackedField(field, index, ordinal)) ordinal += 1 src_fields.sort(key=lambda field: field.ordinal) # Set |min_version| for each field. next_min_version = 0 for packed_field in src_fields: if packed_field.field.min_version is None: assert next_min_version == 0 else: assert packed_field.field.min_version >= next_min_version next_min_version = packed_field.field.min_version packed_field.min_version = next_min_version if (packed_field.min_version != 0 and mojom.IsReferenceKind(packed_field.field.kind) and not packed_field.field.kind.is_nullable): raise Exception("Non-nullable fields are only allowed in version 0 of " "a struct. %s.%s is defined with [MinVersion=%d]." % (self.struct.name, packed_field.field.name, packed_field.min_version)) src_field = src_fields[0] src_field.offset = 0 src_field.bit = 0 dst_fields = self.packed_fields dst_fields.append(src_field) # Then find first slot that each field will fit. for src_field in src_fields[1:]: last_field = dst_fields[0] for i in range(1, len(dst_fields)): next_field = dst_fields[i] offset, bit = GetFieldOffset(src_field, last_field) if offset + src_field.size <= next_field.offset: # Found hole. src_field.offset = offset src_field.bit = bit dst_fields.insert(i, src_field) break last_field = next_field if src_field.offset is None: # Add to end src_field.offset, src_field.bit = GetFieldOffset(src_field, last_field) dst_fields.append(src_field) class ByteInfo(object): def __init__(self): self.is_padding = False self.packed_fields = [] def GetByteLayout(packed_struct): total_payload_size = GetPayloadSizeUpToField( packed_struct.packed_fields[-1] if packed_struct.packed_fields else None) bytes = [ByteInfo() for i in range(total_payload_size)] limit_of_previous_field = 0 for packed_field in packed_struct.packed_fields: for i in range(limit_of_previous_field, packed_field.offset): bytes[i].is_padding = True bytes[packed_field.offset].packed_fields.append(packed_field) limit_of_previous_field = packed_field.offset + packed_field.size for i in range(limit_of_previous_field, len(bytes)): bytes[i].is_padding = True for byte in bytes: # A given byte cannot both be padding and have a fields packed into it. assert not (byte.is_padding and byte.packed_fields) return bytes class VersionInfo(object): def __init__(self, version, num_fields, num_bytes): self.version = version self.num_fields = num_fields self.num_bytes = num_bytes def GetVersionInfo(packed_struct): """Get version information for a struct. Args: packed_struct: A PackedStruct instance. Returns: A non-empty list of VersionInfo instances, sorted by version in increasing order. Note: The version numbers may not be consecutive. """ versions = [] last_version = 0 last_num_fields = 0 last_payload_size = 0 for packed_field in packed_struct.packed_fields_in_ordinal_order: if packed_field.min_version != last_version: versions.append( VersionInfo(last_version, last_num_fields, last_payload_size + HEADER_SIZE)) last_version = packed_field.min_version last_num_fields += 1 # The fields are iterated in ordinal order here. However, the size of a # version is determined by the last field of that version in pack order, # instead of ordinal order. Therefore, we need to calculate the max value. last_payload_size = max(GetPayloadSizeUpToField(packed_field), last_payload_size) assert len(versions) == 0 or last_num_fields != versions[-1].num_fields versions.append(VersionInfo(last_version, last_num_fields, last_payload_size + HEADER_SIZE)) return versions