Bug 462300 - Add support for self-hosting syntax and operations (r=luke)

--HG--
extra : rebase_source : 90e33d171ac0b79aebdf2f652909d72dd7ae2601
This commit is contained in:
Till Schneidereit 2012-07-10 00:11:39 +02:00
parent 6988b7f1b0
commit 472c87095c
21 changed files with 209 additions and 31 deletions

View File

@ -1209,7 +1209,7 @@ TryConvertToGname(BytecodeEmitter *bce, ParseNode *pn, JSOp *op)
static bool
BindNameToSlot(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
JS_ASSERT(pn->isKind(PNK_NAME));
JS_ASSERT(pn->isKind(PNK_NAME) || pn->isKind(PNK_INTRINSICNAME));
/* Don't attempt if 'pn' is already bound or deoptimized or a nop. */
JSOp op = pn->getOp();
@ -1746,6 +1746,9 @@ EmitNameOp(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool callContext)
case JSOP_NAME:
op = JSOP_CALLNAME;
break;
case JSOP_INTRINSICNAME:
op = JSOP_CALLINTRINSIC;
break;
case JSOP_GETGNAME:
op = JSOP_CALLGNAME;
break;
@ -5332,12 +5335,54 @@ EmitCallOrNew(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
* value required for calls (which non-strict mode functions
* will box into the global object).
*/
uint32_t argc = pn->pn_count - 1;
bool emitArgs = true;
ParseNode *pn2 = pn->pn_head;
switch (pn2->getKind()) {
case PNK_NAME:
if (!EmitNameOp(cx, bce, pn2, callop))
return false;
break;
case PNK_INTRINSICNAME:
if (pn2->atom() == cx->runtime->atomState._CallFunctionAtom)
{
/*
* Special-casing of %_CallFunction to emit bytecode that directly
* invokes the callee with the correct |this| object and arguments.
* The call %_CallFunction(receiver, ...args, fun) thus becomes:
* - emit lookup for fun
* - emit lookup for receiver
* - emit lookups for ...args
*
* argc is set to the amount of actually emitted args and the
* emitting of args below is disabled by setting emitArgs to false.
*/
if (pn->pn_count < 3) {
bce->reportError(pn, JSMSG_MORE_ARGS_NEEDED, "%_CallFunction", "1", "s");
return false;
}
ParseNode *funNode = pn2->pn_next;
while (funNode->pn_next)
funNode = funNode->pn_next;
if (!EmitTree(cx, bce, funNode))
return false;
ParseNode *receiver = pn2->pn_next;
if (!EmitTree(cx, bce, receiver))
return false;
bool oldInForInit = bce->inForInit;
bce->inForInit = false;
for (ParseNode *argpn = receiver->pn_next; argpn != funNode; argpn = argpn->pn_next) {
if (!EmitTree(cx, bce, argpn))
return false;
}
bce->inForInit = oldInForInit;
argc -= 2;
emitArgs = false;
break;
}
if (!EmitNameOp(cx, bce, pn2, callop))
return false;
break;
case PNK_DOT:
if (!EmitPropOp(cx, pn2, pn2->getOp(), bce, callop))
return false;
@ -5364,25 +5409,23 @@ EmitCallOrNew(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
if (!callop && Emit1(cx, bce, JSOP_UNDEFINED) < 0)
return false;
/* Remember start of callable-object bytecode for decompilation hint. */
ptrdiff_t off = top;
/*
* Emit code for each argument in order, then emit the JSOP_*CALL or
* JSOP_NEW bytecode with a two-byte immediate telling how many args
* were pushed on the operand stack.
*/
bool oldInForInit = bce->inForInit;
bce->inForInit = false;
for (ParseNode *pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) {
if (!EmitTree(cx, bce, pn3))
return false;
if (emitArgs) {
/*
* Emit code for each argument in order, then emit the JSOP_*CALL or
* JSOP_NEW bytecode with a two-byte immediate telling how many args
* were pushed on the operand stack.
*/
bool oldInForInit = bce->inForInit;
bce->inForInit = false;
for (ParseNode *pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) {
if (!EmitTree(cx, bce, pn3))
return false;
}
bce->inForInit = oldInForInit;
}
bce->inForInit = oldInForInit;
if (NewSrcNote2(cx, bce, SRC_PCBASE, bce->offset() - off) < 0)
if (NewSrcNote2(cx, bce, SRC_PCBASE, bce->offset() - top) < 0)
return false;
uint32_t argc = pn->pn_count - 1;
if (Emit3(cx, bce, pn->getOp(), ARGC_HI(argc), ARGC_LO(argc)) < 0)
return false;
CheckTypeSet(cx, bce, pn->getOp());

View File

@ -35,7 +35,7 @@ UpvarCookie::set(JSContext *cx, unsigned newLevel, uint16_t newSlot)
inline PropertyName *
ParseNode::atom() const
{
JS_ASSERT(isKind(PNK_FUNCTION) || isKind(PNK_NAME));
JS_ASSERT(isKind(PNK_FUNCTION) || isKind(PNK_NAME) || isKind(PNK_INTRINSICNAME));
JSAtom *atom = isKind(PNK_FUNCTION) ? pn_funbox->function()->atom : pn_atom;
return atom->asPropertyName();
}

View File

@ -98,6 +98,7 @@ enum ParseNodeKind {
PNK_LP,
PNK_RP,
PNK_NAME,
PNK_INTRINSICNAME,
PNK_NUMBER,
PNK_STRING,
PNK_REGEXP,

View File

@ -117,7 +117,8 @@ Parser::Parser(JSContext *cx, const CompileOptions &options,
sct(NULL),
keepAtoms(cx->runtime),
foldConstants(foldConstants),
compileAndGo(options.compileAndGo)
compileAndGo(options.compileAndGo),
allowIntrinsicsCalls(options.allowIntrinsicsCalls)
{
cx->activeCompilations++;
}
@ -6497,6 +6498,30 @@ Parser::identifierName(bool afterDoubleDot)
return node;
}
ParseNode *
Parser::intrinsicName()
{
JS_ASSERT(tokenStream.isCurrentTokenType(TOK_MOD));
if (tokenStream.getToken() != TOK_NAME) {
reportError(NULL, JSMSG_SYNTAX_ERROR);
return NULL;
}
PropertyName *name = tokenStream.currentToken().name();
if (!(name == context->runtime->atomState._CallFunctionAtom ||
context->global()->hasIntrinsicFunction(context, name)))
{
reportError(NULL, JSMSG_INTRINSIC_NOT_DEFINED, JS_EncodeString(context, name));
return NULL;
}
ParseNode *node = NameNode::create(PNK_INTRINSICNAME, name, this, this->tc);
if (!node)
return NULL;
JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NAME);
node->setOp(JSOP_INTRINSICNAME);
return node;
}
#if JS_HAS_XML_SUPPORT
ParseNode *
Parser::starOrAtPropertyIdentifier(TokenKind tt)
@ -7054,6 +7079,12 @@ Parser::primaryExpr(TokenKind tt, bool afterDoubleDot)
case TOK_NULL:
return new_<NullLiteral>(tokenStream.currentToken().pos);
case TOK_MOD:
if (allowIntrinsicsCalls)
return intrinsicName();
else
goto syntaxerror;
case TOK_ERROR:
/* The scanner or one of its subroutines reported the error. */
return NULL;

View File

@ -54,6 +54,12 @@ struct Parser : private AutoGCRooter
/* Script can optimize name references based on scope chain. */
const bool compileAndGo:1;
/*
* Self-hosted scripts can use the special syntax %funName(..args) to call
* internal functions.
*/
const bool allowIntrinsicsCalls:1;
public:
Parser(JSContext *cx, const CompileOptions &options,
const jschar *chars, size_t length, bool foldConstants);
@ -230,6 +236,7 @@ struct Parser : private AutoGCRooter
bool checkForFunctionNode(PropertyName *name, ParseNode *node);
ParseNode *identifierName(bool afterDoubleDot);
ParseNode *intrinsicName();
#if JS_HAS_XML_SUPPORT
// True if E4X syntax is allowed in the current syntactic context. Note this

View File

@ -352,3 +352,4 @@ MSG_DEF(JSMSG_FUNCTION_ARGUMENTS_AND_REST, 298, 0, JSEXN_ERR, "the 'arguments' p
MSG_DEF(JSMSG_REST_WITH_DEFAULT, 299, 0, JSEXN_SYNTAXERR, "rest parameter may not have a default")
MSG_DEF(JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT, 300, 0, JSEXN_SYNTAXERR, "parameter(s) with default followed by parameter without default")
MSG_DEF(JSMSG_YIELD_IN_DEFAULT, 301, 0, JSEXN_SYNTAXERR, "yield in default expression")
MSG_DEF(JSMSG_INTRINSIC_NOT_DEFINED, 302, 1, JSEXN_REFERENCEERR, "no intrinsic function {0}")

View File

@ -570,6 +570,8 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx)
case JSOP_RETRVAL:
case JSOP_GETGNAME:
case JSOP_CALLGNAME:
case JSOP_INTRINSICNAME:
case JSOP_CALLINTRINSIC:
case JSOP_SETGNAME:
case JSOP_REGEXP:
case JSOP_OBJECT:

View File

@ -5067,7 +5067,8 @@ JS::CompileOptions::CompileOptions(JSContext *cx)
filename(NULL),
lineno(1),
compileAndGo(cx->hasRunOption(JSOPTION_COMPILE_N_GO)),
noScriptRval(cx->hasRunOption(JSOPTION_NO_SCRIPT_RVAL))
noScriptRval(cx->hasRunOption(JSOPTION_NO_SCRIPT_RVAL)),
allowIntrinsicsCalls(false)
{
}

View File

@ -4090,7 +4090,7 @@ struct JSClass {
* with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
* prevously allowed, but is now an ES5 violation and thus unsupported.
*/
#define JSCLASS_GLOBAL_SLOT_COUNT (JSProto_LIMIT * 3 + 23)
#define JSCLASS_GLOBAL_SLOT_COUNT (JSProto_LIMIT * 3 + 24)
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
#define JSCLASS_GLOBAL_FLAGS \
@ -4962,6 +4962,7 @@ struct CompileOptions {
unsigned lineno;
bool compileAndGo;
bool noScriptRval;
bool allowIntrinsicsCalls;
CompileOptions(JSContext *cx);
CompileOptions &setPrincipals(JSPrincipals *p) { principals = p; return *this; }
@ -4973,6 +4974,7 @@ struct CompileOptions {
}
CompileOptions &setCompileAndGo(bool cng) { compileAndGo = cng; return *this; }
CompileOptions &setNoScriptRval(bool nsr) { noScriptRval = nsr; return *this; }
CompileOptions &setAllowIntrinsicsCalls(bool aic) { allowIntrinsicsCalls = aic; return *this; }
};
extern JS_PUBLIC_API(JSScript *)

View File

@ -149,3 +149,4 @@ DEFINE_ATOM(unescape, "unescape")
DEFINE_ATOM(uneval, "uneval")
DEFINE_ATOM(unwatch, "unwatch")
DEFINE_ATOM(watch, "watch")
DEFINE_ATOM(_CallFunction, "_CallFunction")

View File

@ -3394,11 +3394,13 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
}
case JSOP_NAME:
case JSOP_CALLNAME: {
case JSOP_INTRINSICNAME:
case JSOP_CALLNAME:
case JSOP_CALLINTRINSIC: {
TypeSet *seen = bytecodeTypes(pc);
addTypeBarrier(cx, pc, seen, Type::UnknownType());
seen->addSubset(cx, &pushed[0]);
if (op == JSOP_CALLNAME)
if (op == JSOP_CALLNAME || op == JSOP_CALLINTRINSIC)
pushed[0].addPropagateThis(cx, script, pc, Type::UnknownType());
break;
}

View File

@ -1418,8 +1418,6 @@ ADD_EMPTY_CASE(JSOP_NOP)
ADD_EMPTY_CASE(JSOP_UNUSED1)
ADD_EMPTY_CASE(JSOP_UNUSED2)
ADD_EMPTY_CASE(JSOP_UNUSED3)
ADD_EMPTY_CASE(JSOP_UNUSED8)
ADD_EMPTY_CASE(JSOP_UNUSED9)
ADD_EMPTY_CASE(JSOP_UNUSED10)
ADD_EMPTY_CASE(JSOP_UNUSED11)
ADD_EMPTY_CASE(JSOP_UNUSED12)
@ -2524,6 +2522,19 @@ BEGIN_CASE(JSOP_CALLNAME)
}
END_CASE(JSOP_NAME)
BEGIN_CASE(JSOP_INTRINSICNAME)
BEGIN_CASE(JSOP_CALLINTRINSIC)
{
RootedValue &rval = rootValue0;
if (!IntrinsicNameOperation(cx, script, regs.pc, rval.address()))
goto error;
PUSH_COPY(rval);
TypeScript::Monitor(cx, script, regs.pc, rval);
}
END_CASE(JSOP_INTRINSICNAME)
BEGIN_CASE(JSOP_UINT16)
PUSH_INT32((int32_t) GET_UINT16(regs.pc));
END_CASE(JSOP_UINT16)

View File

@ -21,6 +21,7 @@
#include "jsfuninlines.h"
#include "jsinferinlines.h"
#include "jsopcodeinlines.h"
#include "jspropertycacheinlines.h"
#include "jstypedarrayinlines.h"
@ -359,6 +360,17 @@ SetPropertyOperation(JSContext *cx, jsbytecode *pc, const Value &lval, const Val
return true;
}
inline bool
IntrinsicNameOperation(JSContext *cx, JSScript *script, jsbytecode *pc, Value *vp)
{
JSOp op = JSOp(*pc);
RootedPropertyName name(cx);
name = GetNameFromBytecode(cx, script, pc, op);
JSFunction *fun = cx->global()->getIntrinsicFunction(cx, name);
vp->setObject(*fun);
return true;
}
inline bool
NameOperation(JSContext *cx, JSScript *script, jsbytecode *pc, Value *vp)
{

View File

@ -350,9 +350,17 @@ OPDEF(JSOP_DECALIASEDVAR, 140,"decaliasedvar",NULL, 10, 0, 1, 15, JOF_SCOPEC
OPDEF(JSOP_ALIASEDVARINC, 141,"aliasedvarinc",NULL, 10, 0, 1, 15, JOF_SCOPECOORD|JOF_NAME|JOF_INC|JOF_POST|JOF_TMPSLOT3|JOF_DECOMPOSE)
OPDEF(JSOP_ALIASEDVARDEC, 142,"aliasedvardec",NULL, 10, 0, 1, 15, JOF_SCOPECOORD|JOF_NAME|JOF_DEC|JOF_POST|JOF_TMPSLOT3|JOF_DECOMPOSE)
/*
* Intrinsic names have the syntax %name and can only be used when the
* CompileOptions flag "allowIntrinsicsCalls" is set.
*
* They are used to access intrinsic functions the runtime doesn't give client
* JS code access to from self-hosted code.
*/
OPDEF(JSOP_INTRINSICNAME, 143, "intrinsicname", NULL, 5, 0, 1, 19, JOF_ATOM|JOF_NAME|JOF_TYPESET)
OPDEF(JSOP_CALLINTRINSIC, 144, "callintrinsic", NULL, 5, 0, 1, 19, JOF_ATOM|JOF_NAME|JOF_TYPESET)
/* Unused. */
OPDEF(JSOP_UNUSED8, 143,"unused8", NULL, 1, 0, 0, 0, JOF_BYTE)
OPDEF(JSOP_UNUSED9, 144,"unused9", NULL, 1, 0, 0, 0, JOF_BYTE)
OPDEF(JSOP_UNUSED10, 145,"unused10", NULL, 1, 0, 0, 0, JOF_BYTE)
OPDEF(JSOP_UNUSED11, 146,"unused11", NULL, 1, 0, 0, 0, JOF_BYTE)
OPDEF(JSOP_UNUSED12, 147,"unused12", NULL, 1, 0, 0, 0, JOF_BYTE)

View File

@ -4,6 +4,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef jsopcodeinlines_h__
#define jsopcodeinlines_h__
#include "jsautooplen.h"
#include "frontend/BytecodeEmitter.h"
@ -118,3 +121,5 @@ public:
};
}
#endif /* jsopcodeinlines_h__ */

View File

@ -2645,6 +2645,15 @@ mjit::Compiler::generateMethod()
}
END_CASE(JSOP_NAME)
BEGIN_CASE(JSOP_INTRINSICNAME)
BEGIN_CASE(JSOP_CALLINTRINSIC)
{
PropertyName *name = script->getName(GET_UINT32_INDEX(PC));
jsop_intrinsicname(name, knownPushedType(0));
frame.extra(frame.peek(-1)).name = name;
}
END_CASE(JSOP_INTRINSICNAME)
BEGIN_CASE(JSOP_IMPLICITTHIS)
{
prepareStubCall(Uses(0));
@ -5552,6 +5561,13 @@ mjit::Compiler::jsop_setprop(PropertyName *name, bool popGuaranteed)
return true;
}
void
mjit::Compiler::jsop_intrinsicname(PropertyName *name, JSValueType type)
{
JSFunction *fun = cx->global().get()->getIntrinsicFunction(cx, name);
frame.push(ObjectValue(*fun));
}
void
mjit::Compiler::jsop_name(PropertyName *name, JSValueType type)
{

View File

@ -653,6 +653,7 @@ private:
bool jsop_setprop(PropertyName *name, bool popGuaranteed);
void jsop_setprop_slow(PropertyName *name);
bool jsop_instanceof();
void jsop_intrinsicname(PropertyName *name, JSValueType type);
void jsop_name(PropertyName *name, JSValueType type);
bool jsop_xname(PropertyName *name);
void enterBlock(StaticBlockObject *block);

View File

@ -213,6 +213,13 @@ GlobalObject::setProtoGetter(JSFunction *protoGetter)
setSlot(PROTO_GETTER, ObjectValue(*protoGetter));
}
void
GlobalObject::setIntrinsicsHolder(JSObject *obj)
{
JS_ASSERT(getSlotRef(INTRINSICS).isUndefined());
setSlot(INTRINSICS, ObjectValue(*obj));
}
} // namespace js
#endif

View File

@ -173,6 +173,10 @@ ProtoSetter(JSContext *cx, unsigned argc, Value *vp)
return CallNonGenericMethod(cx, TestProtoSetterThis, ProtoSetterImpl, args);
}
JSFunctionSpec intrinsic_functions[] = {
JS_FN("ThrowTypeError", ThrowTypeError, 0,0),
JS_FS_END
};
JSObject *
GlobalObject::initFunctionAndObjectClasses(JSContext *cx)
{
@ -369,14 +373,20 @@ GlobalObject::initFunctionAndObjectClasses(JSContext *cx)
self->setOriginalEval(evalobj);
/* ES5 13.2.3: Construct the unique [[ThrowTypeError]] function object. */
RootedFunction throwTypeError(cx);
throwTypeError = js_NewFunction(cx, NULL, ThrowTypeError, 0, 0, self, NULL);
RootedFunction throwTypeError(cx, js_NewFunction(cx, NULL, ThrowTypeError, 0, 0, self, NULL));
if (!throwTypeError)
return NULL;
if (!throwTypeError->preventExtensions(cx))
return NULL;
self->setThrowTypeError(throwTypeError);
RootedObject intrinsicsHolder(cx, JS_NewObject(cx, NULL, NULL, self));
if (!intrinsicsHolder)
return NULL;
self->setIntrinsicsHolder(intrinsicsHolder);
if (!JS_DefineFunctions(cx, intrinsicsHolder, intrinsic_functions))
return NULL;
/*
* The global object should have |Object.prototype| as its [[Prototype]].
* Eventually we'd like to have standard classes be there from the start,

View File

@ -100,9 +100,10 @@ class GlobalObject : public JSObject
static const unsigned RUNTIME_CODEGEN_ENABLED = FUNCTION_NS + 1;
static const unsigned FLAGS = RUNTIME_CODEGEN_ENABLED + 1;
static const unsigned DEBUGGERS = FLAGS + 1;
static const unsigned INTRINSICS = DEBUGGERS + 1;
/* Total reserved-slot count for global objects. */
static const unsigned RESERVED_SLOTS = DEBUGGERS + 1;
static const unsigned RESERVED_SLOTS = INTRINSICS + 1;
void staticAsserts() {
/*
@ -135,6 +136,8 @@ class GlobalObject : public JSObject
inline void setOriginalEval(JSObject *evalobj);
inline void setProtoGetter(JSFunction *protoGetter);
inline void setIntrinsicsHolder(JSObject *obj);
Value getConstructor(JSProtoKey key) const {
JS_ASSERT(key <= JSProto_LIMIT);
return getSlot(key);
@ -360,6 +363,20 @@ class GlobalObject : public JSObject
return &self->getPrototype(JSProto_DataView).toObject();
}
bool hasIntrinsicFunction(JSContext *cx, PropertyName *name) {
RootedObject holder(cx, &getSlotRef(INTRINSICS).toObject());
Value fun = NullValue();
return HasDataProperty(cx, holder, NameToId(name), &fun);
}
JSFunction *getIntrinsicFunction(JSContext *cx, PropertyName *name) {
RootedObject holder(cx, &getSlotRef(INTRINSICS).toObject());
Value fun = NullValue();
DebugOnly<bool> ok = HasDataProperty(cx, holder, NameToId(name), &fun);
JS_ASSERT(ok);
return fun.toObject().toFunction();
}
inline RegExpStatics *getRegExpStatics() const;
JSObject *getThrowTypeError() const {

View File

@ -25,7 +25,7 @@ namespace js {
* and saved versions. If deserialization fails, the data should be
* invalidated if possible.
*/
static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 124);
static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 125);
class XDRBuffer {
public: