mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
405 lines
15 KiB
JavaScript
405 lines
15 KiB
JavaScript
/* 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 <brendan@mozilla.org>.
|
|
* 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 format_offset(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");
|
|
}
|
|
|
|
function simulate_cfg(imacro, depth, i) {
|
|
while (i < imacro.code.length) {
|
|
let op = imacro.code[i];
|
|
depth -= (op.info.pops < 0) ? 2 + Number(op.imm1) : op.info.pops;
|
|
depth += op.info.pushes;
|
|
|
|
if (imacro.depths.hasOwnProperty(i) && imacro.depths[i] != depth)
|
|
throw Error("Mismatched depth at " + imacro.filename + ":" + op.line);
|
|
|
|
/*
|
|
* Underflowing depth isn't necessarily fatal; most of the imacros
|
|
* assume they are called with N>0 args so some assume it's ok to go
|
|
* to some depth <N. We simulate starting from 0, as we've no idea
|
|
* what else to do.
|
|
*
|
|
* if (depth < 0)
|
|
* throw Error("Negative static-stack depth at " + imacro.filename + ":" + op.line);
|
|
*/
|
|
if (depth > imacro.maxdepth)
|
|
imacro.maxdepth = depth;
|
|
imacro.depths[i] = depth;
|
|
|
|
if (op.hasOwnProperty("target_index")) {
|
|
if (op.target_index <= i)
|
|
throw Error("Backward jump at " + imacro.filename + ":" + op.line);
|
|
|
|
simulate_cfg(imacro, depth, op.target_index);
|
|
|
|
if (op.info.opname == "goto" || op.info.opname == "gotox")
|
|
return;
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 = [];
|
|
let igroups = [];
|
|
|
|
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: {},
|
|
labeldef_indexes: {},
|
|
labelrefs: {},
|
|
filename: filename,
|
|
depths: {}
|
|
};
|
|
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("/*" + format_offset(op.offset,2) + "*/ " + op.info.jsop +
|
|
(op.imm1 ? ", " + immediate(op) : "") + ",");
|
|
|
|
}
|
|
|
|
imacro.maxdepth = 0;
|
|
simulate_cfg(imacro, 0, 0);
|
|
if (imacro.maxdepth > maxdepth)
|
|
maxdepth = imacro.maxdepth;
|
|
|
|
print(" },");
|
|
}
|
|
|
|
print("};");
|
|
|
|
let opcode = igroup.firstop;
|
|
let oplast = igroup.lastop;
|
|
do {
|
|
opcode2extra[opcode] = maxdepth;
|
|
} while (opcode++ != oplast);
|
|
igroups.push(igroup);
|
|
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;
|
|
op.target_index = imacro.labeldef_indexes[label];
|
|
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");
|
|
|
|
// Blacklist ops that may or must use an atomized double immediate.
|
|
switch (info.opname) {
|
|
case "double":
|
|
case "lookupswitch":
|
|
case "lookupswitchx":
|
|
throw new Error(op.opname + " opcode not yet supported");
|
|
}
|
|
|
|
if (label) {
|
|
imacro.labeldefs[label] = imacro.offset;
|
|
imacro.labeldef_indexes[label] = imacro.code.length;
|
|
}
|
|
|
|
let op = {offset: imacro.offset, info: info, imm1: imm1, imm2: imm2, line:(i+1) };
|
|
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;
|
|
op.target_index = imacro.labeldef_indexes[imm1];
|
|
} 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(")");
|
|
|
|
print("jsbytecode*\njs_GetImacroStart(jsbytecode* pc) {");
|
|
for each (let g in igroups) {
|
|
for each (let m in g.imacros) {
|
|
let start = g.name + "_imacros." + m.name;
|
|
print(" if (size_t(pc - " + start + ") < " + m.offset + ") return " + start + ";");
|
|
}
|
|
}
|
|
print(" return NULL;");
|
|
print("}");
|
|
}
|
|
|
|
for (let i = 0; i < arguments.length; i++) {
|
|
try {
|
|
assemble(arguments[i]);
|
|
} catch (e) {
|
|
print(e.name + ": " + e.message + "\n" + e.stack);
|
|
}
|
|
}
|