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

Bytecode Listing

+ +

This document is automatically generated from +Opcodes.h and +Xdr.h by +make_opcode_doc.py.

+ +

Bytecode version: {version} +(0x{actual_version:08x}).

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

{name}

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

{name}

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