|
1 #!/usr/bin/python -B |
|
2 |
|
3 """ Usage: make_opcode_doc.py PATH_TO_MOZILLA_CENTRAL |
|
4 |
|
5 This script generates SpiderMonkey bytecode documentation |
|
6 from js/src/vm/Opcodes.h and js/src/vm/Xdr.h. |
|
7 |
|
8 Output is written to stdout and should be pasted into the following |
|
9 MDN page: |
|
10 https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode |
|
11 """ |
|
12 |
|
13 from __future__ import print_function |
|
14 import re |
|
15 import sys |
|
16 from xml.sax.saxutils import escape |
|
17 |
|
18 SOURCE_BASE = 'http://mxr.mozilla.org/mozilla-central/source' |
|
19 |
|
20 def error(message): |
|
21 print("Error: {message}".format(message=message), file=sys.stderr) |
|
22 #sys.exit(1) # uncomment when all opcodes documented |
|
23 |
|
24 def get_xdr_version(dir): |
|
25 version_pat = re.compile('XDR_BYTECODE_VERSION = uint32_t\(0xb973c0de - (\d+)\);') |
|
26 |
|
27 version = '' |
|
28 with open('{dir}/js/src/vm/Xdr.h'.format(dir=dir), 'r') as f: |
|
29 data = f.read() |
|
30 |
|
31 m = version_pat.search(data) |
|
32 if m: |
|
33 version = int(m.group(1)) |
|
34 |
|
35 return version |
|
36 |
|
37 quoted_pat = re.compile(r"([^A-Za-z0-9]|^)'([^']+)'") |
|
38 js_pat = re.compile(r"([^A-Za-z0-9]|^)(JS[A-Z0-9_\*]+)") |
|
39 def codify(text): |
|
40 text = re.sub(quoted_pat, '\\1<code>\\2</code>', text) |
|
41 text = re.sub(js_pat, '\\1<code>\\2</code>', text) |
|
42 |
|
43 return text |
|
44 |
|
45 space_star_space_pat = re.compile('^\s*\* ?', re.M) |
|
46 def get_comment_body(comment): |
|
47 return re.sub(space_star_space_pat, '', comment).split('\n') |
|
48 |
|
49 def parse_index(comment): |
|
50 index = [] |
|
51 current_types = None |
|
52 category_name = '' |
|
53 category_pat = re.compile('\[([^\]]+)\]') |
|
54 for line in get_comment_body(comment): |
|
55 m = category_pat.search(line) |
|
56 if m: |
|
57 category_name = m.group(1) |
|
58 if category_name == 'Index': |
|
59 continue |
|
60 current_types = [] |
|
61 index.append((category_name, current_types)) |
|
62 else: |
|
63 type_name = line.strip() |
|
64 if type_name and current_types is not None: |
|
65 current_types.append((type_name, [])) |
|
66 |
|
67 return index |
|
68 |
|
69 class OpcodeInfo: |
|
70 def __init__(self): |
|
71 self.name = '' |
|
72 self.value = '' |
|
73 self.length = '' |
|
74 self.length_override = '' |
|
75 self.nuses = '' |
|
76 self.nuses_override = '' |
|
77 self.ndefs = '' |
|
78 self.ndefs_override = '' |
|
79 self.flags = '' |
|
80 self.operands = '' |
|
81 self.stack_uses = '' |
|
82 self.stack_defs = '' |
|
83 |
|
84 self.desc = '' |
|
85 |
|
86 self.category_name = '' |
|
87 self.type_name = '' |
|
88 |
|
89 self.group = [] |
|
90 self.sort_key = '' |
|
91 |
|
92 def find_by_name(list, name): |
|
93 for (n, body) in list: |
|
94 if n == name: |
|
95 return body |
|
96 |
|
97 return None |
|
98 |
|
99 def add_to_index(index, opcode): |
|
100 types = find_by_name(index, opcode.category_name) |
|
101 if types is None: |
|
102 error("Category is not listed in index: " |
|
103 "{name}".format(name=opcode.category_name)) |
|
104 opcodes = find_by_name(types, opcode.type_name) |
|
105 if opcodes is None: |
|
106 if opcode.type_name: |
|
107 error("Type is not listed in {category}: " |
|
108 "{name}".format(category=opcode.category_name, |
|
109 name=opcode.type_name)) |
|
110 types.append((opcode.type_name, [opcode])) |
|
111 return |
|
112 |
|
113 opcodes.append(opcode) |
|
114 |
|
115 def format_desc(descs): |
|
116 current_type = '' |
|
117 desc = '' |
|
118 for (type, line) in descs: |
|
119 if type != current_type: |
|
120 if current_type: |
|
121 desc += '</{name}>\n'.format(name=current_type) |
|
122 current_type = type |
|
123 if type: |
|
124 desc += '<{name}>'.format(name=current_type) |
|
125 if current_type: |
|
126 desc += line + "\n" |
|
127 if current_type: |
|
128 desc += '</{name}>'.format(name=current_type) |
|
129 |
|
130 return desc |
|
131 |
|
132 tag_pat = re.compile('^\s*[A-Za-z]+:\s*|\s*$') |
|
133 def get_tag_value(line): |
|
134 return re.sub(tag_pat, '', line) |
|
135 |
|
136 def get_opcodes(dir): |
|
137 iter_pat = re.compile(r"/\*(.*?)\*/" # either a documentation comment... |
|
138 r"|" |
|
139 r"macro\(" # or a macro(...) call |
|
140 r"([^,]+),\s*" # op |
|
141 r"([0-9]+),\s*" # val |
|
142 r"[^,]+,\s*" # name |
|
143 r"[^,]+,\s*" # image |
|
144 r"([0-9\-]+),\s*" # length |
|
145 r"([0-9\-]+),\s*" # nuses |
|
146 r"([0-9\-]+),\s*" # ndefs |
|
147 r"([^\)]+)" # format |
|
148 r"\)", re.S) |
|
149 stack_pat = re.compile('^(.*?)\s*=>\s*(.*?)$') |
|
150 |
|
151 index = [] |
|
152 |
|
153 opcode = OpcodeInfo() |
|
154 merged = opcode |
|
155 |
|
156 with open('{dir}/js/src/vm/Opcodes.h'.format(dir=dir), 'r') as f: |
|
157 data = f.read() |
|
158 |
|
159 for m in re.finditer(iter_pat, data): |
|
160 comment = m.group(1) |
|
161 name = m.group(2) |
|
162 |
|
163 if comment: |
|
164 if '[Index]' in comment: |
|
165 index = parse_index(comment) |
|
166 continue |
|
167 |
|
168 if 'Operands:' not in comment: |
|
169 continue |
|
170 |
|
171 state = 'desc' |
|
172 stack = '' |
|
173 descs = [] |
|
174 |
|
175 for line in get_comment_body(comment): |
|
176 if line.startswith(' Category:'): |
|
177 state = 'category' |
|
178 opcode.category_name = get_tag_value(line) |
|
179 elif line.startswith(' Type:'): |
|
180 state = 'type' |
|
181 opcode.type_name = get_tag_value(line) |
|
182 elif line.startswith(' Operands:'): |
|
183 state = 'operands' |
|
184 opcode.operands = get_tag_value(line) |
|
185 elif line.startswith(' Stack:'): |
|
186 state = 'stack' |
|
187 stack = get_tag_value(line) |
|
188 elif line.startswith(' len:'): |
|
189 state = 'len' |
|
190 opcode.length_override = get_tag_value(line) |
|
191 elif line.startswith(' nuses:'): |
|
192 state = 'nuses' |
|
193 opcode.nuses_override = get_tag_value(line) |
|
194 elif line.startswith(' ndefs:'): |
|
195 state = 'ndefs' |
|
196 opcode.ndefs_override = get_tag_value(line) |
|
197 elif state == 'desc': |
|
198 if line.startswith(' '): |
|
199 descs.append(('pre', escape(line[1:]))) |
|
200 else: |
|
201 line = line.strip() |
|
202 if line == '': |
|
203 descs.append(('', line)) |
|
204 else: |
|
205 descs.append(('p', codify(escape(line)))) |
|
206 elif line.startswith(' '): |
|
207 if state == 'operands': |
|
208 opcode.operands += line.strip() |
|
209 elif state == 'stack': |
|
210 stack += line.strip() |
|
211 elif state == 'len': |
|
212 opcode.length_override += line.strip() |
|
213 elif state == 'nuses': |
|
214 opcode.nuses_override += line.strip() |
|
215 elif state == 'ndefs': |
|
216 opcode.ndefs_override += line.strip() |
|
217 |
|
218 opcode.desc = format_desc(descs) |
|
219 |
|
220 m2 = stack_pat.search(stack) |
|
221 if m2: |
|
222 opcode.stack_uses = m2.group(1) |
|
223 opcode.stack_defs = m2.group(2) |
|
224 |
|
225 merged = opcode |
|
226 elif name and not name.startswith('JSOP_UNUSED'): |
|
227 opcode.name = name |
|
228 opcode.value = int(m.group(3)) |
|
229 opcode.length = m.group(4) |
|
230 opcode.nuses = m.group(5) |
|
231 opcode.ndefs = m.group(6) |
|
232 |
|
233 flags = [] |
|
234 for flag in m.group(7).split('|'): |
|
235 if flag != 'JOF_BYTE': |
|
236 flags.append(flag.replace('JOF_', '')) |
|
237 opcode.flags = ', '.join(flags) |
|
238 |
|
239 if merged == opcode: |
|
240 opcode.sort_key = opcode.name |
|
241 if opcode.category_name == '': |
|
242 error("Category is not specified for " |
|
243 "{name}".format(name=opcode.name)) |
|
244 add_to_index(index, opcode) |
|
245 else: |
|
246 if merged.length != opcode.length: |
|
247 error("length should be same for merged section: " |
|
248 "{value1}({name1}) != " |
|
249 "{value2}({name2})".format(name1=merged.name, |
|
250 value1=merged.length, |
|
251 name2=opcode.name, |
|
252 value2=opcode.length)) |
|
253 if merged.nuses != opcode.nuses: |
|
254 error("nuses should be same for merged section: " |
|
255 "{value1}({name1}) != " |
|
256 "{value2}({name2})".format(name1=merged.name, |
|
257 value1=merged.nuses, |
|
258 name2=opcode.name, |
|
259 value2=opcode.nuses)) |
|
260 if merged.ndefs != opcode.ndefs: |
|
261 error("ndefs should be same for merged section: " |
|
262 "{value1}({name1}) != " |
|
263 "{value2}({name2})".format(name1=merged.name, |
|
264 value1=merged.ndefs, |
|
265 name2=opcode.name, |
|
266 value2=opcode.ndefs)) |
|
267 merged.group.append(opcode) |
|
268 if opcode.name < merged.name: |
|
269 merged.sort_key = opcode.name |
|
270 |
|
271 opcode = OpcodeInfo() |
|
272 |
|
273 return index |
|
274 |
|
275 def override(value, override_value): |
|
276 if override_value != '': |
|
277 return override_value |
|
278 |
|
279 return value |
|
280 |
|
281 def format_flags(flags): |
|
282 if flags == '': |
|
283 return '' |
|
284 |
|
285 return ' ({flags})'.format(flags=flags) |
|
286 |
|
287 def print_opcode(opcode): |
|
288 names_template = '{name} [-{nuses}, +{ndefs}]{flags}' |
|
289 names = map(lambda code: names_template.format(name=escape(code.name), |
|
290 nuses=override(code.nuses, |
|
291 opcode.nuses_override), |
|
292 ndefs=override(code.ndefs, |
|
293 opcode.ndefs_override), |
|
294 flags=format_flags(code.flags)), |
|
295 sorted([opcode] + opcode.group, |
|
296 key=lambda opcode: opcode.name)) |
|
297 if len(opcode.group) == 0: |
|
298 values = ['{value} (0x{value:02x})'.format(value=opcode.value)] |
|
299 else: |
|
300 values_template = '{name}: {value} (0x{value:02x})' |
|
301 values = map(lambda code: values_template.format(name=escape(code.name), |
|
302 value=code.value), |
|
303 sorted([opcode] + opcode.group, |
|
304 key=lambda opcode: opcode.name)) |
|
305 |
|
306 print("""<dt>{names}</dt> |
|
307 <dd> |
|
308 <table class="standard-table"> |
|
309 <tr><th>Value</th><td><code>{values}</code></td></tr> |
|
310 <tr><th>Operands</th><td><code>{operands}</code></td></tr> |
|
311 <tr><th>Length</th><td><code>{length}</code></td></tr> |
|
312 <tr><th>Stack Uses</th><td><code>{stack_uses}</code></td></tr> |
|
313 <tr><th>Stack Defs</th><td><code>{stack_defs}</code></td></tr> |
|
314 </table> |
|
315 |
|
316 {desc} |
|
317 </dd> |
|
318 """.format(names='<br>'.join(names), |
|
319 values='<br>'.join(values), |
|
320 operands=escape(opcode.operands), |
|
321 length=escape(override(opcode.length, |
|
322 opcode.length_override)), |
|
323 stack_uses=escape(opcode.stack_uses), |
|
324 stack_defs=escape(opcode.stack_defs), |
|
325 desc=opcode.desc)) # desc is already escaped |
|
326 |
|
327 def make_element_id(name): |
|
328 return name.replace(' ', '-') |
|
329 |
|
330 def print_doc(version, index): |
|
331 print("""<h2 id="Bytecode_Listing">Bytecode Listing</h2> |
|
332 |
|
333 <p>This document is automatically generated from |
|
334 <a href="{source_base}/js/src/vm/Opcodes.h">Opcodes.h</a> and |
|
335 <a href="{source_base}/js/src/vm/Xdr.h">Xdr.h</a> by |
|
336 <a href="{source_base}/js/src/vm/make_opcode_doc.py">make_opcode_doc.py</a>.</p> |
|
337 |
|
338 <p>Bytecode version: <code>{version}</code> |
|
339 (<code>0x{actual_version:08x}</code>).</p> |
|
340 """.format(source_base=SOURCE_BASE, |
|
341 version=version, |
|
342 actual_version=0xb973c0de - version)) |
|
343 |
|
344 for (category_name, types) in index: |
|
345 print('<h3 id="{id}">{name}</h3>'.format(name=category_name, |
|
346 id=make_element_id(category_name))) |
|
347 for (type_name, opcodes) in types: |
|
348 if type_name: |
|
349 print('<h4 id="{id}">{name}</h4>'.format(name=type_name, |
|
350 id=make_element_id(type_name))) |
|
351 print('<dl>') |
|
352 for opcode in sorted(opcodes, |
|
353 key=lambda opcode: opcode.sort_key): |
|
354 print_opcode(opcode) |
|
355 print('</dl>') |
|
356 |
|
357 if __name__ == '__main__': |
|
358 if len(sys.argv) < 2: |
|
359 print("Usage: make_opcode_doc.py PATH_TO_MOZILLA_CENTRAL", |
|
360 file=sys.stderr) |
|
361 sys.exit(1) |
|
362 dir = sys.argv[1] |
|
363 version = get_xdr_version(dir) |
|
364 index = get_opcodes(dir) |
|
365 print_doc(version, index) |