This commit is contained in:
2025-09-27 22:09:23 +03:00
commit 914c0295ac
2468 changed files with 204262 additions and 0 deletions

View File

@@ -0,0 +1,177 @@
'''
This module contains helper classes for alignment arithmetic and checks
'''
from fractions import gcd
class Alignment(object):
def __init__(self, align=4, offset=0):
self.align = align
# normalize the offset (just in case)
self.offset = offset % align
def __eq__(self, other):
return self.align == other.align and self.offset == other.offset
def __str__(self):
return "(align=%d, offset=%d)" % (self.align, self.offset)
@staticmethod
def for_primitive_type(size):
# compute the required start_alignment based on the size of the type
if size % 8 == 0:
# do 8-byte primitives require 8-byte alignment in X11?
return Alignment(8,0)
elif size % 4 == 0:
return Alignment(4,0)
elif size % 2 == 0:
return Alignment(2,0)
else:
return Alignment(1,0)
def align_after_fixed_size(self, size):
new_offset = (self.offset + size) % self.align
return Alignment(self.align, new_offset)
def is_guaranteed_at(self, external_align):
'''
Assuming the given external_align, checks whether
self is fulfilled for all cases.
Returns True if yes, False otherwise.
'''
if self.align == 1 and self.offset == 0:
# alignment 1 with offset 0 is always fulfilled
return True
if external_align is None:
# there is no external align -> fail
return False
if external_align.align < self.align:
# the external align guarantees less alignment -> not guaranteed
return False
if external_align.align % self.align != 0:
# the external align cannot be divided by our align
# -> not guaranteed
# (this can only happen if there are alignments that are not
# a power of 2, which is highly discouraged. But better be
# safe and check for it)
return False
if external_align.offset % self.align != self.offset:
# offsets do not match
return False
return True
def combine_with(self, other):
# returns the alignment that is guaranteed when
# both, self or other, can happen
new_align = gcd(self.align, other.align)
new_offset_candidate1 = self.offset % new_align
new_offset_candidate2 = other.offset % new_align
if new_offset_candidate1 == new_offset_candidate2:
new_offset = new_offset_candidate1
else:
offset_diff = abs(new_offset_candidate2 - new_offset_candidate1)
new_align = gcd(new_align, offset_diff)
new_offset_candidate1 = self.offset % new_align
new_offset_candidate2 = other.offset % new_align
assert new_offset_candidate1 == new_offset_candidate2
new_offset = new_offset_candidate1
# return the result
return Alignment(new_align, new_offset)
class AlignmentLog(object):
def __init__(self):
self.ok_list = []
self.fail_list = []
self.verbosity = 1
def __str__(self):
result = ""
# output the OK-list
for (align_before, field_name, type_obj, callstack, align_after) in self.ok_list:
stacksize = len(callstack)
indent = ' ' * stacksize
if self.ok_callstack_is_relevant(callstack):
if field_name is None or field_name == "":
result += (" %sok: %s:\n\t%sbefore: %s, after: %s\n"
% (indent, str(type_obj), indent, str(align_before), str(align_after)))
else:
result += (" %sok: field \"%s\" in %s:\n\t%sbefore: %s, after: %s\n"
% (indent, str(field_name), str(type_obj),
indent, str(align_before), str(align_after)))
if self.verbosity >= 1:
result += self.callstack_to_str(indent, callstack)
# output the fail-list
for (align_before, field_name, type_obj, callstack, reason) in self.fail_list:
stacksize = len(callstack)
indent = ' ' * stacksize
if field_name is None or field_name == "":
result += (" %sfail: align %s is incompatible with\n\t%s%s\n\t%sReason: %s\n"
% (indent, str(align_before), indent, str(type_obj), indent, reason))
else:
result += (" %sfail: align %s is incompatible with\n\t%sfield \"%s\" in %s\n\t%sReason: %s\n"
% (indent, str(align_before), indent, str(field_name), str(type_obj), indent, reason))
if self.verbosity >= 1:
result += self.callstack_to_str(indent, callstack)
return result
def callstack_to_str(self, indent, callstack):
result = "\t%scallstack: [\n" % indent
for stack_elem in callstack:
result += "\t %s%s\n" % (indent, str(stack_elem))
result += "\t%s]\n" % indent
return result
def ok_callstack_is_relevant(self, ok_callstack):
# determine whether an ok callstack is relevant for logging
if self.verbosity >= 2:
return True
# empty callstacks are always relevant
if len(ok_callstack) == 0:
return True
# check whether the ok_callstack is a subset or equal to a fail_callstack
for (align_before, field_name, type_obj, fail_callstack, reason) in self.fail_list:
if len(ok_callstack) <= len(fail_callstack):
zipped = zip(ok_callstack, fail_callstack[:len(ok_callstack)])
is_subset = all([i == j for i, j in zipped])
if is_subset:
return True
return False
def ok(self, align_before, field_name, type_obj, callstack, align_after):
self.ok_list.append((align_before, field_name, type_obj, callstack, align_after))
def fail(self, align_before, field_name, type_obj, callstack, reason):
self.fail_list.append((align_before, field_name, type_obj, callstack, reason))
def append(self, other):
self.ok_list.extend(other.ok_list)
self.fail_list.extend(other.fail_list)
def ok_count(self):
return len(self.ok_list)

