/* vim: set sw=4 ts=8 et tw=78 ft=javascript: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the TraceMonkey IMacro Assembler. * * The Initial Developer of the Original Code is * Brendan Eich . * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * An imacro (interpreter-macro) assembler in JS, with a light dusting of C * pre-processor. We depend on the snarf function from the js shell, defined * originally for the Narcissus metacircular evaluator, now unconditionally * compiled into the shell. * * Filename suffix conventions, used by Makefile.in rules: * .js.in C-pre-processed JS source * .jsasm SpiderMonkey JS assembly source, which could be input to other * assemblers than imacro_asm.js, hence the generic suffix! * .c.out C source output by imacro_asm.js */ #define ASSERT(cond) _ASSERT(cond, #cond) function _ASSERT(cond, message) { if (!cond) throw new Error("Assertion failed: " + message); } const js_arguments_str = "arguments"; const js_new_str = "new"; const js_typeof_str = "typeof"; const js_void_str = "void"; const js_null_str = "null"; const js_this_str = "this"; const js_false_str = "false"; const js_true_str = "true"; const js_throw_str = "throw"; const js_in_str = "in"; const js_instanceof_str = "instanceof"; const js_getter_str = "getter"; const js_setter_str = "setter"; #define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \ {jsop: #op, opcode: val, opname: name, opsrc: token, oplen: length, \ pops: nuses, pushes: ndefs, precedence: prec, flags: #format}, const NULL = null; const opinfo = [ #include "jsopcode.tbl" ]; const opname2info = {}; const jsop2opcode = {}; for (let i = 0; i < opinfo.length; i++) { let info = opinfo[i]; ASSERT(info.opcode == i); opname2info[info.opname] = info; jsop2opcode[info.jsop] = info.opcode; } function formatoffset(n, w) { let s = n.toString(); while (s.length < w) s = ' ' + s; return s; } function immediate(op) { let info = op.info; let imm1Expr = /^\(/.test(op.imm1); if (info.flags.indexOf("JOF_ATOM") >= 0) { if (/^(?:void|object|function|string|number|boolean)$/.test(op.imm1)) return "0, COMMON_TYPE_ATOM_INDEX(JSTYPE_" + op.imm1.toUpperCase() + ")"; return "0, COMMON_ATOM_INDEX(" + op.imm1 + ")"; } if (info.flags.indexOf("JOF_JUMP") >= 0) { ASSERT(!imm1Expr); return ((op.target >> 8) & 0xff) + ", " + (op.target & 0xff); } if (info.flags.indexOf("JOF_UINT8") >= 0 || info.flags.indexOf("JOF_INT8") >= 0) { if (imm1Expr) return op.imm1; if (isNaN(Number(op.imm1)) || Number(op.imm1) != parseInt(op.imm1)) throw new Error("invalid 8-bit operand: " + op.imm1); return (op.imm1 & 0xff); } if (info.flags.indexOf("JOF_UINT16") >= 0) { if (imm1Expr) return '(_ & 0xff00) >> 8, (_ & 0xff)'.replace(/_/g, op.imm1); return ((op.imm1 & 0xff00) >> 8) + ", " + (op.imm1 & 0xff); } throw new Error(info.jsop + " format not yet implemented"); } /* * Syntax (spaces are significant only to delimit tokens): * * Assembly ::= (Directive? '\n')* * Directive ::= (name ':')? Operation * Operation ::= opname Operands? * Operands ::= Operand (',' Operand)* * Operand ::= name | number | '(' Expr ')' * Expr ::= a constant-expression in the C++ language * containing no parentheses * * We simplify given line structure and the maximum of one immediate operand, * by parsing using split and regexps. For ease of parsing, parentheses are * banned in an Expr for now, even in quotes or a C++ comment. * * Pseudo-ops start with . and include .igroup and .imacro, terminated by .end. * .imacro must nest in .igroup, neither nests in itself. See imacros.jsasm for * examples. */ const line_regexp_parts = [ "^(?:(\\w+):)?", // optional label at start of line "\\s*(\\.?\\w+)", // optional spaces, (pseudo-)opcode "(?:\\s+(\\w+|\\([^)]*\\)))?", // optional first immediate operand "(?:\\s+([\\w-]+|\\([^)]*\\)))?", // optional second immediate operand "(?:\\s*(?:#.*))?$" // optional spaces and comment ]; const line_regexp = new RegExp(line_regexp_parts.join("")); function assemble(filename) { let igroup = null, imacro = null; let opcode2extra = []; print("/* GENERATED BY imacro_asm.js -- DO NOT EDIT!!! */"); let s = snarf(filename); let a = s.split('\n'); for (let i = 0; i < a.length; i++) { if (/^\s*(?:#.*)?$/.test(a[i])) continue; let m = line_regexp.exec(a[i]); if (!m) throw new Error(a[i]); let [, label, opname, imm1, imm2] = m; if (opname[0] == '.') { if (label) throw new Error("invalid label " + label + " before " + opname); switch (opname) { case ".igroup": if (!imm1) throw new Error("missing .igroup name"); if (igroup) throw new Error("nested .igroup " + imm1); let oprange = imm2.match(/^(\w+)(?:-(\w+))?$/); if (!oprange) throw new Error("invalid igroup operator range " + imm2); let firstop = jsop2opcode[oprange[1]]; igroup = { name: imm1, firstop: firstop, lastop: oprange[2] ? jsop2opcode[oprange[2]] : firstop, imacros: [] }; break; case ".imacro": if (!igroup) throw new Error(".imacro outside of .igroup"); if (!imm1) throw new Error("missing .imacro name"); if (imacro) throw new Error("nested .imacro " + imm1); imacro = { name: imm1, offset: 0, code: [], labeldefs: {}, labelrefs: {} }; break; case ".end": if (!imacro) { if (!igroup) throw new Error(".end without prior .igroup or .imacro"); if (imm1 && (imm1 != igroup.name || imm2)) throw new Error(".igroup/.end name mismatch"); let maxdepth = 0; print("static struct {"); for (let j = 0; j < igroup.imacros.length; j++) { imacro = igroup.imacros[j]; print(" jsbytecode " + imacro.name + "[" + imacro.offset + "];"); } print("} " + igroup.name + "_imacros = {"); for (let j = 0; j < igroup.imacros.length; j++) { let depth = 0; imacro = igroup.imacros[j]; print(" {"); for (let k = 0; k < imacro.code.length; k++) { let op = imacro.code[k]; print("/*" + formatoffset(op.offset,2) + "*/ " + op.info.jsop + (op.imm1 ? ", " + immediate(op) : "") + ","); depth -= (op.info.pops < 0) ? 2 + op.imm1 : op.info.pops; depth += op.info.pushes; if (depth > maxdepth) maxdepth = depth; } print(" },"); } print("};"); let opcode = igroup.firstop; let oplast = igroup.lastop; do { opcode2extra[opcode] = maxdepth; } while (opcode++ != oplast); igroup = null; } else { ASSERT(igroup); if (imm1 && imm1 != imacro.name) throw new Error(".imacro/.end name mismatch"); // Backpatch the forward references to labels that must now be defined. for (label in imacro.labelrefs) { if (!imacro.labelrefs.hasOwnProperty(label)) continue; if (!imacro.labeldefs.hasOwnProperty(label)) throw new Error("label " + label + " used but not defined"); let link = imacro.labelrefs[label]; ASSERT(link >= 0); for (;;) { let op = imacro.code[link]; ASSERT(op); ASSERT(op.hasOwnProperty('target')); let next = op.target; op.target = imacro.labeldefs[label] - op.offset; if (next < 0) break; link = next; } } igroup.imacros.push(imacro); } imacro = null; break; default: throw new Error("unknown pseudo-op " + opname); } continue; } if (!opname2info.hasOwnProperty(opname)) throw new Error("unknown opcode " + opname + (label ? " (label " + label + ")" : "")); let info = opname2info[opname]; if (info.oplen == -1) throw new Error("unimplemented opcode " + opname); if (!imacro) throw new Error("opcode " + opname + " outside of .imacro"); if (label) imacro.labeldefs[label] = imacro.offset; let op = {offset: imacro.offset, info: info, imm1: imm1, imm2: imm2}; if (info.flags.indexOf("JOF_JUMP") >= 0) { if (imacro.labeldefs.hasOwnProperty(imm1)) { // Backward reference can be resolved right away, no backpatching needed. op.target = imacro.labeldefs[imm1] - op.offset; } else { // Link op into the .target-linked backpatch chain at labelrefs[imm1]. // The linked list terminates with a -1 sentinel. op.target = imacro.labelrefs.hasOwnProperty(imm1) ? imacro.labelrefs[imm1] : -1; imacro.labelrefs[imm1] = imacro.code.length; } } imacro.code.push(op); imacro.offset += info.oplen; } print("uint8 js_opcode2extra[JSOP_LIMIT] = {"); for (let i = 0; i < opinfo.length; i++) { print(" " + ((i in opcode2extra) ? opcode2extra[i] : "0") + ", /* " + opinfo[i].jsop + " */"); } print("};"); print("#define JSOP_IS_IMACOP(x) (0 \\"); for (let i in opcode2extra) print(" || x == " + opinfo[i].jsop + " \\"); print(")"); } for (let i = 0; i < arguments.length; i++) { try { assemble(arguments[i]); } catch (e) { print(e.name + ": " + e.message + "\n" + e.stack); } }