1 |
/* vim: set sw=4 ts=8 et tw=78 ft=javascript: */ |
2 |
/* ***** BEGIN LICENSE BLOCK ***** |
3 |
* Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
4 |
* |
5 |
* The contents of this file are subject to the Mozilla Public License Version |
6 |
* 1.1 (the "License"); you may not use this file except in compliance with |
7 |
* the License. You may obtain a copy of the License at |
8 |
* http://www.mozilla.org/MPL/ |
9 |
* |
10 |
* Software distributed under the License is distributed on an "AS IS" basis, |
11 |
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
12 |
* for the specific language governing rights and limitations under the |
13 |
* License. |
14 |
* |
15 |
* The Original Code is the TraceMonkey IMacro Assembler. |
16 |
* |
17 |
* The Initial Developer of the Original Code is |
18 |
* Brendan Eich <brendan@mozilla.org>. |
19 |
* Portions created by the Initial Developer are Copyright (C) 2008 |
20 |
* the Initial Developer. All Rights Reserved. |
21 |
* |
22 |
* Contributor(s): |
23 |
* |
24 |
* Alternatively, the contents of this file may be used under the terms of |
25 |
* either the GNU General Public License Version 2 or later (the "GPL"), or |
26 |
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
27 |
* in which case the provisions of the GPL or the LGPL are applicable instead |
28 |
* of those above. If you wish to allow use of your version of this file only |
29 |
* under the terms of either the GPL or the LGPL, and not to allow others to |
30 |
* use your version of this file under the terms of the MPL, indicate your |
31 |
* decision by deleting the provisions above and replace them with the notice |
32 |
* and other provisions required by the GPL or the LGPL. If you do not delete |
33 |
* the provisions above, a recipient may use your version of this file under |
34 |
* the terms of any one of the MPL, the GPL or the LGPL. |
35 |
* |
36 |
* ***** END LICENSE BLOCK ***** */ |
37 |
|
38 |
/* |
39 |
* An imacro (interpreter-macro) assembler in JS, with a light dusting of C |
40 |
* pre-processor. We depend on the snarf function from the js shell, defined |
41 |
* originally for the Narcissus metacircular evaluator, now unconditionally |
42 |
* compiled into the shell. |
43 |
* |
44 |
* Filename suffix conventions, used by Makefile.in rules: |
45 |
* .js.in C-pre-processed JS source |
46 |
* .jsasm SpiderMonkey JS assembly source, which could be input to other |
47 |
* assemblers than imacro_asm.js, hence the generic suffix! |
48 |
* .c.out C source output by imacro_asm.js |
49 |
*/ |
50 |
|
51 |
#define ASSERT(cond) _ASSERT(cond, #cond) |
52 |
|
53 |
function _ASSERT(cond, message) { |
54 |
if (!cond) |
55 |
throw new Error("Assertion failed: " + message); |
56 |
} |
57 |
|
58 |
const js_arguments_str = "arguments"; |
59 |
const js_new_str = "new"; |
60 |
const js_typeof_str = "typeof"; |
61 |
const js_void_str = "void"; |
62 |
const js_null_str = "null"; |
63 |
const js_this_str = "this"; |
64 |
const js_false_str = "false"; |
65 |
const js_true_str = "true"; |
66 |
const js_throw_str = "throw"; |
67 |
const js_in_str = "in"; |
68 |
const js_instanceof_str = "instanceof"; |
69 |
const js_getter_str = "getter"; |
70 |
const js_setter_str = "setter"; |
71 |
|
72 |
#define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \ |
73 |
{jsop: #op, opcode: val, opname: name, opsrc: token, oplen: length, \ |
74 |
pops: nuses, pushes: ndefs, precedence: prec, flags: #format}, |
75 |
|
76 |
const NULL = null; |
77 |
|
78 |
const opinfo = [ |
79 |
#include "jsopcode.tbl" |
80 |
]; |
81 |
|
82 |
const opname2info = {}; |
83 |
const jsop2opcode = {}; |
84 |
|
85 |
for (let i = 0; i < opinfo.length; i++) { |
86 |
let info = opinfo[i]; |
87 |
ASSERT(info.opcode == i); |
88 |
opname2info[info.opname] = info; |
89 |
jsop2opcode[info.jsop] = info.opcode; |
90 |
} |
91 |
|
92 |
function format_offset(n, w) { |
93 |
let s = n.toString(); |
94 |
while (s.length < w) |
95 |
s = ' ' + s; |
96 |
return s; |
97 |
} |
98 |
|
99 |
function immediate(op) { |
100 |
let info = op.info; |
101 |
let imm1Expr = /^\(/.test(op.imm1); |
102 |
if (info.flags.indexOf("JOF_ATOM") >= 0) { |
103 |
if (/^(?:void|object|function|string|number|boolean)$/.test(op.imm1)) |
104 |
return "0, COMMON_TYPE_ATOM_INDEX(JSTYPE_" + op.imm1.toUpperCase() + ")"; |
105 |
return "0, COMMON_ATOM_INDEX(" + op.imm1 + ")"; |
106 |
} |
107 |
if (info.flags.indexOf("JOF_JUMP") >= 0) { |
108 |
ASSERT(!imm1Expr); |
109 |
return ((op.target >> 8) & 0xff) + ", " + (op.target & 0xff); |
110 |
} |
111 |
if (info.flags.indexOf("JOF_UINT8") >= 0 || |
112 |
info.flags.indexOf("JOF_INT8") >= 0) { |
113 |
if (imm1Expr) |
114 |
return op.imm1; |
115 |
if (isNaN(Number(op.imm1)) || Number(op.imm1) != parseInt(op.imm1)) |
116 |
throw new Error("invalid 8-bit operand: " + op.imm1); |
117 |
return (op.imm1 & 0xff); |
118 |
} |
119 |
if (info.flags.indexOf("JOF_UINT16") >= 0) { |
120 |
if (imm1Expr) |
121 |
return '(_ & 0xff00) >> 8, (_ & 0xff)'.replace(/_/g, op.imm1); |
122 |
return ((op.imm1 & 0xff00) >> 8) + ", " + (op.imm1 & 0xff); |
123 |
} |
124 |
throw new Error(info.jsop + " format not yet implemented"); |
125 |
} |
126 |
|
127 |
function simulate_cfg(imacro, depth, i) { |
128 |
while (i < imacro.code.length) { |
129 |
let op = imacro.code[i]; |
130 |
depth -= (op.info.pops < 0) ? 2 + Number(op.imm1) : op.info.pops; |
131 |
depth += op.info.pushes; |
132 |
|
133 |
if (imacro.depths.hasOwnProperty(i) && imacro.depths[i] != depth) |
134 |
throw Error("Mismatched depth at " + imacro.filename + ":" + op.line); |
135 |
|
136 |
/* |
137 |
* Underflowing depth isn't necessarily fatal; most of the imacros |
138 |
* assume they are called with N>0 args so some assume it's ok to go |
139 |
* to some depth <N. We simulate starting from 0, as we've no idea |
140 |
* what else to do. |
141 |
* |
142 |
* if (depth < 0) |
143 |
* throw Error("Negative static-stack depth at " + imacro.filename + ":" + op.line); |
144 |
*/ |
145 |
if (depth > imacro.maxdepth) |
146 |
imacro.maxdepth = depth; |
147 |
imacro.depths[i] = depth; |
148 |
|
149 |
if (op.hasOwnProperty("target_index")) { |
150 |
if (op.target_index <= i) |
151 |
throw Error("Backward jump at " + imacro.filename + ":" + op.line); |
152 |
|
153 |
simulate_cfg(imacro, depth, op.target_index); |
154 |
|
155 |
if (op.info.opname == "goto" || op.info.opname == "gotox") |
156 |
return; |
157 |
} |
158 |
++i; |
159 |
} |
160 |
} |
161 |
|
162 |
/* |
163 |
* Syntax (spaces are significant only to delimit tokens): |
164 |
* |
165 |
* Assembly ::= (Directive? '\n')* |
166 |
* Directive ::= (name ':')? Operation |
167 |
* Operation ::= opname Operands? |
168 |
* Operands ::= Operand (',' Operand)* |
169 |
* Operand ::= name | number | '(' Expr ')' |
170 |
* Expr ::= a constant-expression in the C++ language |
171 |
* containing no parentheses |
172 |
* |
173 |
* We simplify given line structure and the maximum of one immediate operand, |
174 |
* by parsing using split and regexps. For ease of parsing, parentheses are |
175 |
* banned in an Expr for now, even in quotes or a C++ comment. |
176 |
* |
177 |
* Pseudo-ops start with . and include .igroup and .imacro, terminated by .end. |
178 |
* .imacro must nest in .igroup, neither nests in itself. See imacros.jsasm for |
179 |
* examples. |
180 |
*/ |
181 |
const line_regexp_parts = [ |
182 |
"^(?:(\\w+):)?", // optional label at start of line |
183 |
"\\s*(\\.?\\w+)", // optional spaces, (pseudo-)opcode |
184 |
"(?:\\s+(\\w+|\\([^)]*\\)))?", // optional first immediate operand |
185 |
"(?:\\s+([\\w-]+|\\([^)]*\\)))?", // optional second immediate operand |
186 |
"(?:\\s*(?:#.*))?$" // optional spaces and comment |
187 |
]; |
188 |
|
189 |
const line_regexp = new RegExp(line_regexp_parts.join("")); |
190 |
|
191 |
function assemble(filename) { |
192 |
let igroup = null, imacro = null; |
193 |
let opcode2extra = []; |
194 |
let igroups = []; |
195 |
|
196 |
print("/* GENERATED BY imacro_asm.js -- DO NOT EDIT!!! */"); |
197 |
|
198 |
let s = snarf(filename); |
199 |
let a = s.split('\n'); |
200 |
for (let i = 0; i < a.length; i++) { |
201 |
if (/^\s*(?:#.*)?$/.test(a[i])) |
202 |
continue; |
203 |
let m = line_regexp.exec(a[i]); |
204 |
if (!m) |
205 |
throw new Error(a[i]); |
206 |
|
207 |
let [, label, opname, imm1, imm2] = m; |
208 |
|
209 |
if (opname[0] == '.') { |
210 |
if (label) |
211 |
throw new Error("invalid label " + label + " before " + opname); |
212 |
|
213 |
switch (opname) { |
214 |
case ".igroup": |
215 |
if (!imm1) |
216 |
throw new Error("missing .igroup name"); |
217 |
if (igroup) |
218 |
throw new Error("nested .igroup " + imm1); |
219 |
let oprange = imm2.match(/^(\w+)(?:-(\w+))?$/); |
220 |
if (!oprange) |
221 |
throw new Error("invalid igroup operator range " + imm2); |
222 |
let firstop = jsop2opcode[oprange[1]]; |
223 |
igroup = { |
224 |
name: imm1, |
225 |
firstop: firstop, |
226 |
lastop: oprange[2] ? jsop2opcode[oprange[2]] : firstop, |
227 |
imacros: [] |
228 |
}; |
229 |
break; |
230 |
|
231 |
case ".imacro": |
232 |
if (!igroup) |
233 |
throw new Error(".imacro outside of .igroup"); |
234 |
if (!imm1) |
235 |
throw new Error("missing .imacro name"); |
236 |
if (imacro) |
237 |
throw new Error("nested .imacro " + imm1); |
238 |
imacro = { |
239 |
name: imm1, |
240 |
offset: 0, |
241 |
code: [], |
242 |
labeldefs: {}, |
243 |
labeldef_indexes: {}, |
244 |
labelrefs: {}, |
245 |
filename: filename, |
246 |
depths: {} |
247 |
}; |
248 |
break; |
249 |
|
250 |
case ".end": |
251 |
if (!imacro) { |
252 |
if (!igroup) |
253 |
throw new Error(".end without prior .igroup or .imacro"); |
254 |
if (imm1 && (imm1 != igroup.name || imm2)) |
255 |
throw new Error(".igroup/.end name mismatch"); |
256 |
|
257 |
let maxdepth = 0; |
258 |
|
259 |
print("static struct {"); |
260 |
for (let j = 0; j < igroup.imacros.length; j++) { |
261 |
imacro = igroup.imacros[j]; |
262 |
print(" jsbytecode " + imacro.name + "[" + imacro.offset + "];"); |
263 |
} |
264 |
print("} " + igroup.name + "_imacros = {"); |
265 |
|
266 |
for (let j = 0; j < igroup.imacros.length; j++) { |
267 |
let depth = 0; |
268 |
|
269 |
imacro = igroup.imacros[j]; |
270 |
print(" {"); |
271 |
for (let k = 0; k < imacro.code.length; k++) { |
272 |
let op = imacro.code[k]; |
273 |
|
274 |
print("/*" + format_offset(op.offset,2) + "*/ " + op.info.jsop + |
275 |
(op.imm1 ? ", " + immediate(op) : "") + ","); |
276 |
|
277 |
} |
278 |
|
279 |
imacro.maxdepth = 0; |
280 |
simulate_cfg(imacro, 0, 0); |
281 |
if (imacro.maxdepth > maxdepth) |
282 |
maxdepth = imacro.maxdepth; |
283 |
|
284 |
print(" },"); |
285 |
} |
286 |
|
287 |
print("};"); |
288 |
|
289 |
let opcode = igroup.firstop; |
290 |
let oplast = igroup.lastop; |
291 |
do { |
292 |
opcode2extra[opcode] = maxdepth; |
293 |
} while (opcode++ != oplast); |
294 |
igroups.push(igroup); |
295 |
igroup = null; |
296 |
} else { |
297 |
ASSERT(igroup); |
298 |
|
299 |
if (imm1 && imm1 != imacro.name) |
300 |
throw new Error(".imacro/.end name mismatch"); |
301 |
|
302 |
// Backpatch the forward references to labels that must now be defined. |
303 |
for (label in imacro.labelrefs) { |
304 |
if (!imacro.labelrefs.hasOwnProperty(label)) |
305 |
continue; |
306 |
if (!imacro.labeldefs.hasOwnProperty(label)) |
307 |
throw new Error("label " + label + " used but not defined"); |
308 |
let link = imacro.labelrefs[label]; |
309 |
ASSERT(link >= 0); |
310 |
for (;;) { |
311 |
let op = imacro.code[link]; |
312 |
ASSERT(op); |
313 |
ASSERT(op.hasOwnProperty('target')); |
314 |
let next = op.target; |
315 |
op.target = imacro.labeldefs[label] - op.offset; |
316 |
op.target_index = imacro.labeldef_indexes[label]; |
317 |
if (next < 0) |
318 |
break; |
319 |
link = next; |
320 |
} |
321 |
} |
322 |
|
323 |
igroup.imacros.push(imacro); |
324 |
} |
325 |
imacro = null; |
326 |
break; |
327 |
|
328 |
default: |
329 |
throw new Error("unknown pseudo-op " + opname); |
330 |
} |
331 |
continue; |
332 |
} |
333 |
|
334 |
if (!opname2info.hasOwnProperty(opname)) |
335 |
throw new Error("unknown opcode " + opname + (label ? " (label " + label + ")" : "")); |
336 |
|
337 |
let info = opname2info[opname]; |
338 |
if (info.oplen == -1) |
339 |
throw new Error("unimplemented opcode " + opname); |
340 |
|
341 |
if (!imacro) |
342 |
throw new Error("opcode " + opname + " outside of .imacro"); |
343 |
|
344 |
// Blacklist ops that may or must use an atomized double immediate. |
345 |
switch (info.opname) { |
346 |
case "double": |
347 |
case "lookupswitch": |
348 |
case "lookupswitchx": |
349 |
throw new Error(op.opname + " opcode not yet supported"); |
350 |
} |
351 |
|
352 |
if (label) { |
353 |
imacro.labeldefs[label] = imacro.offset; |
354 |
imacro.labeldef_indexes[label] = imacro.code.length; |
355 |
} |
356 |
|
357 |
let op = {offset: imacro.offset, info: info, imm1: imm1, imm2: imm2, line:(i+1) }; |
358 |
if (info.flags.indexOf("JOF_JUMP") >= 0) { |
359 |
if (imacro.labeldefs.hasOwnProperty(imm1)) { |
360 |
// Backward reference can be resolved right away, no backpatching needed. |
361 |
op.target = imacro.labeldefs[imm1] - op.offset; |
362 |
op.target_index = imacro.labeldef_indexes[imm1]; |
363 |
} else { |
364 |
// Link op into the .target-linked backpatch chain at labelrefs[imm1]. |
365 |
// The linked list terminates with a -1 sentinel. |
366 |
op.target = imacro.labelrefs.hasOwnProperty(imm1) ? imacro.labelrefs[imm1] : -1; |
367 |
imacro.labelrefs[imm1] = imacro.code.length; |
368 |
} |
369 |
} |
370 |
|
371 |
imacro.code.push(op); |
372 |
imacro.offset += info.oplen; |
373 |
} |
374 |
|
375 |
print("uint8 js_opcode2extra[JSOP_LIMIT] = {"); |
376 |
for (let i = 0; i < opinfo.length; i++) { |
377 |
print(" " + ((i in opcode2extra) ? opcode2extra[i] : "0") + |
378 |
", /* " + opinfo[i].jsop + " */"); |
379 |
} |
380 |
print("};"); |
381 |
|
382 |
print("#define JSOP_IS_IMACOP(x) (0 \\"); |
383 |
for (let i in opcode2extra) |
384 |
print(" || x == " + opinfo[i].jsop + " \\"); |
385 |
print(")"); |
386 |
|
387 |
print("jsbytecode*\njs_GetImacroStart(jsbytecode* pc) {"); |
388 |
for each (let g in igroups) { |
389 |
for each (let m in g.imacros) { |
390 |
let start = g.name + "_imacros." + m.name; |
391 |
print(" if (size_t(pc - " + start + ") < " + m.offset + ") return " + start + ";"); |
392 |
} |
393 |
} |
394 |
print(" return NULL;"); |
395 |
print("}"); |
396 |
} |
397 |
|
398 |
for (let i = 0; i < arguments.length; i++) { |
399 |
try { |
400 |
assemble(arguments[i]); |
401 |
} catch (e) { |
402 |
print(e.name + ": " + e.message + "\n" + e.stack); |
403 |
} |
404 |
} |