1#!/usr/bin/env python3 -B
2# This Source Code Form is subject to the terms of the Mozilla Public
3# License, v. 2.0. If a copy of the MPL was not distributed with this file,
4# You can obtain one at http://mozilla.org/MPL/2.0/.
5
6
7""" Usage: python make_opcode_doc.py
8
9    This script generates SpiderMonkey bytecode documentation
10    from js/src/vm/Opcodes.h.
11
12    Output is written to stdout and should be pasted into the following
13    MDN page:
14    https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
15"""
16
17import sys
18import os
19
20
21# Allow this script to be run from anywhere.
22this_dir = os.path.dirname(os.path.realpath(__file__))
23sys.path.insert(0, this_dir)
24
25
26import jsopcode
27from xml.sax.saxutils import escape
28
29
30try:
31    import markdown
32except ModuleNotFoundError as exc:
33    if exc.name == "markdown":
34        # Right, most people won't have python-markdown installed. Suggest the
35        # most likely path to getting this running.
36        print("Failed to import markdown: " + exc.msg, file=sys.stderr)
37        if os.path.exists(os.path.join(this_dir, "venv")):
38            print(
39                "It looks like you previously created a virtualenv here. Try this:\n"
40                "    . venv/bin/activate",
41                file=sys.stderr,
42            )
43            sys.exit(1)
44        print(
45            "Try this:\n"
46            "    pip3 install markdown\n"
47            "Or, if you want to avoid installing things globally:\n"
48            "    python3 -m venv venv && . venv/bin/activate && pip3 install markdown",
49            file=sys.stderr,
50        )
51        sys.exit(1)
52    raise exc
53except ImportError as exc:
54    # Oh no! Markdown failed to load. Check for a specific known issue.
55    if exc.msg.startswith("bad magic number in 'opcode'") and os.path.isfile(
56        os.path.join(this_dir, "opcode.pyc")
57    ):
58        print(
59            "Failed to import markdown due to bug 1506380.\n"
60            "This is dumb--it's an old Python cache file in your directory. Try this:\n"
61            "    rm " + this_dir + "/opcode.pyc\n"
62            "The file is obsolete since November 2018.",
63            file=sys.stderr,
64        )
65        sys.exit(1)
66    raise exc
67
68
69SOURCE_BASE = "https://searchfox.org/mozilla-central/source"
70
71FORMAT_TO_IGNORE = {
72    "JOF_BYTE",
73    "JOF_UINT8",
74    "JOF_UINT16",
75    "JOF_UINT24",
76    "JOF_UINT32",
77    "JOF_INT8",
78    "JOF_INT32",
79    "JOF_TABLESWITCH",
80    "JOF_REGEXP",
81    "JOF_DOUBLE",
82    "JOF_LOOPHEAD",
83    "JOF_BIGINT",
84}
85
86
87def format_format(format):
88    format = [flag for flag in format if flag not in FORMAT_TO_IGNORE]
89    if len(format) == 0:
90        return ""
91    return "<div>Format: {format}</div>\n".format(format=", ".join(format))
92
93
94def maybe_escape(value, format_str, fallback=""):
95    if value:
96        return format_str.format(escape(value))
97    return fallback
98
99
100OPCODE_FORMAT = """\
101<dt id="{id}">{names}</dt>
102<dd>
103{operands}{stack}{desc}
104{format}</dd>
105"""
106
107
108def print_opcode(opcode):
109    opcodes = [opcode] + opcode.group
110    names = ", ".join(maybe_escape(code.op, "<code>{}</code>") for code in opcodes)
111    operands = maybe_escape(opcode.operands, "<div>Operands: <code>({})</code></div>\n")
112    stack_uses = maybe_escape(opcode.stack_uses, "<code>{}</code> ")
113    stack_defs = maybe_escape(opcode.stack_defs, " <code>{}</code>")
114    if stack_uses or stack_defs:
115        stack = "<div>Stack: {}&rArr;{}</div>\n".format(stack_uses, stack_defs)
116    else:
117        stack = ""
118
119    print(
120        OPCODE_FORMAT.format(
121            id=opcodes[0].op,
122            names=names,
123            operands=operands,
124            stack=stack,
125            desc=markdown.markdown(opcode.desc),
126            format=format_format(opcode.format_),
127        )
128    )
129
130
131id_cache = dict()
132id_count = dict()
133
134
135def make_element_id(category, type=""):
136    key = "{}:{}".format(category, type)
137    if key in id_cache:
138        return id_cache[key]
139
140    if type == "":
141        id = category.replace(" ", "_")
142    else:
143        id = type.replace(" ", "_")
144
145    if id in id_count:
146        id_count[id] += 1
147        id = "{}_{}".format(id, id_count[id])
148    else:
149        id_count[id] = 1
150
151    id_cache[key] = id
152    return id
153
154
155def print_doc(index):
156    print(
157        """<div>{{{{SpiderMonkeySidebar("Internals")}}}}</div>
158
159<h2 id="Bytecode_Listing">Bytecode Listing</h2>
160
161<p>This document is automatically generated from
162<a href="{source_base}/js/src/vm/Opcodes.h">Opcodes.h</a> by
163<a href="{source_base}/js/src/vm/make_opcode_doc.py">make_opcode_doc.py</a>.</p>
164""".format(
165            source_base=SOURCE_BASE
166        )
167    )
168
169    for (category_name, types) in index:
170        print(
171            '<h3 id="{id}">{name}</h3>'.format(
172                name=category_name, id=make_element_id(category_name)
173            )
174        )
175        for (type_name, opcodes) in types:
176            if type_name:
177                print(
178                    '<h4 id="{id}">{name}</h4>'.format(
179                        name=type_name, id=make_element_id(category_name, type_name)
180                    )
181                )
182            print("<dl>")
183            for opcode in opcodes:
184                print_opcode(opcode)
185            print("</dl>")
186
187
188if __name__ == "__main__":
189    if len(sys.argv) != 1:
190        print("Usage: mach python make_opcode_doc.py", file=sys.stderr)
191        sys.exit(1)
192    js_src_vm_dir = os.path.dirname(os.path.realpath(__file__))
193    root_dir = os.path.abspath(os.path.join(js_src_vm_dir, "..", "..", ".."))
194
195    index, _ = jsopcode.get_opcodes(root_dir)
196    print_doc(index)
197