michael@0: #!/usr/bin/python -B
michael@0:
michael@0: """ Usage: make_opcode_doc.py PATH_TO_MOZILLA_CENTRAL
michael@0:
michael@0: This script generates SpiderMonkey bytecode documentation
michael@0: from js/src/vm/Opcodes.h and js/src/vm/Xdr.h.
michael@0:
michael@0: Output is written to stdout and should be pasted into the following
michael@0: MDN page:
michael@0: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
michael@0: """
michael@0:
michael@0: from __future__ import print_function
michael@0: import re
michael@0: import sys
michael@0: from xml.sax.saxutils import escape
michael@0:
michael@0: SOURCE_BASE = 'http://mxr.mozilla.org/mozilla-central/source'
michael@0:
michael@0: def error(message):
michael@0: print("Error: {message}".format(message=message), file=sys.stderr)
michael@0: #sys.exit(1) # uncomment when all opcodes documented
michael@0:
michael@0: def get_xdr_version(dir):
michael@0: version_pat = re.compile('XDR_BYTECODE_VERSION = uint32_t\(0xb973c0de - (\d+)\);')
michael@0:
michael@0: version = ''
michael@0: with open('{dir}/js/src/vm/Xdr.h'.format(dir=dir), 'r') as f:
michael@0: data = f.read()
michael@0:
michael@0: m = version_pat.search(data)
michael@0: if m:
michael@0: version = int(m.group(1))
michael@0:
michael@0: return version
michael@0:
michael@0: quoted_pat = re.compile(r"([^A-Za-z0-9]|^)'([^']+)'")
michael@0: js_pat = re.compile(r"([^A-Za-z0-9]|^)(JS[A-Z0-9_\*]+)")
michael@0: def codify(text):
michael@0: text = re.sub(quoted_pat, '\\1\\2
', text)
michael@0: text = re.sub(js_pat, '\\1\\2
', text)
michael@0:
michael@0: return text
michael@0:
michael@0: space_star_space_pat = re.compile('^\s*\* ?', re.M)
michael@0: def get_comment_body(comment):
michael@0: return re.sub(space_star_space_pat, '', comment).split('\n')
michael@0:
michael@0: def parse_index(comment):
michael@0: index = []
michael@0: current_types = None
michael@0: category_name = ''
michael@0: category_pat = re.compile('\[([^\]]+)\]')
michael@0: for line in get_comment_body(comment):
michael@0: m = category_pat.search(line)
michael@0: if m:
michael@0: category_name = m.group(1)
michael@0: if category_name == 'Index':
michael@0: continue
michael@0: current_types = []
michael@0: index.append((category_name, current_types))
michael@0: else:
michael@0: type_name = line.strip()
michael@0: if type_name and current_types is not None:
michael@0: current_types.append((type_name, []))
michael@0:
michael@0: return index
michael@0:
michael@0: class OpcodeInfo:
michael@0: def __init__(self):
michael@0: self.name = ''
michael@0: self.value = ''
michael@0: self.length = ''
michael@0: self.length_override = ''
michael@0: self.nuses = ''
michael@0: self.nuses_override = ''
michael@0: self.ndefs = ''
michael@0: self.ndefs_override = ''
michael@0: self.flags = ''
michael@0: self.operands = ''
michael@0: self.stack_uses = ''
michael@0: self.stack_defs = ''
michael@0:
michael@0: self.desc = ''
michael@0:
michael@0: self.category_name = ''
michael@0: self.type_name = ''
michael@0:
michael@0: self.group = []
michael@0: self.sort_key = ''
michael@0:
michael@0: def find_by_name(list, name):
michael@0: for (n, body) in list:
michael@0: if n == name:
michael@0: return body
michael@0:
michael@0: return None
michael@0:
michael@0: def add_to_index(index, opcode):
michael@0: types = find_by_name(index, opcode.category_name)
michael@0: if types is None:
michael@0: error("Category is not listed in index: "
michael@0: "{name}".format(name=opcode.category_name))
michael@0: opcodes = find_by_name(types, opcode.type_name)
michael@0: if opcodes is None:
michael@0: if opcode.type_name:
michael@0: error("Type is not listed in {category}: "
michael@0: "{name}".format(category=opcode.category_name,
michael@0: name=opcode.type_name))
michael@0: types.append((opcode.type_name, [opcode]))
michael@0: return
michael@0:
michael@0: opcodes.append(opcode)
michael@0:
michael@0: def format_desc(descs):
michael@0: current_type = ''
michael@0: desc = ''
michael@0: for (type, line) in descs:
michael@0: if type != current_type:
michael@0: if current_type:
michael@0: desc += '{name}>\n'.format(name=current_type)
michael@0: current_type = type
michael@0: if type:
michael@0: desc += '<{name}>'.format(name=current_type)
michael@0: if current_type:
michael@0: desc += line + "\n"
michael@0: if current_type:
michael@0: desc += '{name}>'.format(name=current_type)
michael@0:
michael@0: return desc
michael@0:
michael@0: tag_pat = re.compile('^\s*[A-Za-z]+:\s*|\s*$')
michael@0: def get_tag_value(line):
michael@0: return re.sub(tag_pat, '', line)
michael@0:
michael@0: def get_opcodes(dir):
michael@0: iter_pat = re.compile(r"/\*(.*?)\*/" # either a documentation comment...
michael@0: r"|"
michael@0: r"macro\(" # or a macro(...) call
michael@0: r"([^,]+),\s*" # op
michael@0: r"([0-9]+),\s*" # val
michael@0: r"[^,]+,\s*" # name
michael@0: r"[^,]+,\s*" # image
michael@0: r"([0-9\-]+),\s*" # length
michael@0: r"([0-9\-]+),\s*" # nuses
michael@0: r"([0-9\-]+),\s*" # ndefs
michael@0: r"([^\)]+)" # format
michael@0: r"\)", re.S)
michael@0: stack_pat = re.compile('^(.*?)\s*=>\s*(.*?)$')
michael@0:
michael@0: index = []
michael@0:
michael@0: opcode = OpcodeInfo()
michael@0: merged = opcode
michael@0:
michael@0: with open('{dir}/js/src/vm/Opcodes.h'.format(dir=dir), 'r') as f:
michael@0: data = f.read()
michael@0:
michael@0: for m in re.finditer(iter_pat, data):
michael@0: comment = m.group(1)
michael@0: name = m.group(2)
michael@0:
michael@0: if comment:
michael@0: if '[Index]' in comment:
michael@0: index = parse_index(comment)
michael@0: continue
michael@0:
michael@0: if 'Operands:' not in comment:
michael@0: continue
michael@0:
michael@0: state = 'desc'
michael@0: stack = ''
michael@0: descs = []
michael@0:
michael@0: for line in get_comment_body(comment):
michael@0: if line.startswith(' Category:'):
michael@0: state = 'category'
michael@0: opcode.category_name = get_tag_value(line)
michael@0: elif line.startswith(' Type:'):
michael@0: state = 'type'
michael@0: opcode.type_name = get_tag_value(line)
michael@0: elif line.startswith(' Operands:'):
michael@0: state = 'operands'
michael@0: opcode.operands = get_tag_value(line)
michael@0: elif line.startswith(' Stack:'):
michael@0: state = 'stack'
michael@0: stack = get_tag_value(line)
michael@0: elif line.startswith(' len:'):
michael@0: state = 'len'
michael@0: opcode.length_override = get_tag_value(line)
michael@0: elif line.startswith(' nuses:'):
michael@0: state = 'nuses'
michael@0: opcode.nuses_override = get_tag_value(line)
michael@0: elif line.startswith(' ndefs:'):
michael@0: state = 'ndefs'
michael@0: opcode.ndefs_override = get_tag_value(line)
michael@0: elif state == 'desc':
michael@0: if line.startswith(' '):
michael@0: descs.append(('pre', escape(line[1:])))
michael@0: else:
michael@0: line = line.strip()
michael@0: if line == '':
michael@0: descs.append(('', line))
michael@0: else:
michael@0: descs.append(('p', codify(escape(line))))
michael@0: elif line.startswith(' '):
michael@0: if state == 'operands':
michael@0: opcode.operands += line.strip()
michael@0: elif state == 'stack':
michael@0: stack += line.strip()
michael@0: elif state == 'len':
michael@0: opcode.length_override += line.strip()
michael@0: elif state == 'nuses':
michael@0: opcode.nuses_override += line.strip()
michael@0: elif state == 'ndefs':
michael@0: opcode.ndefs_override += line.strip()
michael@0:
michael@0: opcode.desc = format_desc(descs)
michael@0:
michael@0: m2 = stack_pat.search(stack)
michael@0: if m2:
michael@0: opcode.stack_uses = m2.group(1)
michael@0: opcode.stack_defs = m2.group(2)
michael@0:
michael@0: merged = opcode
michael@0: elif name and not name.startswith('JSOP_UNUSED'):
michael@0: opcode.name = name
michael@0: opcode.value = int(m.group(3))
michael@0: opcode.length = m.group(4)
michael@0: opcode.nuses = m.group(5)
michael@0: opcode.ndefs = m.group(6)
michael@0:
michael@0: flags = []
michael@0: for flag in m.group(7).split('|'):
michael@0: if flag != 'JOF_BYTE':
michael@0: flags.append(flag.replace('JOF_', ''))
michael@0: opcode.flags = ', '.join(flags)
michael@0:
michael@0: if merged == opcode:
michael@0: opcode.sort_key = opcode.name
michael@0: if opcode.category_name == '':
michael@0: error("Category is not specified for "
michael@0: "{name}".format(name=opcode.name))
michael@0: add_to_index(index, opcode)
michael@0: else:
michael@0: if merged.length != opcode.length:
michael@0: error("length should be same for merged section: "
michael@0: "{value1}({name1}) != "
michael@0: "{value2}({name2})".format(name1=merged.name,
michael@0: value1=merged.length,
michael@0: name2=opcode.name,
michael@0: value2=opcode.length))
michael@0: if merged.nuses != opcode.nuses:
michael@0: error("nuses should be same for merged section: "
michael@0: "{value1}({name1}) != "
michael@0: "{value2}({name2})".format(name1=merged.name,
michael@0: value1=merged.nuses,
michael@0: name2=opcode.name,
michael@0: value2=opcode.nuses))
michael@0: if merged.ndefs != opcode.ndefs:
michael@0: error("ndefs should be same for merged section: "
michael@0: "{value1}({name1}) != "
michael@0: "{value2}({name2})".format(name1=merged.name,
michael@0: value1=merged.ndefs,
michael@0: name2=opcode.name,
michael@0: value2=opcode.ndefs))
michael@0: merged.group.append(opcode)
michael@0: if opcode.name < merged.name:
michael@0: merged.sort_key = opcode.name
michael@0:
michael@0: opcode = OpcodeInfo()
michael@0:
michael@0: return index
michael@0:
michael@0: def override(value, override_value):
michael@0: if override_value != '':
michael@0: return override_value
michael@0:
michael@0: return value
michael@0:
michael@0: def format_flags(flags):
michael@0: if flags == '':
michael@0: return ''
michael@0:
michael@0: return ' ({flags})'.format(flags=flags)
michael@0:
michael@0: def print_opcode(opcode):
michael@0: names_template = '{name} [-{nuses}, +{ndefs}]{flags}'
michael@0: names = map(lambda code: names_template.format(name=escape(code.name),
michael@0: nuses=override(code.nuses,
michael@0: opcode.nuses_override),
michael@0: ndefs=override(code.ndefs,
michael@0: opcode.ndefs_override),
michael@0: flags=format_flags(code.flags)),
michael@0: sorted([opcode] + opcode.group,
michael@0: key=lambda opcode: opcode.name))
michael@0: if len(opcode.group) == 0:
michael@0: values = ['{value} (0x{value:02x})'.format(value=opcode.value)]
michael@0: else:
michael@0: values_template = '{name}: {value} (0x{value:02x})'
michael@0: values = map(lambda code: values_template.format(name=escape(code.name),
michael@0: value=code.value),
michael@0: sorted([opcode] + opcode.group,
michael@0: key=lambda opcode: opcode.name))
michael@0:
michael@0: print("""
Value | {values} |
---|---|
Operands | {operands} |
Length | {length} |
Stack Uses | {stack_uses} |
Stack Defs | {stack_defs} |
This document is automatically generated from michael@0: Opcodes.h and michael@0: Xdr.h by michael@0: make_opcode_doc.py.
michael@0: michael@0:Bytecode version: {version}
michael@0: (0x{actual_version:08x}
).