xorg-xcbproto/xcbgen/expr.py

177 lines
6.3 KiB
Python
Raw Normal View History

'''
This module contains helper classes for structure fields and length expressions.
'''
class Field(object):
'''
Represents a field of a structure.
type is the datatype object for the field.
field_type is the name of the type (string tuple)
field_name is the name of the structure field.
visible is true iff the field should be in the request API.
wire is true iff the field should be in the request structure.
auto is true iff the field is on the wire but not in the request API (e.g. opcode)
enum is the enum name this field refers to, if any.
'''
def __init__(self, type, field_type, field_name, visible, wire, auto, enum=None, isfd=False):
self.type = type
self.field_type = field_type
self.field_name = field_name
self.enum = enum
self.visible = visible
self.wire = wire
self.auto = auto
self.isfd = isfd
self.parent = None
automatic alignment checker / manual offsets The verification algorithm basically traverses the protocol-description of a protocol entity by recursively processing all fields and their types. A start-align consists of two numbers: * the alignment: This is a power of 2, and guarantees that the address (or protocol position) minus the offset can be divided by this number. * the offset: how many bytes the current position is after a position that can be divided by the alignment. The algorithm starts with the start-alignment and computes the alignment of each field from the start-align of the previous field as follows: * if the previous field is a primitive type then the offset is increased by the size of the primitive type module the alignment. If the alignment or offset are incompatible with the primitive type, then an error is reported. * if the previous field is a complex type, then it is processed recursively. * if the previous field is an alignment-pad, then the alignment is adjusted accordingly, as to be expected by the alignment-pad. Start-aligns can also be set manually with the xml-element "required_start_align" which has two attributes: "align" and "offset" If "offset" is omitted, it is assumed to 0. All toplevel protocol entities default to 4-byte start-alignment with offset 0. All non-toplevel complex entities, such as structs, switch, case, ... do not have a default alignment. If no alignment is explicitly specified for them, their alignment is computed by their usage in other entities. In that case, if they are used with aligments that violate the alignment requirements of some of their fields, an error is issued. If they are used with an alignment with non-zero offset, a warning is issued, which recommends to specify the required_start_align explicitly. (Reason: I don't want non-zero offsets to be silently computed automatically. These non-zero offsets have to be reviewed by a human and specified explicitely to record that this was reviewed by a human) If the required_start_align is explicitly specified for an entity then an error will be issued if it is used with an aligment that's incompatible with the explicitly specified alignment. If an entity is used in different contexts with different start-aligns then those start-aligns are combined to an align which is compatible with all aligns. E.g. (align 4, offset 0) and (align 4, offset 2) are combined to (align 2, offset 0). Error reports include the relevant context for a misalignment. For non-toplevel entities this includes the entity-usage stack. There is some complexity in the algorithm for reducing the size of the error-reports to include only the relevant info. This alignment verifier is also a prerequisite for getting rid of implicit alignment altogether. (This will then simplify the generated code and make it faster.) Signed-off-by: Christian Linhart <chris@demorecorder.com>
2015-11-01 18:26:33 +01:00
def __str__(self):
field_string = "Field"
if self.field_name is None:
if self.field_type is not None:
field_string += " with type " + str(self.type)
else:
field_string += " \"" + self.field_name + "\""
if self.parent is not None:
field_string += " in " + str(self.parent)
return field_string
class Expression(object):
'''
Represents a mathematical expression for a list length or exprfield.
Public fields:
op is the operation (text +,*,/,<<,~) or None.
lhs and rhs are the sub-Expressions if op is set.
lenfield_name is the name of the length field, or None for request lists.
lenfield is the Field object for the length field, or None.
bitfield is True if the length field is a bitmask instead of a number.
nmemb is the fixed size (value)of the expression, or None
'''
def __init__(self, elt, parent):
self.parent = parent
self.nmemb = None
self.lenfield_name = None
self.lenfield_type = None
self.lenfield_parent = None
self.lenfield = None
self.lenwire = False
self.bitfield = False
self.op = None
self.lhs = None
self.rhs = None
self.contains_listelement_ref = False
if elt.tag == 'list':
# List going into a request, which has no length field (inferred by server)
self.lenfield_name = elt.get('name') + '_len'
self.lenfield_type = 'CARD32'
elif elt.tag == 'fieldref':
# Standard list with a fieldref
self.lenfield_name = elt.text
elif elt.tag == 'paramref':
self.lenfield_name = elt.text
self.lenfield_type = elt.get('type')
elif elt.tag == 'op':
# Op field. Need to recurse.
self.op = elt.get('op')
self.lhs = Expression(list(elt)[0], parent)
self.rhs = Expression(list(elt)[1], parent)
# Hopefully we don't have two separate length fields...
self.lenfield_name = self.lhs.lenfield_name
if self.lenfield_name == None:
self.lenfield_name = self.rhs.lenfield_name
elif elt.tag == 'unop':
# Op field. Need to recurse.
self.op = elt.get('op')
self.rhs = Expression(list(elt)[0], parent)
self.lenfield_name = self.rhs.lenfield_name
elif elt.tag == 'value':
# Constant expression
self.nmemb = int(elt.text, 0)
elif elt.tag == 'popcount':
self.op = 'popcount'
self.rhs = Expression(list(elt)[0], parent)
self.lenfield_name = self.rhs.lenfield_name
# xcb_popcount returns 'int' - handle the type in the language-specific part
elif elt.tag == 'enumref':
self.op = 'enumref'
self.lenfield_name = (elt.get('ref'), elt.text)
elif elt.tag == 'sumof':
self.op = 'sumof'
self.lenfield_name = elt.get('ref')
subexpressions = list(elt)
if len(subexpressions) > 0:
# sumof with a nested expression which is to be evaluated
# for each list-element in the context of that list-element.
# sumof then returns the sum of the results of these evaluations
self.rhs = Expression(subexpressions[0], parent)
elif elt.tag == 'listelement-ref':
# current list element inside iterating expressions such as sumof
self.op = 'listelement-ref'
self.contains_listelement_ref = True
else:
# Notreached
raise Exception("undefined tag '%s'" % elt.tag)
def fixed_size(self):
return self.nmemb != None
automatic alignment checker / manual offsets The verification algorithm basically traverses the protocol-description of a protocol entity by recursively processing all fields and their types. A start-align consists of two numbers: * the alignment: This is a power of 2, and guarantees that the address (or protocol position) minus the offset can be divided by this number. * the offset: how many bytes the current position is after a position that can be divided by the alignment. The algorithm starts with the start-alignment and computes the alignment of each field from the start-align of the previous field as follows: * if the previous field is a primitive type then the offset is increased by the size of the primitive type module the alignment. If the alignment or offset are incompatible with the primitive type, then an error is reported. * if the previous field is a complex type, then it is processed recursively. * if the previous field is an alignment-pad, then the alignment is adjusted accordingly, as to be expected by the alignment-pad. Start-aligns can also be set manually with the xml-element "required_start_align" which has two attributes: "align" and "offset" If "offset" is omitted, it is assumed to 0. All toplevel protocol entities default to 4-byte start-alignment with offset 0. All non-toplevel complex entities, such as structs, switch, case, ... do not have a default alignment. If no alignment is explicitly specified for them, their alignment is computed by their usage in other entities. In that case, if they are used with aligments that violate the alignment requirements of some of their fields, an error is issued. If they are used with an alignment with non-zero offset, a warning is issued, which recommends to specify the required_start_align explicitly. (Reason: I don't want non-zero offsets to be silently computed automatically. These non-zero offsets have to be reviewed by a human and specified explicitely to record that this was reviewed by a human) If the required_start_align is explicitly specified for an entity then an error will be issued if it is used with an aligment that's incompatible with the explicitly specified alignment. If an entity is used in different contexts with different start-aligns then those start-aligns are combined to an align which is compatible with all aligns. E.g. (align 4, offset 0) and (align 4, offset 2) are combined to (align 2, offset 0). Error reports include the relevant context for a misalignment. For non-toplevel entities this includes the entity-usage stack. There is some complexity in the algorithm for reducing the size of the error-reports to include only the relevant info. This alignment verifier is also a prerequisite for getting rid of implicit alignment altogether. (This will then simplify the generated code and make it faster.) Signed-off-by: Christian Linhart <chris@demorecorder.com>
2015-11-01 18:26:33 +01:00
def get_value(self):
return self.nmemb
# if the value of the expression is a guaranteed multiple of a number
# return this number, else return 1 (which is trivially guaranteed for integers)
def get_multiple(self):
multiple = 1
if self.op == '*':
if self.lhs.fixed_size():
multiple *= self.lhs.get_value()
if self.rhs.fixed_size():
multiple *= self.rhs.get_value()
return multiple
def recursive_resolve_tasks(self, module, parents):
for subexpr in (self.lhs, self.rhs):
if subexpr != None:
subexpr.recursive_resolve_tasks(module, parents)
self.contains_listelement_ref |= subexpr.contains_listelement_ref
def resolve(self, module, parents):
if self.op == 'enumref':
self.lenfield_type = module.get_type(self.lenfield_name[0])
self.lenfield_name = self.lenfield_name[1]
elif self.op == 'sumof':
# need to find the field with lenfield_name
for p in reversed(parents):
fields = dict([(f.field_name, f) for f in p.fields])
if self.lenfield_name in fields.keys():
if p.is_case_or_bitcase:
# switch is the anchestor
self.lenfield_parent = p.parents[-1]
else:
self.lenfield_parent = p
self.lenfield_type = fields[self.lenfield_name].field_type
self.lenfield = fields[self.lenfield_name]
break
self.recursive_resolve_tasks(module, parents)