View File

@@ -0,0 +1,5 @@
class ResolveException(Exception):
'''
Gets thrown when a type doesn't resolve in the XML.
'''
pass

View File

@@ -0,0 +1,175 @@
'''
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
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
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
break
self.recursive_resolve_tasks(module, parents)

View File

@@ -0,0 +1,115 @@
'''
XML parser. One function for each top-level element in the schema.
Most functions just declare a new object and add it to the module.
For typedefs, eventcopies, xidtypes, and other aliases though,
we do not create a new type object, we just record the existing one under a new name.
'''
from os.path import join
from xml.etree.cElementTree import parse
from xcbgen.xtypes import *
def import_(node, module, namespace):
'''
For imports, we load the file, create a new namespace object,
execute recursively, then record the import (for header files, etc.)
'''
# To avoid circular import error
from xcbgen import state
module.import_level = module.import_level + 1
new_file = join(namespace.dir, '%s.xml' % node.text)
new_root = parse(new_file).getroot()
new_namespace = state.Namespace(new_file)
execute(module, new_namespace)
module.import_level = module.import_level - 1
if not module.has_import(node.text):
module.add_import(node.text, new_namespace)
def typedef(node, module, namespace):
id = node.get('newname')
name = namespace.prefix + (id,)
type = module.get_type(node.get('oldname'))
module.add_type(id, namespace.ns, name, type)
def xidtype(node, module, namespace):
id = node.get('name')
name = namespace.prefix + (id,)
type = module.get_type('CARD32')
module.add_type(id, namespace.ns, name, type)
def xidunion(node, module, namespace):
id = node.get('name')
name = namespace.prefix + (id,)
type = module.get_type('CARD32')
module.add_type(id, namespace.ns, name, type)
def enum(node, module, namespace):
id = node.get('name')
name = namespace.prefix + (id,)
type = Enum(name, node)
module.add_type(id, namespace.ns, name, type)
def struct(node, module, namespace):
id = node.get('name')
name = namespace.prefix + (id,)
type = Struct(name, node)
module.add_type(id, namespace.ns, name, type)
def union(node, module, namespace):
id = node.get('name')
name = namespace.prefix + (id,)
type = Union(name, node)
module.add_type(id, namespace.ns, name, type)
def request(node, module, namespace):
id = node.get('name')
name = namespace.prefix + (id,)
type = Request(name, node)
module.add_request(id, name, type)
def event(node, module, namespace):
id = node.get('name')
name = namespace.prefix + (id,)
event = Event(name, node)
event.add_opcode(node.get('number'), name, True)
module.add_event(id, name, event)
def eventcopy(node, module, namespace):
id = node.get('name')
name = namespace.prefix + (id,)
event = module.get_event(node.get('ref'))
event.add_opcode(node.get('number'), name, False)
module.add_event(id, name, event)
def error(node, module, namespace):
id = node.get('name')
name = namespace.prefix + (id,)
error = Error(name, node)
error.add_opcode(node.get('number'), name, True)
module.add_error(id, name, error)
def errorcopy(node, module, namespace):
id = node.get('name')
name = namespace.prefix + (id,)
error = module.get_error(node.get('ref'))
error.add_opcode(node.get('number'), name, False)
module.add_error(id, name, error)
funcs = {'import' : import_,
'typedef' : typedef,
'xidtype' : xidtype,
'xidunion' : xidunion,
'enum' : enum,
'struct' : struct,
'union' : union,
'request' : request,
'event' : event,
'eventcopy' : eventcopy,
'error' : error,
'errorcopy' : errorcopy}
def execute(module, namespace):
for elt in list(namespace.root):
funcs[elt.tag](elt, module, namespace)

View File

@@ -0,0 +1,173 @@
'''
This module contains the namespace class and the singleton module class.
'''
from os.path import dirname, basename
from xml.etree.cElementTree import parse
from xcbgen import matcher
from xcbgen.error import *
from xcbgen.xtypes import *
import __main__
class Namespace(object):
'''
Contains the naming information for an extension.
Public fields:
header is the header attribute ("header file" name).
is_ext is true for extensions, false for xproto.
major_version and minor_version are extension version info.
ext_xname is the X extension name string.
ext_name is the XCB extension name prefix.
'''
def __init__(self, filename):
# Path info
self.path = filename
self.dir = dirname(filename)
self.file = basename(filename)
# Parse XML
self.root = parse(filename).getroot()
self.header = self.root.get('header')
self.ns = self.header + ':'
# Get root element attributes
if self.root.get('extension-xname', False):
self.is_ext = True
self.major_version = self.root.get('major-version')
self.minor_version = self.root.get('minor-version')
self.ext_xname = self.root.get('extension-xname')
self.ext_name = self.root.get('extension-name')
self.prefix = ('xcb', self.ext_name)
else:
self.is_ext = False
self.ext_name = ''
self.prefix = ('xcb',)
class Module(object):
'''
This is the grand, encompassing class that represents an entire XCB specification.
Only gets instantiated once, in the main() routine.
Don't need to worry about this much except to declare it and to get the namespace.
Public fields:
namespace contains the namespace info for the spec.
'''
open = __main__.output['open']
close = __main__.output['close']
def __init__(self, filename, output):
self.namespace = Namespace(filename)
self.output = output
self.imports = []
self.direct_imports = []
self.import_level = 0
self.types = {}
self.events = {}
self.errors = {}
self.all = []
# Register some common types
self.add_type('CARD8', '', ('uint8_t',), tcard8)
self.add_type('CARD16', '', ('uint16_t',), tcard16)
self.add_type('CARD32', '', ('uint32_t',), tcard32)
self.add_type('CARD64', '', ('uint64_t',), tcard64)
self.add_type('INT8', '', ('int8_t',), tint8)
self.add_type('INT16', '', ('int16_t',), tint16)
self.add_type('INT32', '', ('int32_t',), tint32)
self.add_type('INT64', '', ('int64_t',), tint64)
self.add_type('BYTE', '', ('uint8_t',), tcard8)
self.add_type('BOOL', '', ('uint8_t',), tcard8)
self.add_type('char', '', ('char',), tchar)
self.add_type('float', '', ('float',), tfloat)
self.add_type('double', '', ('double',), tdouble)
self.add_type('void', '', ('void',), tcard8)
# This goes out and parses the rest of the XML
def register(self):
matcher.execute(self, self.namespace)
# Recursively resolve all types
def resolve(self):
for (name, item) in self.all:
self.pads = 0
item.resolve(self)
# Call all the output methods
def generate(self):
self.open()
for (name, item) in self.all:
item.out(name)
self.close()
# Keeps track of what's been imported so far.
def add_import(self, name, namespace):
if self.import_level == 0:
self.direct_imports.append((name, namespace.header))
self.imports.append((name, namespace.header))
def has_import(self, name):
for (name_, header) in self.imports:
if name_ == name:
return True
return False
# Keeps track of non-request/event/error datatypes
def add_type(self, id, ns, name, item):
key = ns + id
if key in self.types:
return
self.types[key] = (name, item)
if name[:-1] == self.namespace.prefix:
self.all.append((name, item))
def get_type_impl(self, id, idx):
key = id
if key in self.types:
return self.types[key][idx]
key = self.namespace.ns + id
if key in self.types:
return self.types[key][idx]
for key in self.types.keys():
if key.rpartition(':')[2] == id:
return self.types[key][idx]
raise ResolveException('Type %s not found' % id)
def get_type(self, id):
return self.get_type_impl(id, 1)
def get_type_name(self, id):
return self.get_type_impl(id, 0)
# Keeps track of request datatypes
def add_request(self, id, name, item):
if name[:-1] == self.namespace.prefix:
self.all.append((name, item))
# Keeps track of event datatypes
def add_event(self, id, name, item):
self.events[id] = (name, item)
if name[:-1] == self.namespace.prefix:
self.all.append((name, item))
def get_event(self, id):
return self.events[id][1]
# Keeps track of error datatypes
def add_error(self, id, name, item):
self.errors[id] = (name, item)
if name[:-1] == self.namespace.prefix:
self.all.append((name, item))
def get_error(self, id):
return self.errors[id][1]

File diff suppressed because it is too large Load Diff