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
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.
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
50 def __init__(self
, jsop
, opcode
, opname
, opsrc
, oplen
, pops
, pushes
, precedence
, flags
):
58 self
.precedence
= precedence
61 def readFileLines(filename
):
68 def load_ops(filename
):
69 opdef_regexp
= re
.compile(r
'''(?x)
70 ^ OPDEF \( (JSOP_\w+), \s* # op
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
78 ([\w_| ]+) \s* # format
81 def decode_string_expr(expr
):
85 assert expr
[-1] == '"'
87 assert expr
.startswith('js_') and expr
.endswith('_str')
91 for lineno
, line
in enumerate(readFileLines(filename
)):
92 if line
.startswith('OPDEF'):
93 m
= opdef_regexp
.match(line
)
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('|')))
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
)
114 raise ValueError("invalid 8-bit operand: " + s
)
123 raise ValueError("invalid 16-bit operand: " + s
)
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
:
134 return "%d, %d" % ((op
.target
>> 8) & 0xff, op
.target
& 0xff)
135 if 'JOF_UINT8' in info
.flags
or 'JOF_INT8' in info
.flags
:
138 return str(to_uint8(op
.imm1
))
139 if 'JOF_UINT16' in info
.flags
:
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
:
151 if any_group_opcode
is None:
152 any_group_opcode
= opcode
154 expected_depth
= None
156 expected_depth
= opi
.pushes
- opi
.pops
157 elif expected_depth
is None:
159 raise ValueError("imacro shared by constant- and variable-stack-defs/uses instructions")
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
)):
169 if opi
.opname
== 'imacop':
170 opi
= opinfo
[any_group_opcode
]
173 depth
-= 2 + int(op
.imm1
)
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
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'):
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
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
230 oprange_regexp
= re
.compile(r
'^\w+(?:-\w+)?(?:,\w+(?:-\w+)?)*$')
232 class IGroup(object):
233 def __init__(self
, name
, ops
):
238 class IMacro(object):
239 def __init__(self
, name
, filename
):
244 self
.labeldef_indexes
= {}
246 self
.filename
= filename
250 class Instruction(object):
251 def __init__(self
, offset
, info
, imm1
, imm2
, lineno
):
258 def assemble(filename
, outfile
):
259 write
= outfile
.write
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
)):
272 line
= re
.sub(r
'#.*', '', line
).rstrip()
275 m
= line_regexp
.match(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':
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
)
294 for current
in imm2
.split(","):
295 split
= current
.split('-')
296 opcode
= jsop2opcode
[split
[0]]
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):
307 fail("repeated opcode " + opinfo
[opcode
].jsop
)
310 igroup
= IGroup(imm1
, ops
)
312 elif opname
== '.imacro':
314 fail(".imacro outside of .igroup")
316 fail("missing .imacro name")
318 fail("nested .imacro " + imm1
)
319 imacro
= IMacro(imm1
, filename
)
321 elif opname
== '.fixup':
323 fail(".fixup outside of .imacro")
324 if len(imacro
.code
) != 0:
325 fail(".fixup must be first item in .imacro")
327 fail("missing .fixup argument")
331 fail(".fixup argument must be a nonzero integer")
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':
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")
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
:
355 for op
in imacro
.code
:
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
369 for opcode
in igroup
.ops
:
370 opcode2extra
[opcode
] = maxdepth
371 igroups
.append(igroup
)
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
]
386 op
= imacro
.code
[link
]
388 op
.target
= imacro
.labeldefs
[label
] - op
.offset
389 op
.target_index
= imacro
.labeldef_indexes
[label
]
394 igroup
.imacros
.append(imacro
)
398 fail("unknown pseudo-op " + opname
)
401 if opname
not in opname2info
:
402 fail("unknown opcode " + opname
)
404 info
= opname2info
[opname
]
406 fail("unimplemented opcode " + opname
)
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")
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
]
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
]
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
))
442 write("#define JSOP_IS_IMACOP(x) (0 \\\n")
443 for i
in sorted(opcode2extra
):
444 write(" || x == %s \\\n" % opinfo
[i
].jsop
)
447 write("jsbytecode*\njs_GetImacroStart(jsbytecode* pc) {\n")
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")
456 if __name__
== '__main__':
458 if len(sys
.argv
) != 3:
459 print "usage: python imacro_asm.py infile.jsasm outfile.c.out"
462 f
= open(sys
.argv
[2], 'w')
464 assemble(sys
.argv
[1], f
)