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 += '\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 += ''.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("""
{names}
michael@0:
michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0:
Value{values}
Operands{operands}
Length{length}
Stack Uses{stack_uses}
Stack Defs{stack_defs}
michael@0: michael@0: {desc} michael@0:
michael@0: """.format(names='
'.join(names), michael@0: values='
'.join(values), michael@0: operands=escape(opcode.operands), michael@0: length=escape(override(opcode.length, michael@0: opcode.length_override)), michael@0: stack_uses=escape(opcode.stack_uses), michael@0: stack_defs=escape(opcode.stack_defs), michael@0: desc=opcode.desc)) # desc is already escaped michael@0: michael@0: def make_element_id(name): michael@0: return name.replace(' ', '-') michael@0: michael@0: def print_doc(version, index): michael@0: print("""

Bytecode Listing

michael@0: michael@0:

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}).

michael@0: """.format(source_base=SOURCE_BASE, michael@0: version=version, michael@0: actual_version=0xb973c0de - version)) michael@0: michael@0: for (category_name, types) in index: michael@0: print('

{name}

'.format(name=category_name, michael@0: id=make_element_id(category_name))) michael@0: for (type_name, opcodes) in types: michael@0: if type_name: michael@0: print('

{name}

'.format(name=type_name, michael@0: id=make_element_id(type_name))) michael@0: print('
') michael@0: for opcode in sorted(opcodes, michael@0: key=lambda opcode: opcode.sort_key): michael@0: print_opcode(opcode) michael@0: print('
') michael@0: michael@0: if __name__ == '__main__': michael@0: if len(sys.argv) < 2: michael@0: print("Usage: make_opcode_doc.py PATH_TO_MOZILLA_CENTRAL", michael@0: file=sys.stderr) michael@0: sys.exit(1) michael@0: dir = sys.argv[1] michael@0: version = get_xdr_version(dir) michael@0: index = get_opcodes(dir) michael@0: print_doc(version, index)