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)