js/src/vm/make_opcode_doc.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/vm/make_opcode_doc.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,365 @@
     1.4 +#!/usr/bin/python -B
     1.5 +
     1.6 +""" Usage: make_opcode_doc.py PATH_TO_MOZILLA_CENTRAL
     1.7 +
     1.8 +    This script generates SpiderMonkey bytecode documentation
     1.9 +    from js/src/vm/Opcodes.h and js/src/vm/Xdr.h.
    1.10 +
    1.11 +    Output is written to stdout and should be pasted into the following
    1.12 +    MDN page:
    1.13 +    https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
    1.14 +"""
    1.15 +
    1.16 +from __future__ import print_function
    1.17 +import re
    1.18 +import sys
    1.19 +from xml.sax.saxutils import escape
    1.20 +
    1.21 +SOURCE_BASE = 'http://mxr.mozilla.org/mozilla-central/source'
    1.22 +
    1.23 +def error(message):
    1.24 +    print("Error: {message}".format(message=message), file=sys.stderr)
    1.25 +    #sys.exit(1) # uncomment when all opcodes documented
    1.26 +
    1.27 +def get_xdr_version(dir):
    1.28 +    version_pat = re.compile('XDR_BYTECODE_VERSION = uint32_t\(0xb973c0de - (\d+)\);')
    1.29 +
    1.30 +    version = ''
    1.31 +    with open('{dir}/js/src/vm/Xdr.h'.format(dir=dir), 'r') as f:
    1.32 +        data = f.read()
    1.33 +
    1.34 +    m = version_pat.search(data)
    1.35 +    if m:
    1.36 +        version = int(m.group(1))
    1.37 +
    1.38 +    return version
    1.39 +
    1.40 +quoted_pat = re.compile(r"([^A-Za-z0-9]|^)'([^']+)'")
    1.41 +js_pat = re.compile(r"([^A-Za-z0-9]|^)(JS[A-Z0-9_\*]+)")
    1.42 +def codify(text):
    1.43 +    text = re.sub(quoted_pat, '\\1<code>\\2</code>', text)
    1.44 +    text = re.sub(js_pat, '\\1<code>\\2</code>', text)
    1.45 +
    1.46 +    return text
    1.47 +
    1.48 +space_star_space_pat = re.compile('^\s*\* ?', re.M)
    1.49 +def get_comment_body(comment):
    1.50 +    return re.sub(space_star_space_pat, '', comment).split('\n')
    1.51 +
    1.52 +def parse_index(comment):
    1.53 +    index = []
    1.54 +    current_types = None
    1.55 +    category_name = ''
    1.56 +    category_pat = re.compile('\[([^\]]+)\]')
    1.57 +    for line in get_comment_body(comment):
    1.58 +        m = category_pat.search(line)
    1.59 +        if m:
    1.60 +            category_name = m.group(1)
    1.61 +            if category_name == 'Index':
    1.62 +                continue
    1.63 +            current_types = []
    1.64 +            index.append((category_name, current_types))
    1.65 +        else:
    1.66 +            type_name = line.strip()
    1.67 +            if type_name and current_types is not None:
    1.68 +                current_types.append((type_name, []))
    1.69 +
    1.70 +    return index
    1.71 +
    1.72 +class OpcodeInfo:
    1.73 +    def __init__(self):
    1.74 +        self.name = ''
    1.75 +        self.value = ''
    1.76 +        self.length = ''
    1.77 +        self.length_override = ''
    1.78 +        self.nuses = ''
    1.79 +        self.nuses_override = ''
    1.80 +        self.ndefs = ''
    1.81 +        self.ndefs_override = ''
    1.82 +        self.flags = ''
    1.83 +        self.operands = ''
    1.84 +        self.stack_uses = ''
    1.85 +        self.stack_defs = ''
    1.86 +
    1.87 +        self.desc = ''
    1.88 +
    1.89 +        self.category_name = ''
    1.90 +        self.type_name = ''
    1.91 +
    1.92 +        self.group = []
    1.93 +        self.sort_key = ''
    1.94 +
    1.95 +def find_by_name(list, name):
    1.96 +    for (n, body) in list:
    1.97 +        if n == name:
    1.98 +            return body
    1.99 +
   1.100 +    return None
   1.101 +
   1.102 +def add_to_index(index, opcode):
   1.103 +    types = find_by_name(index, opcode.category_name)
   1.104 +    if types is None:
   1.105 +        error("Category is not listed in index: "
   1.106 +              "{name}".format(name=opcode.category_name))
   1.107 +    opcodes = find_by_name(types, opcode.type_name)
   1.108 +    if opcodes is None:
   1.109 +        if opcode.type_name:
   1.110 +            error("Type is not listed in {category}: "
   1.111 +                  "{name}".format(category=opcode.category_name,
   1.112 +                                  name=opcode.type_name))
   1.113 +        types.append((opcode.type_name, [opcode]))
   1.114 +        return
   1.115 +
   1.116 +    opcodes.append(opcode)
   1.117 +
   1.118 +def format_desc(descs):
   1.119 +    current_type = ''
   1.120 +    desc = ''
   1.121 +    for (type, line) in descs:
   1.122 +        if type != current_type:
   1.123 +            if current_type:
   1.124 +                desc += '</{name}>\n'.format(name=current_type)
   1.125 +            current_type = type
   1.126 +            if type:
   1.127 +                desc += '<{name}>'.format(name=current_type)
   1.128 +        if current_type:
   1.129 +            desc += line + "\n"
   1.130 +    if current_type:
   1.131 +        desc += '</{name}>'.format(name=current_type)
   1.132 +
   1.133 +    return desc
   1.134 +
   1.135 +tag_pat = re.compile('^\s*[A-Za-z]+:\s*|\s*$')
   1.136 +def get_tag_value(line):
   1.137 +    return re.sub(tag_pat, '', line)
   1.138 +
   1.139 +def get_opcodes(dir):
   1.140 +    iter_pat = re.compile(r"/\*(.*?)\*/"  # either a documentation comment...
   1.141 +                          r"|"
   1.142 +                          r"macro\("      # or a macro(...) call
   1.143 +                                 r"([^,]+),\s*"     # op
   1.144 +                                 r"([0-9]+),\s*"    # val
   1.145 +                                 r"[^,]+,\s*"       # name
   1.146 +                                 r"[^,]+,\s*"       # image
   1.147 +                                 r"([0-9\-]+),\s*"  # length
   1.148 +                                 r"([0-9\-]+),\s*"  # nuses
   1.149 +                                 r"([0-9\-]+),\s*"  # ndefs
   1.150 +                                 r"([^\)]+)"        # format
   1.151 +                          r"\)", re.S)
   1.152 +    stack_pat = re.compile('^(.*?)\s*=>\s*(.*?)$')
   1.153 +
   1.154 +    index = []
   1.155 +
   1.156 +    opcode = OpcodeInfo()
   1.157 +    merged = opcode
   1.158 +
   1.159 +    with open('{dir}/js/src/vm/Opcodes.h'.format(dir=dir), 'r') as f:
   1.160 +        data = f.read()
   1.161 +
   1.162 +    for m in re.finditer(iter_pat, data):
   1.163 +        comment = m.group(1)
   1.164 +        name = m.group(2)
   1.165 +
   1.166 +        if comment:
   1.167 +            if '[Index]' in comment:
   1.168 +                index = parse_index(comment)
   1.169 +                continue
   1.170 +
   1.171 +            if 'Operands:' not in comment:
   1.172 +                continue
   1.173 +
   1.174 +            state = 'desc'
   1.175 +            stack = ''
   1.176 +            descs = []
   1.177 +
   1.178 +            for line in get_comment_body(comment):
   1.179 +                if line.startswith('  Category:'):
   1.180 +                    state = 'category'
   1.181 +                    opcode.category_name = get_tag_value(line)
   1.182 +                elif line.startswith('  Type:'):
   1.183 +                    state = 'type'
   1.184 +                    opcode.type_name = get_tag_value(line)
   1.185 +                elif line.startswith('  Operands:'):
   1.186 +                    state = 'operands'
   1.187 +                    opcode.operands = get_tag_value(line)
   1.188 +                elif line.startswith('  Stack:'):
   1.189 +                    state = 'stack'
   1.190 +                    stack = get_tag_value(line)
   1.191 +                elif line.startswith('  len:'):
   1.192 +                    state = 'len'
   1.193 +                    opcode.length_override = get_tag_value(line)
   1.194 +                elif line.startswith('  nuses:'):
   1.195 +                    state = 'nuses'
   1.196 +                    opcode.nuses_override = get_tag_value(line)
   1.197 +                elif line.startswith('  ndefs:'):
   1.198 +                    state = 'ndefs'
   1.199 +                    opcode.ndefs_override = get_tag_value(line)
   1.200 +                elif state == 'desc':
   1.201 +                    if line.startswith(' '):
   1.202 +                        descs.append(('pre', escape(line[1:])))
   1.203 +                    else:
   1.204 +                        line = line.strip()
   1.205 +                        if line == '':
   1.206 +                            descs.append(('', line))
   1.207 +                        else:
   1.208 +                            descs.append(('p', codify(escape(line))))
   1.209 +                elif line.startswith('  '):
   1.210 +                    if state == 'operands':
   1.211 +                        opcode.operands += line.strip()
   1.212 +                    elif state == 'stack':
   1.213 +                        stack += line.strip()
   1.214 +                    elif state == 'len':
   1.215 +                        opcode.length_override += line.strip()
   1.216 +                    elif state == 'nuses':
   1.217 +                        opcode.nuses_override += line.strip()
   1.218 +                    elif state == 'ndefs':
   1.219 +                        opcode.ndefs_override += line.strip()
   1.220 +
   1.221 +            opcode.desc = format_desc(descs)
   1.222 +
   1.223 +            m2 = stack_pat.search(stack)
   1.224 +            if m2:
   1.225 +                opcode.stack_uses = m2.group(1)
   1.226 +                opcode.stack_defs = m2.group(2)
   1.227 +
   1.228 +            merged = opcode
   1.229 +        elif name and not name.startswith('JSOP_UNUSED'):
   1.230 +            opcode.name = name
   1.231 +            opcode.value = int(m.group(3))
   1.232 +            opcode.length = m.group(4)
   1.233 +            opcode.nuses = m.group(5)
   1.234 +            opcode.ndefs = m.group(6)
   1.235 +
   1.236 +            flags = []
   1.237 +            for flag in m.group(7).split('|'):
   1.238 +                if flag != 'JOF_BYTE':
   1.239 +                    flags.append(flag.replace('JOF_', ''))
   1.240 +            opcode.flags = ', '.join(flags)
   1.241 +
   1.242 +            if merged == opcode:
   1.243 +                opcode.sort_key = opcode.name
   1.244 +                if opcode.category_name == '':
   1.245 +                    error("Category is not specified for "
   1.246 +                          "{name}".format(name=opcode.name))
   1.247 +                add_to_index(index, opcode)
   1.248 +            else:
   1.249 +                if merged.length != opcode.length:
   1.250 +                    error("length should be same for merged section: "
   1.251 +                          "{value1}({name1}) != "
   1.252 +                          "{value2}({name2})".format(name1=merged.name,
   1.253 +                                                     value1=merged.length,
   1.254 +                                                     name2=opcode.name,
   1.255 +                                                     value2=opcode.length))
   1.256 +                if merged.nuses != opcode.nuses:
   1.257 +                    error("nuses should be same for merged section: "
   1.258 +                          "{value1}({name1}) != "
   1.259 +                          "{value2}({name2})".format(name1=merged.name,
   1.260 +                                                     value1=merged.nuses,
   1.261 +                                                     name2=opcode.name,
   1.262 +                                                     value2=opcode.nuses))
   1.263 +                if merged.ndefs != opcode.ndefs:
   1.264 +                    error("ndefs should be same for merged section: "
   1.265 +                          "{value1}({name1}) != "
   1.266 +                          "{value2}({name2})".format(name1=merged.name,
   1.267 +                                                     value1=merged.ndefs,
   1.268 +                                                     name2=opcode.name,
   1.269 +                                                     value2=opcode.ndefs))
   1.270 +                merged.group.append(opcode)
   1.271 +                if opcode.name < merged.name:
   1.272 +                    merged.sort_key = opcode.name
   1.273 +
   1.274 +            opcode = OpcodeInfo()
   1.275 +
   1.276 +    return index
   1.277 +
   1.278 +def override(value, override_value):
   1.279 +    if override_value != '':
   1.280 +        return override_value
   1.281 +
   1.282 +    return value
   1.283 +
   1.284 +def format_flags(flags):
   1.285 +    if flags == '':
   1.286 +        return ''
   1.287 +
   1.288 +    return ' ({flags})'.format(flags=flags)
   1.289 +
   1.290 +def print_opcode(opcode):
   1.291 +    names_template = '{name} [-{nuses}, +{ndefs}]{flags}'
   1.292 +    names = map(lambda code: names_template.format(name=escape(code.name),
   1.293 +                                                   nuses=override(code.nuses,
   1.294 +                                                                  opcode.nuses_override),
   1.295 +                                                   ndefs=override(code.ndefs,
   1.296 +                                                                  opcode.ndefs_override),
   1.297 +                                                   flags=format_flags(code.flags)),
   1.298 +                sorted([opcode] + opcode.group,
   1.299 +                       key=lambda opcode: opcode.name))
   1.300 +    if len(opcode.group) == 0:
   1.301 +        values = ['{value} (0x{value:02x})'.format(value=opcode.value)]
   1.302 +    else:
   1.303 +        values_template = '{name}: {value} (0x{value:02x})'
   1.304 +        values = map(lambda code: values_template.format(name=escape(code.name),
   1.305 +                                                         value=code.value),
   1.306 +                    sorted([opcode] + opcode.group,
   1.307 +                           key=lambda opcode: opcode.name))
   1.308 +
   1.309 +    print("""<dt>{names}</dt>
   1.310 +<dd>
   1.311 +<table class="standard-table">
   1.312 +<tr><th>Value</th><td><code>{values}</code></td></tr>
   1.313 +<tr><th>Operands</th><td><code>{operands}</code></td></tr>
   1.314 +<tr><th>Length</th><td><code>{length}</code></td></tr>
   1.315 +<tr><th>Stack Uses</th><td><code>{stack_uses}</code></td></tr>
   1.316 +<tr><th>Stack Defs</th><td><code>{stack_defs}</code></td></tr>
   1.317 +</table>
   1.318 +
   1.319 +{desc}
   1.320 +</dd>
   1.321 +""".format(names='<br>'.join(names),
   1.322 +           values='<br>'.join(values),
   1.323 +           operands=escape(opcode.operands),
   1.324 +           length=escape(override(opcode.length,
   1.325 +                                  opcode.length_override)),
   1.326 +           stack_uses=escape(opcode.stack_uses),
   1.327 +           stack_defs=escape(opcode.stack_defs),
   1.328 +           desc=opcode.desc)) # desc is already escaped
   1.329 +
   1.330 +def make_element_id(name):
   1.331 +    return name.replace(' ', '-')
   1.332 +
   1.333 +def print_doc(version, index):
   1.334 +    print("""<h2 id="Bytecode_Listing">Bytecode Listing</h2>
   1.335 +
   1.336 +<p>This document is automatically generated from
   1.337 +<a href="{source_base}/js/src/vm/Opcodes.h">Opcodes.h</a> and
   1.338 +<a href="{source_base}/js/src/vm/Xdr.h">Xdr.h</a> by
   1.339 +<a href="{source_base}/js/src/vm/make_opcode_doc.py">make_opcode_doc.py</a>.</p>
   1.340 +
   1.341 +<p>Bytecode version: <code>{version}</code>
   1.342 +(<code>0x{actual_version:08x}</code>).</p>
   1.343 +""".format(source_base=SOURCE_BASE,
   1.344 +           version=version,
   1.345 +           actual_version=0xb973c0de - version))
   1.346 +
   1.347 +    for (category_name, types) in index:
   1.348 +        print('<h3 id="{id}">{name}</h3>'.format(name=category_name,
   1.349 +                                                 id=make_element_id(category_name)))
   1.350 +        for (type_name, opcodes) in types:
   1.351 +            if type_name:
   1.352 +                print('<h4 id="{id}">{name}</h4>'.format(name=type_name,
   1.353 +                                                         id=make_element_id(type_name)))
   1.354 +            print('<dl>')
   1.355 +            for opcode in sorted(opcodes,
   1.356 +                                 key=lambda opcode: opcode.sort_key):
   1.357 +                print_opcode(opcode)
   1.358 +            print('</dl>')
   1.359 +
   1.360 +if __name__ == '__main__':
   1.361 +    if len(sys.argv) < 2:
   1.362 +        print("Usage: make_opcode_doc.py PATH_TO_MOZILLA_CENTRAL",
   1.363 +              file=sys.stderr)
   1.364 +        sys.exit(1)
   1.365 +    dir = sys.argv[1]
   1.366 +    version = get_xdr_version(dir)
   1.367 +    index = get_opcodes(dir)
   1.368 +    print_doc(version, index)

mercurial