Merge mozilla-central and tracemonkey. (a=blockers)
[mozilla-central.git] / js / src / imacro_asm.py
blob2e85e5543f8dcb5afe7e3a782d0774d5964d2cb5
1 #!/usr/bin/env python
2 # -*- Mode: Python; tab-width: 4; indent-tabs-mode: nil -*-
3 # ***** BEGIN LICENSE BLOCK *****
4 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 # The contents of this file are subject to the Mozilla Public License Version
7 # 1.1 (the "License"); you may not use this file except in compliance with
8 # the License. You may obtain a copy of the License at
9 # http://www.mozilla.org/MPL/
11 # Software distributed under the License is distributed on an "AS IS" basis,
12 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 # for the specific language governing rights and limitations under the
14 # License.
16 # The Original Code is the TraceMonkey IMacro Assembler.
18 # The Initial Developer of the Original Code is
19 # Brendan Eich <brendan@mozilla.org>.
20 # Portions created by the Initial Developer are Copyright (C) 2008
21 # the Initial Developer. All Rights Reserved.
23 # Contributor(s):
25 # Alternatively, the contents of this file may be used under the terms of
26 # either the GNU General Public License Version 2 or later (the "GPL"), or
27 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 # in which case the provisions of the GPL or the LGPL are applicable instead
29 # of those above. If you wish to allow use of your version of this file only
30 # under the terms of either the GPL or the LGPL, and not to allow others to
31 # use your version of this file under the terms of the MPL, indicate your
32 # decision by deleting the provisions above and replace them with the notice
33 # and other provisions required by the GPL or the LGPL. If you do not delete
34 # the provisions above, a recipient may use your version of this file under
35 # the terms of any one of the MPL, the GPL or the LGPL.
37 # ***** END LICENSE BLOCK *****
39 # An imacro (interpreter-macro) assembler in Python.
41 # Filename suffix conventions, used by Makefile.in rules:
42 # .jsasm SpiderMonkey JS assembly source, which could be input to other
43 # assemblers than imacro_asm.js, hence the generic suffix!
44 # .c.out C source output by imacro_asm.js
46 import re
47 import os
49 class Op:
50 def __init__(self, jsop, opcode, opname, opsrc, oplen, pops, pushes, precedence, flags):
51 self.jsop = jsop
52 self.opcode = opcode
53 self.opname = opname
54 self.opsrc = opsrc
55 self.oplen = oplen
56 self.pops = pops
57 self.pushes = pushes
58 self.precedence = precedence
59 self.flags = flags
61 def readFileLines(filename):
62 f = open(filename)
63 try:
64 return f.readlines()
65 finally:
66 f.close()
68 def load_ops(filename):
69 opdef_regexp = re.compile(r'''(?x)
70 ^ OPDEF \( (JSOP_\w+), \s* # op
71 ([0-9]+), \s* # val
72 ("[^"]+" | [\w_]+), \s* # name
73 ("[^"]+" | [\w_]+), \s* # image
74 (-1|[0-9]+), \s* # len
75 (-1|[0-9]+), \s* # uses
76 (-1|[0-9]+), \s* # defs
77 ([0-9]+), \s* # prec
78 ([\w_| ]+) \s* # format
79 \) \s* $''')
81 def decode_string_expr(expr):
82 if expr == 'NULL':
83 return None
84 if expr[0] == '"':
85 assert expr[-1] == '"'
86 return expr[1:-1]
87 assert expr.startswith('js_') and expr.endswith('_str')
88 return expr[3:-4]
90 opinfo = []
91 for lineno, line in enumerate(readFileLines(filename)):
92 if line.startswith('OPDEF'):
93 m = opdef_regexp.match(line)
94 if m is None:
95 raise ValueError("OPDEF line of wrong format in jsopcode.tbl at line %d" % (lineno + 1))
96 jsop, opcode, opname, opsrc, oplen, pops, pushes, precedence, flags = m.groups()
97 assert int(opcode) == len(opinfo)
98 opinfo.append(Op(jsop, int(opcode), decode_string_expr(opname),
99 decode_string_expr(opsrc), int(oplen), int(pops), int(pushes),
100 int(precedence), flags.replace(' ', '').split('|')))
101 return opinfo
103 opinfo = load_ops(os.path.join(os.path.dirname(__file__), "jsopcode.tbl"))
104 opname2info = dict((info.opname, info) for info in opinfo)
105 jsop2opcode = dict((info.jsop, info.opcode) for info in opinfo)
107 def to_uint8(s):
108 try:
109 n = int(s)
110 except ValueError:
111 n = -1
112 if 0 <= n < (1<<8):
113 return n
114 raise ValueError("invalid 8-bit operand: " + s)
116 def to_uint16(s):
117 try:
118 n = int(s)
119 except ValueError:
120 n = -1
121 if 0 <= n < (1<<16):
122 return n
123 raise ValueError("invalid 16-bit operand: " + s)
125 def immediate(op):
126 info = op.info
127 imm1Expr = op.imm1.startswith('(')
128 if 'JOF_ATOM' in info.flags:
129 if op.imm1 in ('void', 'object', 'function', 'string', 'number', 'boolean'):
130 return "0, COMMON_TYPE_ATOM_INDEX(JSTYPE_%s)" % op.imm1.upper()
131 return "0, COMMON_ATOM_INDEX(%s)" % op.imm1
132 if 'JOF_JUMP' in info.flags:
133 assert not imm1Expr
134 return "%d, %d" % ((op.target >> 8) & 0xff, op.target & 0xff)
135 if 'JOF_UINT8' in info.flags or 'JOF_INT8' in info.flags:
136 if imm1Expr:
137 return op.imm1
138 return str(to_uint8(op.imm1))
139 if 'JOF_UINT16' in info.flags:
140 if imm1Expr:
141 return '(%s & 0xff00) >> 8, (%s & 0xff)' % (op.imm1, op.imm1)
142 v = to_uint16(op.imm1)
143 return "%d, %d" % ((v & 0xff00) >> 8, v & 0xff)
144 raise NotImplementedError(info.jsop + " format not yet implemented")
146 def simulate_cfg(igroup, imacro, depth, i):
147 any_group_opcode = None
148 expected_depth = None
149 for opcode in igroup.ops:
150 opi = opinfo[opcode]
151 if any_group_opcode is None:
152 any_group_opcode = opcode
153 if opi.pops < 0:
154 expected_depth = None
155 else:
156 expected_depth = opi.pushes - opi.pops
157 elif expected_depth is None:
158 if opi.pops >= 0:
159 raise ValueError("imacro shared by constant- and variable-stack-defs/uses instructions")
160 else:
161 if opi.pops < 0:
162 raise ValueError("imacro shared by constant- and variable-stack-defs/uses instructions")
163 if opi.pushes - opi.pops != expected_depth:
164 raise ValueError("imacro shared by instructions with different stack depths")
166 for i in range(i, len(imacro.code)):
167 op = imacro.code[i]
168 opi = op.info
169 if opi.opname == 'imacop':
170 opi = opinfo[any_group_opcode]
172 if opi.pops < 0:
173 depth -= 2 + int(op.imm1)
174 else:
175 depth -= opi.pops
176 depth += opi.pushes
178 if i in imacro.depths and imacro.depths[i] != depth:
179 raise ValueError("Mismatched depth at %s:%d" % (imacro.filename, op.line))
181 # Underflowing depth isn't necessarily fatal; most of the imacros
182 # assume they are called with N>0 args so some assume it's ok to go
183 # to some depth <N. We simulate starting from 0, as we've no idea
184 # what else to do.
186 # if depth < 0:
187 # raise ValueError("Negative static-stack depth at %s:%d" % (imacro.filename, op.line))
188 if depth > imacro.maxdepth:
189 imacro.maxdepth = depth
190 imacro.depths[i] = depth
192 if hasattr(op, "target_index"):
193 if op.target_index <= i:
194 raise ValueError("Backward jump at %s:%d" % (imacro.filename, op.line))
195 simulate_cfg(igroup, imacro, depth, op.target_index)
196 if op.info.opname in ('goto', 'gotox'):
197 return
199 if expected_depth is not None and depth != expected_depth:
200 raise ValueError("Expected depth %d, got %d" % (expected_depth, depth))
203 # Syntax (spaces are significant only to delimit tokens):
205 # Assembly ::= (Directive? '\n')*
206 # Directive ::= (name ':')? Operation
207 # Operation ::= opname Operands?
208 # Operands ::= Operand (',' Operand)*
209 # Operand ::= name | number | '(' Expr ')'
210 # Expr ::= a constant-expression in the C++ language
211 # containing no parentheses
213 # We simplify given line structure and the maximum of one immediate operand,
214 # by parsing using split and regexps. For ease of parsing, parentheses are
215 # banned in an Expr for now, even in quotes or a C++ comment.
217 # Pseudo-ops start with . and include .igroup and .imacro, terminated by .end.
218 # .imacro must nest in .igroup, neither nests in itself. See imacros.jsasm for
219 # examples.
221 line_regexp = re.compile(r'''(?x)
223 (?: (\w+): )? # optional label at start of line
224 \s* (\.?\w+) # optional spaces, (pseudo-)opcode
225 (?: \s+ ([+-]?\w+ | \([^)]*\)) )? # optional first immediate operand
226 (?: \s+ ([\w,-]+ | \([^)]*\)) )? # optional second immediate operand
227 (?: \s* (?:\#.*) )? # optional spaces and comment
228 $''')
230 oprange_regexp = re.compile(r'^\w+(?:-\w+)?(?:,\w+(?:-\w+)?)*$')
232 class IGroup(object):
233 def __init__(self, name, ops):
234 self.name = name
235 self.ops = ops
236 self.imacros = []
238 class IMacro(object):
239 def __init__(self, name, filename):
240 self.name = name
241 self.offset = 0
242 self.code = []
243 self.labeldefs = {}
244 self.labeldef_indexes = {}
245 self.labelrefs = {}
246 self.filename = filename
247 self.depths = {}
248 self.initdepth = 0
250 class Instruction(object):
251 def __init__(self, offset, info, imm1, imm2, lineno):
252 self.offset = offset
253 self.info = info
254 self.imm1 = imm1
255 self.imm2 = imm2
256 self.lineno = lineno
258 def assemble(filename, outfile):
259 write = outfile.write
260 igroup = None
261 imacro = None
262 opcode2extra = {}
263 igroups = []
265 write("/* GENERATED BY imacro_asm.js -- DO NOT EDIT!!! */\n")
267 def fail(msg, *args):
268 raise ValueError("%s at %s:%d" % (msg % args, filename, lineno + 1))
270 for lineno, line in enumerate(readFileLines(filename)):
271 # strip comments
272 line = re.sub(r'#.*', '', line).rstrip()
273 if line == "":
274 continue
275 m = line_regexp.match(line)
276 if m is None:
277 fail(line)
279 label, opname, imm1, imm2 = m.groups()
281 if opname.startswith('.'):
282 if label is not None:
283 fail("invalid label %s before %s" % (label, opname))
285 if opname == '.igroup':
286 if imm1 is None:
287 fail("missing .igroup name")
288 if igroup is not None:
289 fail("nested .igroup " + imm1)
290 if oprange_regexp.match(imm2) is None:
291 fail("invalid igroup operator range " + imm2)
293 ops = set()
294 for current in imm2.split(","):
295 split = current.split('-')
296 opcode = jsop2opcode[split[0]]
297 if len(split) == 1:
298 lastopcode = opcode
299 else:
300 assert len(split) == 2
301 lastopcode = jsop2opcode[split[1]]
302 if opcode >= lastopcode:
303 fail("invalid opcode range: " + current)
305 for opcode in range(opcode, lastopcode + 1):
306 if opcode in ops:
307 fail("repeated opcode " + opinfo[opcode].jsop)
308 ops.add(opcode)
310 igroup = IGroup(imm1, ops)
312 elif opname == '.imacro':
313 if igroup is None:
314 fail(".imacro outside of .igroup")
315 if imm1 is None:
316 fail("missing .imacro name")
317 if imacro:
318 fail("nested .imacro " + imm1)
319 imacro = IMacro(imm1, filename)
321 elif opname == '.fixup':
322 if imacro is None:
323 fail(".fixup outside of .imacro")
324 if len(imacro.code) != 0:
325 fail(".fixup must be first item in .imacro")
326 if imm1 is None:
327 fail("missing .fixup argument")
328 try:
329 fixup = int(imm1)
330 except ValueError:
331 fail(".fixup argument must be a nonzero integer")
332 if fixup == 0:
333 fail(".fixup argument must be a nonzero integer")
334 if imacro.initdepth != 0:
335 fail("more than one .fixup in .imacro")
336 imacro.initdepth = fixup
338 elif opname == '.end':
339 if imacro is None:
340 if igroup is None:
341 fail(".end without prior .igroup or .imacro")
342 if imm1 is not None and (imm1 != igroup.name or imm2 is not None):
343 fail(".igroup/.end name mismatch")
345 maxdepth = 0
347 write("static struct {\n")
348 for imacro in igroup.imacros:
349 write(" jsbytecode %s[%d];\n" % (imacro.name, imacro.offset))
350 write("} %s_imacros = {\n" % igroup.name)
352 for imacro in igroup.imacros:
353 depth = 0
354 write(" {\n")
355 for op in imacro.code:
356 operand = ""
357 if op.imm1 is not None:
358 operand = ", " + immediate(op)
359 write("/*%2d*/ %s%s,\n" % (op.offset, op.info.jsop, operand))
361 imacro.maxdepth = imacro.initdepth
362 simulate_cfg(igroup, imacro, imacro.initdepth, 0)
363 if imacro.maxdepth > maxdepth:
364 maxdepth = imacro.maxdepth
366 write(" },\n")
367 write("};\n")
369 for opcode in igroup.ops:
370 opcode2extra[opcode] = maxdepth
371 igroups.append(igroup)
372 igroup = None
373 else:
374 assert igroup is not None
376 if imm1 is not None and imm1 != imacro.name:
377 fail(".imacro/.end name mismatch")
379 # Backpatch the forward references to labels that must now be defined.
380 for label in imacro.labelrefs:
381 if label not in imacro.labeldefs:
382 fail("label " + label + " used but not defined")
383 link = imacro.labelrefs[label]
384 assert link >= 0
385 while True:
386 op = imacro.code[link]
387 next = op.target
388 op.target = imacro.labeldefs[label] - op.offset
389 op.target_index = imacro.labeldef_indexes[label]
390 if next < 0:
391 break
392 link = next
394 igroup.imacros.append(imacro)
395 imacro = None
397 else:
398 fail("unknown pseudo-op " + opname)
399 continue
401 if opname not in opname2info:
402 fail("unknown opcode " + opname)
404 info = opname2info[opname]
405 if info.oplen == -1:
406 fail("unimplemented opcode " + opname)
408 if imacro is None:
409 fail("opcode %s outside of .imacro", opname)
411 # Blacklist ops that may or must use an atomized double immediate.
412 if info.opname in ('double', 'lookupswitch', 'lookupswitchx'):
413 fail(op.opname + " opcode not yet supported")
415 if label:
416 imacro.labeldefs[label] = imacro.offset
417 imacro.labeldef_indexes[label] = len(imacro.code)
419 op = Instruction(imacro.offset, info, imm1, imm2, lineno + 1)
420 if 'JOF_JUMP' in info.flags:
421 if imm1 in imacro.labeldefs:
422 # Backward reference can be resolved right away, no backpatching needed.
423 op.target = imacro.labeldefs[imm1] - op.offset
424 op.target_index = imacro.labeldef_indexes[imm1]
425 else:
426 # Link op into the .target-linked backpatch chain at labelrefs[imm1].
427 # The linked list terminates with a -1 sentinel.
428 if imm1 in imacro.labelrefs:
429 op.target = imacro.labelrefs[imm1]
430 else:
431 op.target = -1
432 imacro.labelrefs[imm1] = len(imacro.code)
434 imacro.code.append(op)
435 imacro.offset += info.oplen
437 write("uint8 js_opcode2extra[JSOP_LIMIT] = {\n")
438 for i in range(len(opinfo)):
439 write(" %d, /* %s */\n" % (opcode2extra.get(i, 0), opinfo[i].jsop))
440 write("};\n")
442 write("#define JSOP_IS_IMACOP(x) (0 \\\n")
443 for i in sorted(opcode2extra):
444 write(" || x == %s \\\n" % opinfo[i].jsop)
445 write(")\n")
447 write("jsbytecode*\njs_GetImacroStart(jsbytecode* pc) {\n")
448 for g in igroups:
449 for m in g.imacros:
450 start = g.name + "_imacros." + m.name
451 write(" if (size_t(pc - %s) < %d) return %s;\n" % (start, m.offset, start))
453 write(" return NULL;\n")
454 write("}\n")
456 if __name__ == '__main__':
457 import sys
458 if len(sys.argv) != 3:
459 print "usage: python imacro_asm.py infile.jsasm outfile.c.out"
460 sys.exit(1)
462 f = open(sys.argv[2], 'w')
463 try:
464 assemble(sys.argv[1], f)
465 finally:
466 f.close()