gecko/xpcom/base/gqi.py
2008-01-25 09:14:48 -08:00

789 lines
25 KiB
Python

# -*- Mode: Python: tab-width: 8; indent-tabs-mode: nil; python-indent: 4 -*-
"""Implement QueryInterface using optimized generated code."""
import os, errno, re, sys, sets
# Global controlling debug output to sys.stderr
debug = False
def findfile(f, curfile, includedirs):
"""Find a file 'f' by looking in the directory of the current file
and then in all directories specified by -I until it is found. Returns
the path of the file that was found.
@raises IOError is the file is not found."""
sdirs = [os.path.dirname(curfile)]
sdirs.extend(includedirs)
for dir in sdirs:
t = os.path.join(dir, f)
if os.path.exists(t):
return t
raise IOError(errno.ENOENT, "File not found", f)
# Giant parsing loop: parse input files into the internal representation,
# keeping track of file/line numbering information. This iterator automatically
# skips blank lines and lines beginning with //
class IterFile(object):
def __init__(self, file):
self._file = file
self._iter = open(file)
self._lineno = 0
def __iter__(self):
return self
def next(self):
self._lineno += 1
line = self._iter.next().strip()
if line == '' or line.startswith('//'):
return self.next()
return line
def loc(self):
return "%s:%i" % (self._file, self._lineno)
class uniqdict(dict):
"""A subclass of dict that will throw an error if you attempt to set the same key to different values."""
def __setitem__(self, key, value):
if key in self and not value == self[key]:
raise IndexError('Key "%s" already present in uniqdict' % key)
dict.__setitem__(self, key, value)
def update(self, d):
for key, value in d.iteritems():
if key in self and not value == self[key]:
raise IndexError('Key "%s" already present in uniqdict' % key)
dict.update(self, d)
def dump_hex_tuple(t):
return "(%s)" % ", ".join(["%#x" % i for i in t])
class UUID(object):
def __init__(self, name, iidstr, pseudo=False, base=None):
"""This method *assumes* that the UUID is validly formed."""
self.name = name
self.pseudo = pseudo
self.base = base
# iid is in 32-16-16-8*8 format, as hex *strings*
iid = (iidstr[0:8], iidstr[9:13], iidstr[14:18],
iidstr[19:21], iidstr[21:23], iidstr[24:26], iidstr[26:28],
iidstr[28:30], iidstr[30:32], iidstr[32:34], iidstr[34:36])
self.iid = iid
# (big_endian_words, little_endian_words)
self.words = (
(int(iid[0], 16),
int(iid[1] + iid[2], 16),
int("".join([iid[i] for i in xrange(3, 7)]), 16),
int("".join([iid[i] for i in xrange(7, 11)]), 16)),
(int(iid[0], 16),
int(iid[2] + iid[1], 16),
int("".join([iid[i] for i in xrange(6, 2, -1)]), 16),
int("".join([iid[i] for i in xrange(10, 6, -1)]), 16)))
if debug:
print >>sys.stderr, "%r" % self
print >>sys.stderr, " bigendian: %s" % dump_hex_tuple(self.words[False])
print >>sys.stderr, " littleendian: %r" % dump_hex_tuple(self.words[True])
def __eq__(self, uuid):
return self.iid == uuid.iid and self.name == uuid.name
def __repr__(self):
return """UUID(%r, "%s-%s-%s-%s-%s", pseudo=%r, base=%r)""" % (
self.name, self.iid[0], self.iid[1], self.iid[2], self.iid[3] + self.iid[4],
"".join([self.iid[i] for i in xrange(5, 11)]), self.pseudo, self.base)
def asstruct(self):
return "{ 0x%s, 0x%s, 0x%s, { 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s } }" % self.iid
def runtime_assertion(self):
if self.pseudo:
return """
static const nsID kGQI_%(iname)s = %(struct)s;
NS_ASSERTION(NS_GET_IID(%(iname)s).Equals(kGQI_%(iname)s),
"GQI pseudo-IID doesn't match reality.");
""" % {
'iname': self.name,
'struct': self.asstruct()
}
else:
return ""
_uuid_pattern_string = r'[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}'
_includefinder = re.compile(r'#include\s+(?:"|\<)(?P<filename>[a-z\.\-\_0-9]+)(?:"|\>)\s*$', re.I)
_uuidfinder = re.compile(r'uuid\((?P<iid>' + _uuid_pattern_string + r')\)', re.I)
_namefinder = re.compile(r'interface\s+(?P<interface>[A-Za-z_0-9]+)\s+:\s+(?P<base>[A-Za-z]+)')
def importidl(file, includedirs):
"""Parse an IDL file. Returns (mapofiids, setofdeps)"""
iids = {}
deps = sets.Set()
deps.add(file)
f = IterFile(file)
for line in f:
if line.startswith('%{'):
# Skip literal IDL blocks such as %{C++ %}
for line in f:
if line.startswith('%}'):
break
continue
m = _includefinder.match(line)
if m is not None:
importediids, importeddeps = importidl(findfile(m.group('filename'), file, includedirs), includedirs)
iids.update(importediids)
deps |= importeddeps
continue
m = _uuidfinder.search(line)
if m is not None:
iid = m.group('iid')
line = f.next()
if line == 'interface nsISupports {':
iids['nsISupports'] = UUID('nsISupports', iid)
else:
m = _namefinder.match(line)
if m is None:
raise Exception("%s: expected interface" % f.loc())
iids[m.group('interface')] = \
UUID(m.group('interface'), iid, base=m.group('base'))
return iids, deps
def gqi(ifaces, endian):
"""Find an algorithm that uniquely identifies interfaces by a single
word. Returns (indexfunction, table)"""
for bits in xrange(3, 10):
bitmask = (1 << bits) - 1
for word in xrange(0, 4):
for sh in xrange(0, 33 - bits):
shmask = bitmask << sh
# print "Trying word: %i, bits: %i, shift: %i" % (word, bits, sh)
# print "Bitmask: %x" % (bitmask)
# print "Shifter mask: %x" % (shmask)
l = list([None for i in xrange(0, 1 << bits)])
try:
for i in xrange(0, len(ifaces)):
n = (ifaces[i].uuid.words[endian][word] & shmask) >> sh
if l[n] is not None:
# print "found conflict, index %i" % n
# print "old iface: %s" % l[n].uuid
# print "new iface: %s" % i.uuid
raise IndexError()
l[n] = i
except IndexError:
continue
# If we got here, we're ok... create the table we want
indexfunc = "(reinterpret_cast<const PRUint32*>(&aIID)[%i] & 0x%x) >> %i" % (word, shmask, sh)
return indexfunc, l
raise Exception("No run of 9 bits within a word was unique!: interfaces: %s" % [i.uuid for i in ifaces])
def basecast(item, baselist):
"""Returns a string where an item is static_cast through a list of bases."""
for base in baselist:
item = "static_cast<%s*>(%s)" % (base, item)
return item
class QIImpl(object):
_default_unfound = """
*aResult = nsnull;
return NS_NOINTERFACE;
"""
_qiimpl = """
enum %(cname)s_QIActionType {
%(actionenums)s
};
struct %(cname)s_QIAction {
const nsID *iid;
%(actiontype)s
union {
void *ptr;
%(actiondata)s
} data;
};
NS_IMETHODIMP %(cname)s::QueryInterface(REFNSIID aIID, void **aResult)
{
#ifdef DEBUG
%(pseudoassertions)s
#endif
static const PRUint8 kLookupTable[] = {
#ifdef IS_BIG_ENDIAN
%(biglookup)s
#else
%(littlelookup)s
#endif
};
static const %(cname)s_QIAction kActionTable[] = {
%(actiontable)s
};
nsISupports* found = nsnull;
const %(cname)s_QIAction *entry;
PRUint32 index =
#ifdef IS_BIG_ENDIAN
%(bigindexfunc)s;
#else
%(littleindexfunc)s;
#endif
PRUint8 action = kLookupTable[index];
if (action == 0xFF)
goto unfound;
entry = kActionTable + action;
if (!entry->iid->Equals(aIID))
goto unfound;
%(actioncode)s
NS_NOTREACHED("Unhandled case?");
%(nullcheck)s
exit_addref:
NS_ASSERTION(found, "Interface should have been found by now.");
found->AddRef();
*aResult = found;
return NS_OK;
unfound:
%(unfound)s
}"""
_datatypes = """
%(type)s %(name)s;
"""
_actiontable_entry = """
{
&NS_GET_IID(%(iname)s),
%(enumtype)s
%(emitTable)s
},
"""
_actioncode_multi = """
switch (entry->action) {
%s
}
"""
_actioncode_multi_entry = """
case %(cname)s_%(enumtype)s:
%(setResult)s
"""
_nullcheck = """
exit_nullcheck_addref:
if (!found)
return NS_ERROR_OUT_OF_MEMORY;
"""
_refcounting = """
NS_IMPL_%(threadsafe)sADDREF(%(cname)s)
NS_IMPL_%(threadsafe)sRELEASE(%(cname)s)
"""
def __init__(self, cname, ifaces, emitrefcount=False,
threadsafe="", unfound=None, base=None):
self.cname = cname
self.emitrefcount = emitrefcount
self.threadsafe = threadsafe
self.base = base
self.ifaces = ifaces
self.unfound = unfound
# ensure that interfaces are not duplicated
ifdict = uniqdict()
for i, baselist in self.iter_all_ifaces():
ifdict[i.uuid.name] = i
def iter_self_and_bases(self):
"""yields (impl, baselist) for all bases"""
impl = self
baselist = []
while impl is not None:
baselist = list(baselist)
baselist.append(impl.cname)
yield impl, baselist
impl = impl.base
def iter_all_ifaces(self):
"""yields (iface, baselist) for all interfaces"""
for impl, baselist in self.iter_self_and_bases():
for i in impl.ifaces:
yield i, baselist
def output(self, fd):
unfound = None
for impl, baselist in self.iter_self_and_bases():
if impl.unfound is not None:
unfound = impl.unfound
break
if unfound is None:
unfound = self._default_unfound
needsnullcheck = False
actions = sets.Set()
types = uniqdict()
for i, baselist in self.iter_all_ifaces():
action = i.action
actions.add(action)
if action.needsnullcheck:
needsnullcheck = True
if action.datatype:
types[action.varname] = action.datatype
actionenums = ", ".join(["%s_%s" % (self.cname, action.enumtype)
for action in actions])
# types.ptr is explicitly first (for initialization), so delete it
types.pop('ptr', None)
actiondata = "".join([self._datatypes % {'type': type,
'name': name}
for name, type in types.iteritems()])
# list of (i, baselist)
ilist = [i for i in self.iter_all_ifaces()]
pseudoassertions = "".join([i.uuid.runtime_assertion()
for i, baselist in ilist])
bigindexfunc, bigitable = gqi([iface for iface, baselist in ilist], False)
littleindexfunc, littleitable = gqi([iface for iface, baselist in ilist], True)
# (falseval, trueval)[conditional expression] is the python
# ternary operator
biglookup = ",\n".join([(str(i), "0xFF")[i is None]
for i in bigitable])
littlelookup = ",\n".join([(str(i), "0xFF")[i is None]
for i in littleitable])
if len(actions) > 1:
actiontype = "%s_QIActionType action;" % self.cname
enumtype = "%(cname)s_%(enumtype)s,"
else:
actiontype = ""
enumtype = ""
actiontable = "".join([self._actiontable_entry %
{'enumtype': enumtype % {'cname': self.cname,
'enumtype': iface.action.enumtype},
'iname': iface.uuid.name,
'emitTable': iface.emitTable(self.cname, baselist)}
for iface, baselist in ilist])
if len(actions) == 1:
actioncode = iter(actions).next().setResult % {
'classname': self.cname
}
else:
actioncode = self._actioncode_multi % (
"".join([self._actioncode_multi_entry % {
'cname': self.cname,
'enumtype': action.enumtype,
'setResult': action.setResult % {'classname': self.cname}}
for action in actions])
)
if needsnullcheck:
nullcheck = self._nullcheck
else:
nullcheck = ""
print >>fd, self._qiimpl % {
'cname': self.cname,
'actionenums': actionenums,
'actiontype': actiontype,
'actiondata': actiondata,
'pseudoassertions': pseudoassertions,
'biglookup': biglookup,
'littlelookup': littlelookup,
'actiontable': actiontable,
'bigindexfunc': bigindexfunc,
'littleindexfunc': littleindexfunc,
'actioncode': actioncode,
'nullcheck': nullcheck,
'unfound': unfound
}
if self.emitrefcount:
print >>fd, self._refcounting % {
'threadsafe': self.threadsafe,
'cname': self.cname
}
class PointerAction(object):
enumtype = "STATIC_POINTER"
datatype = "void*"
varname = "ptr"
setResult = """*aResult = entry->data.ptr;
return NS_OK;"""
needsnullcheck = False
nsCycleCollectionParticipant = UUID("nsCycleCollectionParticipant", "9674489b-1f6f-4550-a730-ccaedd104cf9", pseudo=True)
class CCParticipantResponse(object):
action = PointerAction
def __init__(self, classname):
self.classname = classname
self.uuid = nsCycleCollectionParticipant
def emitTable(self, classname, baselist):
return "{ &NS_CYCLE_COLLECTION_NAME(%s) }" % classname
class TearoffResponse(object):
"""Because each tearoff is different, this response is its own action."""
datatype = None
needsnullcheck = True
def __init__(self, uuid, allocator):
self.setResult = """
found = static_cast<%s*>(%s);
goto exit_nullcheck_addref;
""" % (uuid.name, allocator)
self.uuid = uuid
self.action = self
self.enumtype = "NS_TEAROFF_%s" % uuid.name
def emitTable(self, classname, baselist):
return "{0}"
nsCycleCollectionISupports = UUID("nsCycleCollectionISupports", "c61eac14-5f7a-4481-965e-7eaa6effa85f", pseudo=True)
class CCSupportsAction(object):
enumtype = "CC_ISUPPORTS"
datatype = None
setResult = """
found = NS_CYCLE_COLLECTION_CLASSNAME(%(classname)s)::Upcast(this);
goto exit_nullcheck_addref;
"""
needsnullcheck = True
class CCISupportsResponse(object):
action = CCSupportsAction
def __init__(self, classname):
self.classname = classname
self.uuid = nsCycleCollectionISupports
def emitTable(self, classname, baselist):
return "{ 0 }"
nsIClassInfo = UUID("nsIClassInfo", "986c11d0-f340-11d4-9075-0010a4e73d9a", pseudo=True)
class DOMCIResult(object):
datatype = None
enumtype = "DOMCI"
needsnullcheck = True
def __init__(self, domciname):
self.action = self
self.uuid = nsIClassInfo
self.setResult = """
found = NS_GetDOMClassInfoInstance(eDOMClassInfo_%s_id);
goto exit_nullcheck_addref;
""" % domciname
def emitTable(self, classname, baselist):
return "{ 0 }"
class OffsetAction(object):
enumtype = "OFFSET_THIS_ADDREF"
datatype = "PRUint32"
varname = "offset"
setResult = """
found = reinterpret_cast<nsISupports*>(reinterpret_cast<char*>(this) + entry->data.offset);
goto exit_addref;
"""
needsnullcheck = False
class OffsetThisQIResponse(object):
action = OffsetAction
def __init__(self, uuid):
self.uuid = uuid
def emitTable(self, classname, baselist):
casted = basecast("((%s*) 0x1000)" % classname, baselist)
return """
{ reinterpret_cast<void*>(
reinterpret_cast<char*>(
static_cast<%(iname)s*>(%(casted)s)) -
reinterpret_cast<char*>(%(casted)s))
}""" % { 'casted': casted,
'iname': self.uuid.name }
class OffsetFutureThisQIResponse(OffsetThisQIResponse):
action = OffsetAction
def __init__(self, uuid):
self.uuid = uuid
def emitTable(self, classname, baselist):
casted = "((%s*) 0x1000)" % classname
return """
{ reinterpret_cast<void*>(
reinterpret_cast<char*>(
static_cast<%(iname)s*>(%(casted)s)) -
reinterpret_cast<char*>(%(casted)s))
}""" % { 'casted': casted,
'iname': self.uuid.name }
class OffsetThisQIResponseAmbiguous(OffsetThisQIResponse):
def __init__(self, uuid, intermediate):
self.intermediate = intermediate
OffsetThisQIResponse.__init__(self, uuid)
def emitTable(self, classname, baselist):
casted = basecast("((%s*) 0x1000)" % classname, baselist)
return """
{ reinterpret_cast<void*>(
reinterpret_cast<char*>(
static_cast<%(iname)s*>(
static_cast<%(intermediate)s*>(%(casted)s))) -
reinterpret_cast<char*>(%(casted)s))
}""" % \
{ 'casted': casted,
'iname': self.uuid.name,
'intermediate': self.intermediate.name }
class LiteralAction(object):
datatype = None
def __init__(self, uname, code):
self.enumtype = "LITERAL_CODE_%s" % uname
self.setResult = code
self.needsnullcheck = code.find('exit_nullcheck_addref') != -1
class LiteralResponse(object):
def __init__(self, action, uuid):
self.action = action
self.uuid = uuid
def emitTable(self, cname, baselist):
return "{0}"
_map_entry = re.compile(r'(?P<action>[A-Z_]+)\((?P<list>.*)\)$')
_split_commas = re.compile(r'\s*,\s*')
def build_map(f, cname, iids):
"""Parse the body of an NS_INTERFACE_MAP and return (members, unfound)"""
unfound = None
members = []
for line in f:
if line == 'NS_INTERFACE_MAP_END':
return (members, unfound)
if line == 'NS_INTERFACE_MAP_UNFOUND':
unfound = ''
for line in f:
if line == 'END':
break
unfound += line
continue
if line.find(')') == -1:
for line2 in f:
line += line2
if line.find(')') != -1:
break
m = _map_entry.match(line)
if m is None:
raise Exception("%s: Unparseable interface map entry" % f.loc())
items = _split_commas.split(m.group('list'))
action = m.group('action')
if action == 'NS_INTERFACE_MAP_ENTRY':
iname, = items
members.append(OffsetThisQIResponse(iids[iname]))
elif action == 'NS_FUTURE_INTERFACE_MAP_ENTRY':
iname, = items
members.append(OffsetFutureThisQIResponse(iids[iname]))
elif action == 'NS_INTERFACE_MAP_ENTRY_AMBIGUOUS':
iname, intermediate = items
members.append(OffsetThisQIResponseAmbiguous(iids[iname], iids[intermediate]))
elif action == 'NS_INTERFACE_MAP_ENTRY_TEAROFF':
iname, allocator = items
members.append(TearoffResponse(iids[iname], allocator))
elif action == 'NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO':
domciname, = items
members.append(DOMCIResult(domciname))
elif action == 'NS_INTERFACE_MAP_ENTRY_LITERAL':
code = ''
for line in f:
if line == 'END':
break
code += line
a = LiteralAction(items[0], code)
for iname in items:
iface = iids[iname]
members.append(LiteralResponse(a, iface))
else:
raise Exception("%s: Unexpected interface map entry" % f.loc())
raise Exception("%s: Unexpected EOF" % f.loc())
_import = re.compile(r'%(?P<type>import|import-idl)\s+(?:"|\<)(?P<filename>[a-z\.\-\_0-9]+)(?:"|\>)\s*$', re.I)
_impl_qi = re.compile(r'NS_IMPL_(?P<threadsafe>THREADSAFE_)?(?P<type>QUERY_INTERFACE|ISUPPORTS)(?:\d+)\((?P<bases>.*)\)\s*$')
_pseudoiid = re.compile(r'%pseudo-iid\s+(?P<name>[a-z_0-9]+)\s+(?P<iid>' + _uuid_pattern_string + r')\s*$', re.I)
_map_begin = re.compile(r'NS_INTERFACE_MAP_BEGIN(?P<cc>_CYCLE_COLLECTION)?\((?P<classname>[A-Za-z0-9+]+)(?:\s*,\s*(?P<base>[A-Za-z0-9+]+))?\)$')
def parsefile(file, fd, includedirs):
"""Parse a file, returning a (map of name->QIImpls, set of deps)
%{C++ blocks are printed immediately to fd."""
iids = uniqdict()
impls = uniqdict()
imported = uniqdict()
deps = sets.Set()
deps.add(file)
f = IterFile(file)
for line in f:
if len(line) == 0:
continue
if line == "%{C++":
for line in f:
if line == "%}":
break
print >>fd, line
continue
m = _pseudoiid.match(line)
if m is not None:
uuid = UUID(m.group('name'), m.group('iid'), pseudo=True)
iids[m.group('name')] = uuid
continue
m = _import.match(line)
if m is not None:
if m.group('type') == 'import':
newimpls, newdeps = parsefile(findfile(m.group('filename'), file, includedirs), fd, includedirs)
imported.update(newimpls)
deps |= newdeps
else:
newiids, newdeps = importidl(findfile(m.group('filename'), file, includedirs), includedirs)
iids.update(newiids)
deps |= newdeps
print >>fd, '#include "%s"' % m.group('filename').replace('.idl', '.h')
continue
if line.find('NS_IMPL_') != -1:
if line.find(')') == -1:
for follow in f:
line += follow
if follow.find(')') != -1:
break
if line.find(')') == -1:
raise Exception("%s: Unterminated NS_IMPL_ call" % f.loc())
m = _impl_qi.match(line)
if m is None:
raise Exception("%s: Unparseable NS_IMPL_ call" % f.loc())
bases = _split_commas.split(m.group('bases'))
cname = bases.pop(0)
baseuuids = [iids[name] for name in bases]
ifaces = [OffsetThisQIResponse(uuid) for uuid in baseuuids]
ifaces.append(OffsetThisQIResponseAmbiguous(iids['nsISupports'], ifaces[0].uuid))
q = QIImpl(cname, ifaces,
emitrefcount=(m.group('type') == 'ISUPPORTS'),
threadsafe=m.group('threadsafe') or '')
impls[cname] = q
continue
m = _map_begin.match(line)
if m is not None:
members, unfound = build_map(f, m.group('classname'), iids)
if m.group('cc') is not None:
members.append(CCParticipantResponse(m.group('classname')))
members.append(CCISupportsResponse(m.group('classname')))
base = None
if m.group('base') is not None:
if m.group('base') in impls:
base = impls[m.group('base')]
else:
base = imported[m.group('base')]
q = QIImpl(m.group('classname'), members, unfound=unfound, base=base)
impls[m.group('classname')] = q
continue
raise Exception("%s: unexpected input line" % f.loc())
return impls, deps
def main():
from optparse import OptionParser
o = OptionParser()
o.add_option("-I", action="append", dest="include_dirs", default=[],
help="Directory to search for included files.")
o.add_option("-D", dest="deps_file",
help="Write dependencies to a file")
o.add_option("-o", dest="out_file",
help="Write output to file. Required")
(options, files) = o.parse_args()
if options.out_file is None:
o.print_help()
sys.exit(1)
outfd = open(options.out_file, 'w')
deps = sets.Set()
for file in files:
impls, newdeps = parsefile(file, outfd, options.include_dirs)
for q in impls.itervalues():
q.output(outfd)
deps |= newdeps
if options.deps_file is not None:
depsfd = open(options.deps_file, 'w')
print >>depsfd, "%s: %s" % (options.out_file,
" \\\n ".join(deps))
if __name__ == '__main__':
main()