mirror of
https://github.com/UOX3DevTeam/UOX3
synced 2026-08-13 12:27:04 -04:00
1856 lines
57 KiB
C++
1856 lines
57 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=4 sw=4 et tw=99:
|
|
*
|
|
* ***** 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 Mozilla SpiderMonkey JavaScript 1.9 code, released
|
|
* May 28, 2008.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Brendan Eich <brendan@mozilla.org>
|
|
*
|
|
* Contributor(s):
|
|
* David Anderson <danderson@mozilla.com>
|
|
* David Mandelin <dmandelin@mozilla.com>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either of 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 ***** */
|
|
#include "jsbool.h"
|
|
#include "jscntxt.h"
|
|
#include "jsemit.h"
|
|
#include "jslibmath.h"
|
|
#include "jsnum.h"
|
|
#include "jsscope.h"
|
|
#include "jsobjinlines.h"
|
|
#include "jsscriptinlines.h"
|
|
|
|
#include "methodjit/MethodJIT.h"
|
|
#include "methodjit/Compiler.h"
|
|
#include "methodjit/StubCalls.h"
|
|
#include "methodjit/FrameState-inl.h"
|
|
|
|
#include "jsautooplen.h"
|
|
|
|
using namespace js;
|
|
using namespace js::mjit;
|
|
|
|
typedef JSC::MacroAssembler::RegisterID RegisterID;
|
|
|
|
RegisterID
|
|
mjit::Compiler::rightRegForShift(FrameEntry *rhs)
|
|
{
|
|
#if defined(JS_CPU_X86) || defined(JS_CPU_X64)
|
|
/*
|
|
* Gross: RHS _must_ be in ECX, on x86.
|
|
* Note that we take this first so that we can't up with other register
|
|
* allocations (below) owning ecx before rhs.
|
|
*/
|
|
RegisterID reg = JSC::X86Registers::ecx;
|
|
if (!rhs->isConstant())
|
|
frame.copyDataIntoReg(rhs, reg);
|
|
return reg;
|
|
#else
|
|
if (rhs->isConstant())
|
|
return frame.allocReg();
|
|
return frame.copyDataIntoReg(rhs);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_rsh_const_int(FrameEntry *lhs, FrameEntry *rhs)
|
|
{
|
|
RegisterID rhsData = rightRegForShift(rhs);
|
|
RegisterID result = frame.allocReg();
|
|
masm.move(Imm32(lhs->getValue().toInt32()), result);
|
|
masm.rshift32(rhsData, result);
|
|
|
|
frame.freeReg(rhsData);
|
|
frame.popn(2);
|
|
frame.pushTypedPayload(JSVAL_TYPE_INT32, result);
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_rsh_int_int(FrameEntry *lhs, FrameEntry *rhs)
|
|
{
|
|
RegisterID rhsData = rightRegForShift(rhs);
|
|
RegisterID lhsData = frame.copyDataIntoReg(lhs);
|
|
masm.rshift32(rhsData, lhsData);
|
|
frame.freeReg(rhsData);
|
|
frame.popn(2);
|
|
frame.pushTypedPayload(JSVAL_TYPE_INT32, lhsData);
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_rsh_int_const(FrameEntry *lhs, FrameEntry *rhs)
|
|
{
|
|
int32 shiftAmount = rhs->getValue().toInt32();
|
|
|
|
if (!shiftAmount) {
|
|
frame.pop();
|
|
return;
|
|
}
|
|
|
|
RegisterID result = frame.copyDataIntoReg(lhs);
|
|
masm.rshift32(Imm32(shiftAmount), result);
|
|
frame.popn(2);
|
|
frame.pushTypedPayload(JSVAL_TYPE_INT32, result);
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_rsh_unknown_const(FrameEntry *lhs, FrameEntry *rhs)
|
|
{
|
|
int32 shiftAmount = rhs->getValue().toInt32();
|
|
|
|
RegisterID lhsType = frame.tempRegForType(lhs);
|
|
frame.pinReg(lhsType);
|
|
RegisterID lhsData = frame.copyDataIntoReg(lhs);
|
|
frame.unpinReg(lhsType);
|
|
|
|
Jump lhsIntGuard = masm.testInt32(Assembler::NotEqual, lhsType);
|
|
stubcc.linkExitDirect(lhsIntGuard, stubcc.masm.label());
|
|
|
|
Jump lhsDoubleGuard = stubcc.masm.testDouble(Assembler::NotEqual, lhsType);
|
|
frame.loadDouble(lhs, FPRegisters::First, stubcc.masm);
|
|
Jump lhsTruncateGuard = stubcc.masm.branchTruncateDoubleToInt32(FPRegisters::First, lhsData);
|
|
stubcc.crossJump(stubcc.masm.jump(), masm.label());
|
|
|
|
lhsDoubleGuard.linkTo(stubcc.masm.label(), &stubcc.masm);
|
|
lhsTruncateGuard.linkTo(stubcc.masm.label(), &stubcc.masm);
|
|
|
|
frame.sync(stubcc.masm, Uses(2));
|
|
OOL_STUBCALL(stubs::Rsh);
|
|
|
|
if (shiftAmount)
|
|
masm.rshift32(Imm32(shiftAmount), lhsData);
|
|
|
|
frame.popn(2);
|
|
frame.pushTypedPayload(JSVAL_TYPE_INT32, lhsData);
|
|
|
|
stubcc.rejoin(Changes(1));
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_rsh_const_unknown(FrameEntry *lhs, FrameEntry *rhs)
|
|
{
|
|
RegisterID rhsData = rightRegForShift(rhs);
|
|
RegisterID rhsType = frame.tempRegForType(rhs);
|
|
frame.pinReg(rhsType);
|
|
RegisterID result = frame.allocReg();
|
|
frame.unpinReg(rhsType);
|
|
|
|
Jump rhsIntGuard = masm.testInt32(Assembler::NotEqual, rhsType);
|
|
stubcc.linkExit(rhsIntGuard, Uses(2));
|
|
stubcc.leave();
|
|
OOL_STUBCALL(stubs::Rsh);
|
|
masm.move(Imm32(lhs->getValue().toInt32()), result);
|
|
masm.rshift32(rhsData, result);
|
|
frame.freeReg(rhsData);
|
|
|
|
frame.popn(2);
|
|
frame.pushTypedPayload(JSVAL_TYPE_INT32, result);
|
|
stubcc.rejoin(Changes(1));
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_rsh_int_unknown(FrameEntry *lhs, FrameEntry *rhs)
|
|
{
|
|
RegisterID rhsData = rightRegForShift(rhs);
|
|
RegisterID rhsType = frame.tempRegForType(rhs);
|
|
frame.pinReg(rhsType);
|
|
RegisterID lhsData = frame.copyDataIntoReg(lhs);
|
|
frame.unpinReg(rhsType);
|
|
|
|
Jump rhsIntGuard = masm.testInt32(Assembler::NotEqual, rhsType);
|
|
stubcc.linkExit(rhsIntGuard, Uses(2));
|
|
stubcc.leave();
|
|
OOL_STUBCALL(stubs::Rsh);
|
|
|
|
masm.rshift32(rhsData, lhsData);
|
|
frame.freeReg(rhsData);
|
|
frame.popn(2);
|
|
frame.pushTypedPayload(JSVAL_TYPE_INT32, lhsData);
|
|
|
|
stubcc.rejoin(Changes(1));
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_rsh_unknown_any(FrameEntry *lhs, FrameEntry *rhs)
|
|
{
|
|
JS_ASSERT(!lhs->isTypeKnown());
|
|
JS_ASSERT(!rhs->isNotType(JSVAL_TYPE_INT32));
|
|
|
|
/* Allocate registers. */
|
|
RegisterID rhsData = rightRegForShift(rhs);
|
|
|
|
MaybeRegisterID rhsType;
|
|
if (!rhs->isTypeKnown()) {
|
|
rhsType.setReg(frame.tempRegForType(rhs));
|
|
frame.pinReg(rhsType.reg());
|
|
}
|
|
|
|
RegisterID lhsData = frame.copyDataIntoReg(lhs);
|
|
MaybeRegisterID lhsType;
|
|
if (rhsType.isSet() && frame.haveSameBacking(lhs, rhs))
|
|
lhsType = rhsType;
|
|
else
|
|
lhsType = frame.tempRegForType(lhs);
|
|
|
|
/* Non-integer rhs jumps to stub. */
|
|
MaybeJump rhsIntGuard;
|
|
if (rhsType.isSet()) {
|
|
rhsIntGuard.setJump(masm.testInt32(Assembler::NotEqual, rhsType.reg()));
|
|
frame.unpinReg(rhsType.reg());
|
|
}
|
|
|
|
/* Non-integer lhs jumps to double guard. */
|
|
Jump lhsIntGuard = masm.testInt32(Assembler::NotEqual, lhsType.reg());
|
|
stubcc.linkExitDirect(lhsIntGuard, stubcc.masm.label());
|
|
|
|
/* Attempt to convert lhs double to int32. */
|
|
Jump lhsDoubleGuard = stubcc.masm.testDouble(Assembler::NotEqual, lhsType.reg());
|
|
frame.loadDouble(lhs, FPRegisters::First, stubcc.masm);
|
|
Jump lhsTruncateGuard = stubcc.masm.branchTruncateDoubleToInt32(FPRegisters::First, lhsData);
|
|
stubcc.crossJump(stubcc.masm.jump(), masm.label());
|
|
|
|
lhsDoubleGuard.linkTo(stubcc.masm.label(), &stubcc.masm);
|
|
lhsTruncateGuard.linkTo(stubcc.masm.label(), &stubcc.masm);
|
|
|
|
if (rhsIntGuard.isSet())
|
|
stubcc.linkExitDirect(rhsIntGuard.getJump(), stubcc.masm.label());
|
|
frame.sync(stubcc.masm, Uses(2));
|
|
OOL_STUBCALL(stubs::Rsh);
|
|
|
|
masm.rshift32(rhsData, lhsData);
|
|
|
|
frame.freeReg(rhsData);
|
|
frame.popn(2);
|
|
frame.pushTypedPayload(JSVAL_TYPE_INT32, lhsData);
|
|
|
|
stubcc.rejoin(Changes(1));
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_rsh()
|
|
{
|
|
FrameEntry *rhs = frame.peek(-1);
|
|
FrameEntry *lhs = frame.peek(-2);
|
|
|
|
if (tryBinaryConstantFold(cx, frame, JSOP_RSH, lhs, rhs))
|
|
return;
|
|
|
|
if (lhs->isNotType(JSVAL_TYPE_INT32) || rhs->isNotType(JSVAL_TYPE_INT32)) {
|
|
prepareStubCall(Uses(2));
|
|
INLINE_STUBCALL(stubs::Rsh);
|
|
frame.popn(2);
|
|
frame.pushSyncedType(JSVAL_TYPE_INT32);
|
|
return;
|
|
}
|
|
|
|
JS_ASSERT(!(lhs->isConstant() && rhs->isConstant()));
|
|
if (lhs->isConstant()) {
|
|
if (rhs->isType(JSVAL_TYPE_INT32))
|
|
jsop_rsh_const_int(lhs, rhs);
|
|
else
|
|
jsop_rsh_const_unknown(lhs, rhs);
|
|
} else if (rhs->isConstant()) {
|
|
if (lhs->isType(JSVAL_TYPE_INT32))
|
|
jsop_rsh_int_const(lhs, rhs);
|
|
else
|
|
jsop_rsh_unknown_const(lhs, rhs);
|
|
} else {
|
|
if (lhs->isType(JSVAL_TYPE_INT32) && rhs->isType(JSVAL_TYPE_INT32))
|
|
jsop_rsh_int_int(lhs, rhs);
|
|
else if (lhs->isType(JSVAL_TYPE_INT32))
|
|
jsop_rsh_int_unknown(lhs, rhs);
|
|
else
|
|
jsop_rsh_unknown_any(lhs, rhs);
|
|
}
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_bitnot()
|
|
{
|
|
FrameEntry *top = frame.peek(-1);
|
|
|
|
/* We only want to handle integers here. */
|
|
if (top->isTypeKnown() && top->getKnownType() != JSVAL_TYPE_INT32) {
|
|
prepareStubCall(Uses(1));
|
|
INLINE_STUBCALL(stubs::BitNot);
|
|
frame.pop();
|
|
frame.pushSyncedType(JSVAL_TYPE_INT32);
|
|
return;
|
|
}
|
|
|
|
/* Test the type. */
|
|
bool stubNeeded = false;
|
|
if (!top->isTypeKnown()) {
|
|
Jump intFail = frame.testInt32(Assembler::NotEqual, top);
|
|
stubcc.linkExit(intFail, Uses(1));
|
|
frame.learnType(top, JSVAL_TYPE_INT32);
|
|
stubNeeded = true;
|
|
}
|
|
|
|
if (stubNeeded) {
|
|
stubcc.leave();
|
|
OOL_STUBCALL(stubs::BitNot);
|
|
}
|
|
|
|
RegisterID reg = frame.ownRegForData(top);
|
|
masm.not32(reg);
|
|
frame.pop();
|
|
frame.pushTypedPayload(JSVAL_TYPE_INT32, reg);
|
|
|
|
if (stubNeeded)
|
|
stubcc.rejoin(Changes(1));
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_bitop(JSOp op)
|
|
{
|
|
FrameEntry *rhs = frame.peek(-1);
|
|
FrameEntry *lhs = frame.peek(-2);
|
|
|
|
VoidStub stub;
|
|
switch (op) {
|
|
case JSOP_BITOR:
|
|
stub = stubs::BitOr;
|
|
break;
|
|
case JSOP_BITAND:
|
|
stub = stubs::BitAnd;
|
|
break;
|
|
case JSOP_BITXOR:
|
|
stub = stubs::BitXor;
|
|
break;
|
|
case JSOP_LSH:
|
|
stub = stubs::Lsh;
|
|
break;
|
|
case JSOP_URSH:
|
|
stub = stubs::Ursh;
|
|
break;
|
|
default:
|
|
JS_NOT_REACHED("wat");
|
|
return;
|
|
}
|
|
|
|
bool lhsIntOrDouble = !(lhs->isNotType(JSVAL_TYPE_DOUBLE) &&
|
|
lhs->isNotType(JSVAL_TYPE_INT32));
|
|
|
|
/* Fast-path double to int conversion. */
|
|
if (!lhs->isConstant() && rhs->isConstant() && lhsIntOrDouble &&
|
|
rhs->isType(JSVAL_TYPE_INT32) && rhs->getValue().toInt32() == 0 &&
|
|
(op == JSOP_BITOR || op == JSOP_LSH)) {
|
|
RegisterID reg = frame.copyDataIntoReg(lhs);
|
|
if (lhs->isType(JSVAL_TYPE_INT32)) {
|
|
frame.popn(2);
|
|
frame.pushTypedPayload(JSVAL_TYPE_INT32, reg);
|
|
return;
|
|
}
|
|
MaybeJump isInt;
|
|
if (!lhs->isType(JSVAL_TYPE_DOUBLE)) {
|
|
RegisterID typeReg = frame.tempRegForType(lhs);
|
|
isInt = masm.testInt32(Assembler::Equal, typeReg);
|
|
Jump notDouble = masm.testDouble(Assembler::NotEqual, typeReg);
|
|
stubcc.linkExit(notDouble, Uses(2));
|
|
}
|
|
frame.loadDouble(lhs, FPRegisters::First, masm);
|
|
|
|
Jump truncateGuard = masm.branchTruncateDoubleToInt32(FPRegisters::First, reg);
|
|
stubcc.linkExit(truncateGuard, Uses(2));
|
|
stubcc.leave();
|
|
OOL_STUBCALL(stub);
|
|
|
|
if (isInt.isSet())
|
|
isInt.get().linkTo(masm.label(), &masm);
|
|
frame.popn(2);
|
|
frame.pushTypedPayload(JSVAL_TYPE_INT32, reg);
|
|
stubcc.rejoin(Changes(1));
|
|
return;
|
|
}
|
|
|
|
/* We only want to handle integers here. */
|
|
if (rhs->isNotType(JSVAL_TYPE_INT32) || lhs->isNotType(JSVAL_TYPE_INT32) ||
|
|
(op == JSOP_URSH && rhs->isConstant() && rhs->getValue().toInt32() % 32 == 0)) {
|
|
prepareStubCall(Uses(2));
|
|
INLINE_STUBCALL(stub);
|
|
frame.popn(2);
|
|
if (op == JSOP_URSH)
|
|
frame.pushSynced();
|
|
else
|
|
frame.pushSyncedType(JSVAL_TYPE_INT32);
|
|
return;
|
|
}
|
|
|
|
/* Test the types. */
|
|
bool stubNeeded = false;
|
|
if (!rhs->isTypeKnown()) {
|
|
Jump rhsFail = frame.testInt32(Assembler::NotEqual, rhs);
|
|
stubcc.linkExit(rhsFail, Uses(2));
|
|
frame.learnType(rhs, JSVAL_TYPE_INT32);
|
|
stubNeeded = true;
|
|
}
|
|
if (!lhs->isTypeKnown() && !frame.haveSameBacking(lhs, rhs)) {
|
|
Jump lhsFail = frame.testInt32(Assembler::NotEqual, lhs);
|
|
stubcc.linkExit(lhsFail, Uses(2));
|
|
stubNeeded = true;
|
|
}
|
|
|
|
if (lhs->isConstant() && rhs->isConstant()) {
|
|
int32 L = lhs->getValue().toInt32();
|
|
int32 R = rhs->getValue().toInt32();
|
|
|
|
frame.popn(2);
|
|
switch (op) {
|
|
case JSOP_BITOR:
|
|
frame.push(Int32Value(L | R));
|
|
return;
|
|
case JSOP_BITXOR:
|
|
frame.push(Int32Value(L ^ R));
|
|
return;
|
|
case JSOP_BITAND:
|
|
frame.push(Int32Value(L & R));
|
|
return;
|
|
case JSOP_LSH:
|
|
frame.push(Int32Value(L << R));
|
|
return;
|
|
case JSOP_URSH:
|
|
{
|
|
uint32 unsignedL;
|
|
if (ValueToECMAUint32(cx, lhs->getValue(), (uint32_t*)&unsignedL)) {
|
|
frame.push(NumberValue(uint32(unsignedL >> (R & 31))));
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
JS_NOT_REACHED("say wat");
|
|
}
|
|
}
|
|
|
|
RegisterID reg;
|
|
|
|
switch (op) {
|
|
case JSOP_BITOR:
|
|
case JSOP_BITXOR:
|
|
case JSOP_BITAND:
|
|
{
|
|
/* Commutative, and we're guaranteed both are ints. */
|
|
if (lhs->isConstant()) {
|
|
JS_ASSERT(!rhs->isConstant());
|
|
FrameEntry *temp = rhs;
|
|
rhs = lhs;
|
|
lhs = temp;
|
|
}
|
|
|
|
reg = frame.ownRegForData(lhs);
|
|
if (rhs->isConstant()) {
|
|
if (op == JSOP_BITAND)
|
|
masm.and32(Imm32(rhs->getValue().toInt32()), reg);
|
|
else if (op == JSOP_BITXOR)
|
|
masm.xor32(Imm32(rhs->getValue().toInt32()), reg);
|
|
else
|
|
masm.or32(Imm32(rhs->getValue().toInt32()), reg);
|
|
} else if (frame.shouldAvoidDataRemat(rhs)) {
|
|
if (op == JSOP_BITAND)
|
|
masm.and32(masm.payloadOf(frame.addressOf(rhs)), reg);
|
|
else if (op == JSOP_BITXOR)
|
|
masm.xor32(masm.payloadOf(frame.addressOf(rhs)), reg);
|
|
else
|
|
masm.or32(masm.payloadOf(frame.addressOf(rhs)), reg);
|
|
} else {
|
|
RegisterID rhsReg = frame.tempRegForData(rhs);
|
|
if (op == JSOP_BITAND)
|
|
masm.and32(rhsReg, reg);
|
|
else if (op == JSOP_BITXOR)
|
|
masm.xor32(rhsReg, reg);
|
|
else
|
|
masm.or32(rhsReg, reg);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case JSOP_LSH:
|
|
case JSOP_URSH:
|
|
{
|
|
/* Not commutative. */
|
|
if (rhs->isConstant()) {
|
|
RegisterID reg = frame.ownRegForData(lhs);
|
|
int shift = rhs->getValue().toInt32() & 0x1F;
|
|
|
|
if (shift) {
|
|
if (op == JSOP_LSH)
|
|
masm.lshift32(Imm32(shift), reg);
|
|
else
|
|
masm.urshift32(Imm32(shift), reg);
|
|
}
|
|
if (stubNeeded) {
|
|
stubcc.leave();
|
|
OOL_STUBCALL(stub);
|
|
}
|
|
frame.popn(2);
|
|
|
|
/* x >>> 0 may result in a double, handled above. */
|
|
JS_ASSERT_IF(op == JSOP_URSH, shift >= 1);
|
|
frame.pushTypedPayload(JSVAL_TYPE_INT32, reg);
|
|
|
|
if (stubNeeded)
|
|
stubcc.rejoin(Changes(1));
|
|
|
|
return;
|
|
}
|
|
#if defined(JS_CPU_X86) || defined(JS_CPU_X64)
|
|
/* Grosssssss! RHS _must_ be in ECX, on x86 */
|
|
RegisterID rr = frame.tempRegInMaskForData(rhs,
|
|
Registers::maskReg(JSC::X86Registers::ecx));
|
|
#else
|
|
RegisterID rr = frame.tempRegForData(rhs);
|
|
#endif
|
|
|
|
if (frame.haveSameBacking(lhs, rhs)) {
|
|
// It's okay to allocReg(). If |rr| is evicted, it won't result in
|
|
// a load, and |rr == reg| is fine since this is (x << x).
|
|
reg = frame.allocReg();
|
|
if (rr != reg)
|
|
masm.move(rr, reg);
|
|
} else {
|
|
frame.pinReg(rr);
|
|
if (lhs->isConstant()) {
|
|
reg = frame.allocReg();
|
|
masm.move(Imm32(lhs->getValue().toInt32()), reg);
|
|
} else {
|
|
reg = frame.copyDataIntoReg(lhs);
|
|
}
|
|
frame.unpinReg(rr);
|
|
}
|
|
|
|
if (op == JSOP_LSH) {
|
|
masm.lshift32(rr, reg);
|
|
} else {
|
|
masm.urshift32(rr, reg);
|
|
|
|
Jump isNegative = masm.branch32(Assembler::LessThan, reg, Imm32(0));
|
|
stubcc.linkExit(isNegative, Uses(2));
|
|
stubNeeded = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
JS_NOT_REACHED("NYI");
|
|
return;
|
|
}
|
|
|
|
if (stubNeeded) {
|
|
stubcc.leave();
|
|
OOL_STUBCALL(stub);
|
|
}
|
|
|
|
frame.pop();
|
|
frame.pop();
|
|
|
|
if (op == JSOP_URSH)
|
|
frame.pushNumber(reg, true);
|
|
else
|
|
frame.pushTypedPayload(JSVAL_TYPE_INT32, reg);
|
|
|
|
if (stubNeeded)
|
|
stubcc.rejoin(Changes(1));
|
|
}
|
|
|
|
static inline bool
|
|
CheckNullOrUndefined(FrameEntry *fe)
|
|
{
|
|
if (!fe->isTypeKnown())
|
|
return false;
|
|
JSValueType type = fe->getKnownType();
|
|
return type == JSVAL_TYPE_NULL || type == JSVAL_TYPE_UNDEFINED;
|
|
}
|
|
|
|
bool
|
|
mjit::Compiler::jsop_equality(JSOp op, BoolStub stub, jsbytecode *target, JSOp fused)
|
|
{
|
|
FrameEntry *rhs = frame.peek(-1);
|
|
FrameEntry *lhs = frame.peek(-2);
|
|
|
|
/* The compiler should have handled constant folding. */
|
|
JS_ASSERT(!(rhs->isConstant() && lhs->isConstant()));
|
|
|
|
bool lhsTest;
|
|
if ((lhsTest = CheckNullOrUndefined(lhs)) || CheckNullOrUndefined(rhs)) {
|
|
/* What's the other mask? */
|
|
FrameEntry *test = lhsTest ? rhs : lhs;
|
|
|
|
if (test->isTypeKnown())
|
|
return emitStubCmpOp(stub, target, fused);
|
|
|
|
/* The other side must be null or undefined. */
|
|
RegisterID reg = frame.ownRegForType(test);
|
|
frame.pop();
|
|
frame.pop();
|
|
|
|
/*
|
|
* :FIXME: Easier test for undefined || null?
|
|
* Maybe put them next to each other, subtract, do a single compare?
|
|
*/
|
|
|
|
if (target) {
|
|
frame.syncAndForgetEverything();
|
|
|
|
if ((op == JSOP_EQ && fused == JSOP_IFNE) ||
|
|
(op == JSOP_NE && fused == JSOP_IFEQ)) {
|
|
/*
|
|
* It would be easier to just have two jumpAndTrace calls here, but since
|
|
* each jumpAndTrace creates a TRACE IC, and since we want the bytecode
|
|
* to have a reference to the TRACE IC at the top of the loop, it's much
|
|
* better to have only one TRACE IC per loop, and hence at most one
|
|
* jumpAndTrace.
|
|
*/
|
|
Jump b1 = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_UNDEFINED));
|
|
Jump b2 = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_NULL));
|
|
Jump j1 = masm.jump();
|
|
b1.linkTo(masm.label(), &masm);
|
|
b2.linkTo(masm.label(), &masm);
|
|
Jump j2 = masm.jump();
|
|
if (!jumpAndTrace(j2, target))
|
|
return false;
|
|
j1.linkTo(masm.label(), &masm);
|
|
} else {
|
|
Jump j = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_UNDEFINED));
|
|
Jump j2 = masm.branchPtr(Assembler::NotEqual, reg, ImmType(JSVAL_TYPE_NULL));
|
|
if (!jumpAndTrace(j2, target))
|
|
return false;
|
|
j.linkTo(masm.label(), &masm);
|
|
}
|
|
} else {
|
|
Jump j = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_UNDEFINED));
|
|
Jump j2 = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_NULL));
|
|
masm.move(Imm32(op == JSOP_NE), reg);
|
|
Jump j3 = masm.jump();
|
|
j2.linkTo(masm.label(), &masm);
|
|
j.linkTo(masm.label(), &masm);
|
|
masm.move(Imm32(op == JSOP_EQ), reg);
|
|
j3.linkTo(masm.label(), &masm);
|
|
frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, reg);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return emitStubCmpOp(stub, target, fused);
|
|
}
|
|
|
|
bool
|
|
mjit::Compiler::jsop_relational(JSOp op, BoolStub stub, jsbytecode *target, JSOp fused)
|
|
{
|
|
FrameEntry *rhs = frame.peek(-1);
|
|
FrameEntry *lhs = frame.peek(-2);
|
|
|
|
/* The compiler should have handled constant folding. */
|
|
JS_ASSERT(!(rhs->isConstant() && lhs->isConstant()));
|
|
|
|
/* Always slow path... */
|
|
if ((lhs->isNotType(JSVAL_TYPE_INT32) && lhs->isNotType(JSVAL_TYPE_DOUBLE) &&
|
|
lhs->isNotType(JSVAL_TYPE_STRING)) ||
|
|
(rhs->isNotType(JSVAL_TYPE_INT32) && rhs->isNotType(JSVAL_TYPE_DOUBLE) &&
|
|
rhs->isNotType(JSVAL_TYPE_STRING))) {
|
|
if (op == JSOP_EQ || op == JSOP_NE)
|
|
return jsop_equality(op, stub, target, fused);
|
|
return emitStubCmpOp(stub, target, fused);
|
|
}
|
|
|
|
if (op == JSOP_EQ || op == JSOP_NE) {
|
|
if ((lhs->isNotType(JSVAL_TYPE_INT32) && lhs->isNotType(JSVAL_TYPE_STRING)) ||
|
|
(rhs->isNotType(JSVAL_TYPE_INT32) && rhs->isNotType(JSVAL_TYPE_STRING))) {
|
|
return emitStubCmpOp(stub, target, fused);
|
|
} else if (!target && (lhs->isType(JSVAL_TYPE_STRING) || rhs->isType(JSVAL_TYPE_STRING))) {
|
|
return emitStubCmpOp(stub, target, fused);
|
|
} else if (frame.haveSameBacking(lhs, rhs)) {
|
|
return emitStubCmpOp(stub, target, fused);
|
|
} else {
|
|
return jsop_equality_int_string(op, stub, target, fused);
|
|
}
|
|
}
|
|
|
|
if (frame.haveSameBacking(lhs, rhs)) {
|
|
return jsop_relational_self(op, stub, target, fused);
|
|
} else if (lhs->isType(JSVAL_TYPE_STRING) || rhs->isType(JSVAL_TYPE_STRING)) {
|
|
return emitStubCmpOp(stub, target, fused);
|
|
} else if (lhs->isType(JSVAL_TYPE_DOUBLE) || rhs->isType(JSVAL_TYPE_DOUBLE)) {
|
|
return jsop_relational_double(op, stub, target, fused);
|
|
} else {
|
|
return jsop_relational_full(op, stub, target, fused);
|
|
}
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_not()
|
|
{
|
|
FrameEntry *top = frame.peek(-1);
|
|
|
|
if (top->isConstant()) {
|
|
const Value &v = top->getValue();
|
|
frame.pop();
|
|
frame.push(BooleanValue(!js_ValueToBoolean(v)));
|
|
return;
|
|
}
|
|
|
|
if (top->isTypeKnown()) {
|
|
JSValueType type = top->getKnownType();
|
|
switch (type) {
|
|
case JSVAL_TYPE_INT32:
|
|
{
|
|
RegisterID data = frame.allocReg(Registers::SingleByteRegs);
|
|
if (frame.shouldAvoidDataRemat(top))
|
|
masm.loadPayload(frame.addressOf(top), data);
|
|
else
|
|
masm.move(frame.tempRegForData(top), data);
|
|
|
|
masm.set32(Assembler::Equal, data, Imm32(0), data);
|
|
|
|
frame.pop();
|
|
frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, data);
|
|
break;
|
|
}
|
|
|
|
case JSVAL_TYPE_BOOLEAN:
|
|
{
|
|
RegisterID reg = frame.ownRegForData(top);
|
|
|
|
masm.xor32(Imm32(1), reg);
|
|
|
|
frame.pop();
|
|
frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, reg);
|
|
break;
|
|
}
|
|
|
|
case JSVAL_TYPE_OBJECT:
|
|
{
|
|
frame.pop();
|
|
frame.push(BooleanValue(false));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
prepareStubCall(Uses(1));
|
|
INLINE_STUBCALL(stubs::ValueToBoolean);
|
|
|
|
RegisterID reg = Registers::ReturnReg;
|
|
frame.takeReg(reg);
|
|
masm.xor32(Imm32(1), reg);
|
|
|
|
frame.pop();
|
|
frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, reg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
RegisterID data = frame.allocReg(Registers::SingleByteRegs);
|
|
if (frame.shouldAvoidDataRemat(top))
|
|
masm.loadPayload(frame.addressOf(top), data);
|
|
else
|
|
masm.move(frame.tempRegForData(top), data);
|
|
RegisterID type = frame.tempRegForType(top);
|
|
Label syncTarget = stubcc.syncExitAndJump(Uses(1));
|
|
|
|
|
|
/* Inline path is for booleans. */
|
|
Jump jmpNotBool = masm.testBoolean(Assembler::NotEqual, type);
|
|
masm.xor32(Imm32(1), data);
|
|
|
|
|
|
/* OOL path is for int + object. */
|
|
Label lblMaybeInt32 = stubcc.masm.label();
|
|
|
|
Jump jmpNotInt32 = stubcc.masm.testInt32(Assembler::NotEqual, type);
|
|
stubcc.masm.set32(Assembler::Equal, data, Imm32(0), data);
|
|
Jump jmpInt32Exit = stubcc.masm.jump();
|
|
|
|
Label lblMaybeObject = stubcc.masm.label();
|
|
Jump jmpNotObject = stubcc.masm.testPrimitive(Assembler::Equal, type);
|
|
stubcc.masm.move(Imm32(0), data);
|
|
Jump jmpObjectExit = stubcc.masm.jump();
|
|
|
|
|
|
/* Rejoin location. */
|
|
Label lblRejoin = masm.label();
|
|
|
|
/* Patch up jumps. */
|
|
stubcc.linkExitDirect(jmpNotBool, lblMaybeInt32);
|
|
|
|
jmpNotInt32.linkTo(lblMaybeObject, &stubcc.masm);
|
|
stubcc.crossJump(jmpInt32Exit, lblRejoin);
|
|
|
|
jmpNotObject.linkTo(syncTarget, &stubcc.masm);
|
|
stubcc.crossJump(jmpObjectExit, lblRejoin);
|
|
|
|
|
|
/* Leave. */
|
|
stubcc.leave();
|
|
OOL_STUBCALL(stubs::Not);
|
|
|
|
frame.pop();
|
|
frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, data);
|
|
|
|
stubcc.rejoin(Changes(1));
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_typeof()
|
|
{
|
|
FrameEntry *fe = frame.peek(-1);
|
|
|
|
if (fe->isTypeKnown()) {
|
|
JSRuntime *rt = cx->runtime;
|
|
|
|
JSAtom *atom = NULL;
|
|
switch (fe->getKnownType()) {
|
|
case JSVAL_TYPE_STRING:
|
|
atom = rt->atomState.typeAtoms[JSTYPE_STRING];
|
|
break;
|
|
case JSVAL_TYPE_UNDEFINED:
|
|
atom = rt->atomState.typeAtoms[JSTYPE_VOID];
|
|
break;
|
|
case JSVAL_TYPE_NULL:
|
|
atom = rt->atomState.typeAtoms[JSTYPE_OBJECT];
|
|
break;
|
|
case JSVAL_TYPE_OBJECT:
|
|
atom = NULL;
|
|
break;
|
|
case JSVAL_TYPE_BOOLEAN:
|
|
atom = rt->atomState.typeAtoms[JSTYPE_BOOLEAN];
|
|
break;
|
|
default:
|
|
atom = rt->atomState.typeAtoms[JSTYPE_NUMBER];
|
|
break;
|
|
}
|
|
|
|
if (atom) {
|
|
frame.pop();
|
|
frame.push(StringValue(ATOM_TO_STRING(atom)));
|
|
return;
|
|
}
|
|
}
|
|
|
|
JSOp fused = JSOp(PC[JSOP_TYPEOF_LENGTH]);
|
|
if (fused == JSOP_STRING && !fe->isTypeKnown()) {
|
|
JSOp op = JSOp(PC[JSOP_TYPEOF_LENGTH + JSOP_STRING_LENGTH]);
|
|
|
|
if (op == JSOP_STRICTEQ || op == JSOP_EQ || op == JSOP_STRICTNE || op == JSOP_NE) {
|
|
JSAtom *atom = script->getAtom(fullAtomIndex(PC + JSOP_TYPEOF_LENGTH));
|
|
JSRuntime *rt = cx->runtime;
|
|
JSValueType type = JSVAL_TYPE_BOXED;
|
|
Assembler::Condition cond = (op == JSOP_STRICTEQ || op == JSOP_EQ)
|
|
? Assembler::Equal
|
|
: Assembler::NotEqual;
|
|
|
|
if (atom == rt->atomState.typeAtoms[JSTYPE_VOID]) {
|
|
type = JSVAL_TYPE_UNDEFINED;
|
|
} else if (atom == rt->atomState.typeAtoms[JSTYPE_STRING]) {
|
|
type = JSVAL_TYPE_STRING;
|
|
} else if (atom == rt->atomState.typeAtoms[JSTYPE_BOOLEAN]) {
|
|
type = JSVAL_TYPE_BOOLEAN;
|
|
} else if (atom == rt->atomState.typeAtoms[JSTYPE_NUMBER]) {
|
|
type = JSVAL_TYPE_INT32;
|
|
|
|
/* JSVAL_TYPE_DOUBLE is 0x0 and JSVAL_TYPE_INT32 is 0x1, use <= or > to match both */
|
|
cond = (cond == Assembler::Equal) ? Assembler::BelowOrEqual : Assembler::Above;
|
|
}
|
|
|
|
if (type != JSVAL_TYPE_BOXED) {
|
|
PC += JSOP_STRING_LENGTH;;
|
|
PC += JSOP_EQ_LENGTH;
|
|
|
|
RegisterID result = frame.allocReg(Registers::SingleByteRegs);
|
|
|
|
#if defined JS_NUNBOX32
|
|
if (frame.shouldAvoidTypeRemat(fe))
|
|
masm.set32(cond, masm.tagOf(frame.addressOf(fe)), ImmType(type), result);
|
|
else
|
|
masm.set32(cond, frame.tempRegForType(fe), ImmType(type), result);
|
|
#elif defined JS_PUNBOX64
|
|
masm.setPtr(cond, frame.tempRegForType(fe), ImmType(type), result);
|
|
#endif
|
|
|
|
frame.pop();
|
|
frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, result);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
prepareStubCall(Uses(1));
|
|
INLINE_STUBCALL(stubs::TypeOf);
|
|
frame.pop();
|
|
frame.takeReg(Registers::ReturnReg);
|
|
frame.pushTypedPayload(JSVAL_TYPE_STRING, Registers::ReturnReg);
|
|
}
|
|
|
|
bool
|
|
mjit::Compiler::booleanJumpScript(JSOp op, jsbytecode *target)
|
|
{
|
|
FrameEntry *fe = frame.peek(-1);
|
|
|
|
MaybeRegisterID type;
|
|
MaybeRegisterID data;
|
|
|
|
if (!fe->isTypeKnown() && !frame.shouldAvoidTypeRemat(fe))
|
|
type.setReg(frame.copyTypeIntoReg(fe));
|
|
data.setReg(frame.copyDataIntoReg(fe));
|
|
|
|
frame.syncAndForgetEverything();
|
|
|
|
Assembler::Condition cond = (op == JSOP_IFNE || op == JSOP_OR)
|
|
? Assembler::NonZero
|
|
: Assembler::Zero;
|
|
Assembler::Condition ncond = (op == JSOP_IFNE || op == JSOP_OR)
|
|
? Assembler::Zero
|
|
: Assembler::NonZero;
|
|
|
|
/* Inline path: Boolean guard + call script. */
|
|
MaybeJump jmpNotBool;
|
|
MaybeJump jmpNotExecScript;
|
|
if (type.isSet()) {
|
|
jmpNotBool.setJump(masm.testBoolean(Assembler::NotEqual, type.reg()));
|
|
} else {
|
|
if (!fe->isTypeKnown()) {
|
|
jmpNotBool.setJump(masm.testBoolean(Assembler::NotEqual,
|
|
frame.addressOf(fe)));
|
|
} else if (fe->isNotType(JSVAL_TYPE_BOOLEAN) &&
|
|
fe->isNotType(JSVAL_TYPE_INT32)) {
|
|
jmpNotBool.setJump(masm.jump());
|
|
}
|
|
}
|
|
|
|
/*
|
|
* TODO: We don't need the second jump if
|
|
* jumpInScript() can go from ool path to inline path.
|
|
*/
|
|
jmpNotExecScript.setJump(masm.branchTest32(ncond, data.reg(), data.reg()));
|
|
Label lblExecScript = masm.label();
|
|
Jump j = masm.jump();
|
|
|
|
|
|
/* OOL path: Conversion to boolean. */
|
|
MaybeJump jmpCvtExecScript;
|
|
MaybeJump jmpCvtRejoin;
|
|
Label lblCvtPath = stubcc.masm.label();
|
|
|
|
if (!fe->isTypeKnown() ||
|
|
!(fe->isType(JSVAL_TYPE_BOOLEAN) || fe->isType(JSVAL_TYPE_INT32))) {
|
|
stubcc.masm.infallibleVMCall(JS_FUNC_TO_DATA_PTR(void *, stubs::ValueToBoolean),
|
|
frame.localSlots());
|
|
|
|
jmpCvtExecScript.setJump(stubcc.masm.branchTest32(cond, Registers::ReturnReg,
|
|
Registers::ReturnReg));
|
|
jmpCvtRejoin.setJump(stubcc.masm.jump());
|
|
}
|
|
|
|
/* Rejoin tag. */
|
|
Label lblAfterScript = masm.label();
|
|
|
|
/* Patch up jumps. */
|
|
if (jmpNotBool.isSet())
|
|
stubcc.linkExitDirect(jmpNotBool.getJump(), lblCvtPath);
|
|
if (jmpNotExecScript.isSet())
|
|
jmpNotExecScript.getJump().linkTo(lblAfterScript, &masm);
|
|
|
|
if (jmpCvtExecScript.isSet())
|
|
stubcc.crossJump(jmpCvtExecScript.getJump(), lblExecScript);
|
|
if (jmpCvtRejoin.isSet())
|
|
stubcc.crossJump(jmpCvtRejoin.getJump(), lblAfterScript);
|
|
|
|
frame.pop();
|
|
|
|
return jumpAndTrace(j, target);
|
|
}
|
|
|
|
bool
|
|
mjit::Compiler::jsop_ifneq(JSOp op, jsbytecode *target)
|
|
{
|
|
FrameEntry *fe = frame.peek(-1);
|
|
|
|
if (fe->isConstant()) {
|
|
JSBool b = js_ValueToBoolean(fe->getValue());
|
|
|
|
frame.pop();
|
|
|
|
if (op == JSOP_IFEQ)
|
|
b = !b;
|
|
if (b) {
|
|
frame.syncAndForgetEverything();
|
|
if (!jumpAndTrace(masm.jump(), target))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return booleanJumpScript(op, target);
|
|
}
|
|
|
|
bool
|
|
mjit::Compiler::jsop_andor(JSOp op, jsbytecode *target)
|
|
{
|
|
FrameEntry *fe = frame.peek(-1);
|
|
|
|
if (fe->isConstant()) {
|
|
JSBool b = js_ValueToBoolean(fe->getValue());
|
|
|
|
/* Short-circuit. */
|
|
if ((op == JSOP_OR && b == JS_TRUE) ||
|
|
(op == JSOP_AND && b == JS_FALSE)) {
|
|
frame.syncAndForgetEverything();
|
|
if (!jumpAndTrace(masm.jump(), target))
|
|
return false;
|
|
}
|
|
|
|
frame.pop();
|
|
return true;
|
|
}
|
|
|
|
return booleanJumpScript(op, target);
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_localinc(JSOp op, uint32 slot, bool popped)
|
|
{
|
|
if (popped || (op == JSOP_INCLOCAL || op == JSOP_DECLOCAL)) {
|
|
int amt = (op == JSOP_LOCALINC || op == JSOP_INCLOCAL) ? -1 : 1;
|
|
|
|
// Before:
|
|
// After: V
|
|
frame.pushLocal(slot);
|
|
|
|
// Before: V
|
|
// After: V 1
|
|
frame.push(Int32Value(amt));
|
|
|
|
// Note, SUB will perform integer conversion for us.
|
|
// Before: V 1
|
|
// After: N+1
|
|
jsop_binary(JSOP_SUB, stubs::Sub);
|
|
|
|
// Before: N+1
|
|
// After: N+1
|
|
frame.storeLocal(slot, popped);
|
|
|
|
if (popped)
|
|
frame.pop();
|
|
} else {
|
|
int amt = (op == JSOP_LOCALINC || op == JSOP_INCLOCAL) ? 1 : -1;
|
|
|
|
// Before:
|
|
// After: V
|
|
frame.pushLocal(slot);
|
|
|
|
// Before: V
|
|
// After: N
|
|
jsop_pos();
|
|
|
|
// Before: N
|
|
// After: N N
|
|
frame.dup();
|
|
|
|
// Before: N N
|
|
// After: N N 1
|
|
frame.push(Int32Value(amt));
|
|
|
|
// Before: N N 1
|
|
// After: N N+1
|
|
jsop_binary(JSOP_ADD, stubs::Add);
|
|
|
|
// Before: N N+1
|
|
// After: N N+1
|
|
frame.storeLocal(slot, true);
|
|
|
|
// Before: N N+1
|
|
// After: N
|
|
frame.pop();
|
|
}
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_arginc(JSOp op, uint32 slot, bool popped)
|
|
{
|
|
if (popped || (op == JSOP_INCARG || op == JSOP_DECARG)) {
|
|
int amt = (op == JSOP_ARGINC || op == JSOP_INCARG) ? -1 : 1;
|
|
|
|
// Before:
|
|
// After: V
|
|
frame.pushArg(slot);
|
|
|
|
// Before: V
|
|
// After: V 1
|
|
frame.push(Int32Value(amt));
|
|
|
|
// Note, SUB will perform integer conversion for us.
|
|
// Before: V 1
|
|
// After: N+1
|
|
jsop_binary(JSOP_SUB, stubs::Sub);
|
|
|
|
// Before: N+1
|
|
// After: N+1
|
|
frame.storeArg(slot, popped);
|
|
|
|
if (popped)
|
|
frame.pop();
|
|
} else {
|
|
int amt = (op == JSOP_ARGINC || op == JSOP_INCARG) ? 1 : -1;
|
|
|
|
// Before:
|
|
// After: V
|
|
frame.pushArg(slot);
|
|
|
|
// Before: V
|
|
// After: N
|
|
jsop_pos();
|
|
|
|
// Before: N
|
|
// After: N N
|
|
frame.dup();
|
|
|
|
// Before: N N
|
|
// After: N N 1
|
|
frame.push(Int32Value(amt));
|
|
|
|
// Before: N N 1
|
|
// After: N N+1
|
|
jsop_binary(JSOP_ADD, stubs::Add);
|
|
|
|
// Before: N N+1
|
|
// After: N N+1
|
|
frame.storeArg(slot, true);
|
|
|
|
// Before: N N+1
|
|
// After: N
|
|
frame.pop();
|
|
}
|
|
}
|
|
|
|
static inline bool
|
|
IsCacheableSetElem(FrameEntry *obj, FrameEntry *id, FrameEntry *value)
|
|
{
|
|
if (obj->isNotType(JSVAL_TYPE_OBJECT))
|
|
return false;
|
|
if (id->isNotType(JSVAL_TYPE_INT32))
|
|
return false;
|
|
if (id->isConstant() && id->getValue().toInt32() < 0)
|
|
return false;
|
|
|
|
// obj[obj] * is not allowed, since it will never optimize.
|
|
// obj[id] = id is allowed.
|
|
// obj[id] = obj is allowed.
|
|
if (obj->hasSameBacking(id))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
mjit::Compiler::jsop_setelem(bool popGuaranteed)
|
|
{
|
|
FrameEntry *obj = frame.peek(-3);
|
|
FrameEntry *id = frame.peek(-2);
|
|
FrameEntry *value = frame.peek(-1);
|
|
|
|
if (!IsCacheableSetElem(obj, id, value)) {
|
|
jsop_setelem_slow();
|
|
return true;
|
|
}
|
|
|
|
SetElementICInfo ic = SetElementICInfo(JSOp(*PC));
|
|
|
|
// One by one, check if the most important stack entries have registers,
|
|
// and if so, pin them. This is to avoid spilling and reloading from the
|
|
// stack as we incrementally allocate other registers.
|
|
MaybeRegisterID pinnedValueType = frame.maybePinType(value);
|
|
MaybeRegisterID pinnedValueData = frame.maybePinData(value);
|
|
|
|
// Pin |obj| if it doesn't share a backing with |value|.
|
|
MaybeRegisterID pinnedObjData;
|
|
if (!obj->hasSameBacking(value))
|
|
pinnedObjData = frame.maybePinData(obj);
|
|
|
|
// Pin |id| if it doesn't share a backing with |value|.
|
|
MaybeRegisterID pinnedIdData;
|
|
if (!id->hasSameBacking(value))
|
|
pinnedIdData = frame.maybePinData(id);
|
|
|
|
// Note: The fact that |obj| and |value|, or |id| and |value| can be
|
|
// copies, is a little complicated, but it is safe. Explanations
|
|
// follow at each point. Keep in mind two points:
|
|
// 1) maybePin() never allocates a register, it only pins if a register
|
|
// already existed.
|
|
// 2) tempRegForData() will work fine on a pinned register.
|
|
|
|
// Guard that the object is an object.
|
|
if (!obj->isTypeKnown()) {
|
|
Jump j = frame.testObject(Assembler::NotEqual, obj);
|
|
stubcc.linkExit(j, Uses(3));
|
|
}
|
|
|
|
// Guard that the id is int32.
|
|
if (!id->isTypeKnown()) {
|
|
Jump j = frame.testInt32(Assembler::NotEqual, id);
|
|
stubcc.linkExit(j, Uses(3));
|
|
}
|
|
|
|
// Grab a register for the object. It's safe to unpin |obj| because it
|
|
// won't have been pinned if it shares a backing with |value|. However,
|
|
// it would not be safe to copyDataIntoReg() if the value was pinned,
|
|
// since this could evict the register. So we special case.
|
|
frame.maybeUnpinReg(pinnedObjData);
|
|
if (obj->hasSameBacking(value) && pinnedValueData.isSet()) {
|
|
ic.objReg = frame.allocReg();
|
|
masm.move(pinnedValueData.reg(), ic.objReg);
|
|
} else {
|
|
ic.objReg = frame.copyDataIntoReg(obj);
|
|
}
|
|
|
|
// pinEntry() will ensure pinned registers for |value|. To avoid a
|
|
// double-pin assert, first unpin any registers that |value| had.
|
|
frame.maybeUnpinReg(pinnedValueType);
|
|
frame.maybeUnpinReg(pinnedValueData);
|
|
frame.pinEntry(value, ic.vr);
|
|
|
|
// Store rematerialization information about the key. This is the final
|
|
// register we allocate, and thus it can use tempRegForData() without
|
|
// the worry of being spilled. Once again, this is safe even if |id|
|
|
// shares a backing with |value|, because tempRegForData() will work on
|
|
// the pinned register, and |pinnedIdData| will not double-pin.
|
|
frame.maybeUnpinReg(pinnedIdData);
|
|
if (id->isConstant())
|
|
ic.key = Int32Key::FromConstant(id->getValue().toInt32());
|
|
else
|
|
ic.key = Int32Key::FromRegister(frame.tempRegForData(id));
|
|
|
|
// Unpin the value since register allocation is complete.
|
|
frame.unpinEntry(ic.vr);
|
|
|
|
// Now it's also safe to grab remat info for obj (all exits that can
|
|
// generate stubs must have the same register state).
|
|
ic.objRemat = frame.dataRematInfo(obj);
|
|
|
|
// All patchable guards must occur after this point.
|
|
RESERVE_IC_SPACE(masm);
|
|
ic.fastPathStart = masm.label();
|
|
|
|
// Create the common out-of-line sync block, taking care to link previous
|
|
// guards here after.
|
|
RESERVE_OOL_SPACE(stubcc.masm);
|
|
ic.slowPathStart = stubcc.syncExit(Uses(3));
|
|
|
|
// Guard obj is a dense array.
|
|
ic.claspGuard = masm.testObjClass(Assembler::NotEqual, ic.objReg, &js_ArrayClass);
|
|
stubcc.linkExitDirect(ic.claspGuard, ic.slowPathStart);
|
|
|
|
// Guard capacity in range.
|
|
Jump capacityGuard = masm.guardArrayCapacity(ic.objReg, ic.key);
|
|
stubcc.linkExitDirect(capacityGuard, ic.slowPathStart);
|
|
|
|
// Load the dynamic slots vector.
|
|
masm.loadPtr(Address(ic.objReg, offsetof(JSObject, slots)), ic.objReg);
|
|
|
|
// Guard there's no hole, then store directly to the slot.
|
|
if (ic.key.isConstant()) {
|
|
Address slot(ic.objReg, ic.key.index() * sizeof(Value));
|
|
ic.holeGuard = masm.guardNotHole(slot);
|
|
masm.storeValue(ic.vr, slot);
|
|
} else {
|
|
BaseIndex slot(ic.objReg, ic.key.reg(), Assembler::JSVAL_SCALE);
|
|
ic.holeGuard = masm.guardNotHole(slot);
|
|
masm.storeValue(ic.vr, slot);
|
|
}
|
|
stubcc.linkExitDirect(ic.holeGuard, ic.slowPathStart);
|
|
|
|
stubcc.leave();
|
|
#if defined JS_POLYIC
|
|
passICAddress(&ic);
|
|
ic.slowPathCall = OOL_STUBCALL(STRICT_VARIANT(ic::SetElement));
|
|
#else
|
|
OOL_STUBCALL(STRICT_VARIANT(stubs::SetElem));
|
|
#endif
|
|
|
|
ic.fastPathRejoin = masm.label();
|
|
|
|
// When generating typed array stubs, it may be necessary to call
|
|
// js_DoubleToECMAInt32(), which would clobber registers. To deal with
|
|
// this, we tell the IC exactly which registers need to be saved
|
|
// across calls.
|
|
ic.volatileMask = frame.regsInUse();
|
|
|
|
// If the RHS will be popped, and doesn't overlap any live values, then
|
|
// there's no need to save it across calls. Note that this is not true of
|
|
// |obj| or |key|, which will be used to compute the LHS reference for
|
|
// assignment.
|
|
//
|
|
// Note that the IC wants to clobber |vr.dataReg| to convert for typed
|
|
// arrays. If this clobbering is necessary, we must preserve dataReg,
|
|
// even if it's not in a volatile register.
|
|
if (popGuaranteed &&
|
|
!ic.vr.isConstant() &&
|
|
!value->isCopy() &&
|
|
!frame.haveSameBacking(value, obj) &&
|
|
!frame.haveSameBacking(value, id))
|
|
{
|
|
ic.volatileMask &= ~Registers::maskReg(ic.vr.dataReg());
|
|
if (!ic.vr.isTypeKnown())
|
|
ic.volatileMask &= ~Registers::maskReg(ic.vr.typeReg());
|
|
} else if (!ic.vr.isConstant()) {
|
|
ic.volatileMask |= Registers::maskReg(ic.vr.dataReg());
|
|
}
|
|
|
|
frame.freeReg(ic.objReg);
|
|
frame.shimmy(2);
|
|
stubcc.rejoin(Changes(2));
|
|
|
|
#if defined JS_POLYIC
|
|
if (!setElemICs.append(ic))
|
|
return false;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline bool
|
|
IsCacheableGetElem(FrameEntry *obj, FrameEntry *id)
|
|
{
|
|
if (obj->isTypeKnown() && obj->getKnownType() != JSVAL_TYPE_OBJECT)
|
|
return false;
|
|
if (id->isTypeKnown() &&
|
|
!(id->getKnownType() == JSVAL_TYPE_INT32
|
|
#if defined JS_POLYIC
|
|
|| id->getKnownType() == JSVAL_TYPE_STRING
|
|
#endif
|
|
)) {
|
|
return false;
|
|
}
|
|
|
|
if (id->isTypeKnown() && id->getKnownType() == JSVAL_TYPE_INT32 && id->isConstant() &&
|
|
id->getValue().toInt32() < 0) {
|
|
return false;
|
|
}
|
|
|
|
// obj[obj] is not allowed, since it will never optimize.
|
|
if (obj->hasSameBacking(id))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
mjit::Compiler::jsop_getelem(bool isCall)
|
|
{
|
|
FrameEntry *obj = frame.peek(-2);
|
|
FrameEntry *id = frame.peek(-1);
|
|
|
|
if (!IsCacheableGetElem(obj, id)) {
|
|
if (isCall)
|
|
jsop_callelem_slow();
|
|
else
|
|
jsop_getelem_slow();
|
|
return true;
|
|
}
|
|
|
|
GetElementICInfo ic = GetElementICInfo(JSOp(*PC));
|
|
|
|
// Pin the top of the stack to avoid spills, before allocating registers.
|
|
MaybeRegisterID pinnedIdData = frame.maybePinData(id);
|
|
MaybeRegisterID pinnedIdType = frame.maybePinType(id);
|
|
|
|
MaybeJump objTypeGuard;
|
|
if (!obj->isTypeKnown()) {
|
|
// Test the type of the object without spilling the payload.
|
|
MaybeRegisterID pinnedObjData = frame.maybePinData(obj);
|
|
Jump guard = frame.testObject(Assembler::NotEqual, obj);
|
|
frame.maybeUnpinReg(pinnedObjData);
|
|
|
|
// Create a sync path, which we'll rejoin manually later. This is safe
|
|
// as long as the IC does not build a stub; it won't, because |obj|
|
|
// won't be an object. If we extend this IC to support strings, all
|
|
// that needs to change is a little code movement.
|
|
stubcc.linkExit(guard, Uses(2));
|
|
objTypeGuard = stubcc.masm.jump();
|
|
}
|
|
|
|
// Get a mutable register for the object. This will be the data reg.
|
|
ic.objReg = frame.copyDataIntoReg(obj);
|
|
|
|
// For potential dense array calls, grab an extra reg to save the
|
|
// outgoing object.
|
|
MaybeRegisterID thisReg;
|
|
if (isCall && id->mightBeType(JSVAL_TYPE_INT32)) {
|
|
thisReg = frame.allocReg();
|
|
masm.move(ic.objReg, thisReg.reg());
|
|
}
|
|
|
|
// Get a mutable register for pushing the result type. We kill two birds
|
|
// with one stone by making sure, if the key type is not known, to be loaded
|
|
// into this register. In this case it is both an input and an output.
|
|
frame.maybeUnpinReg(pinnedIdType);
|
|
if (id->isConstant() || id->isTypeKnown())
|
|
ic.typeReg = frame.allocReg();
|
|
else
|
|
ic.typeReg = frame.copyTypeIntoReg(id);
|
|
|
|
// Fill in the id value.
|
|
frame.maybeUnpinReg(pinnedIdData);
|
|
if (id->isConstant()) {
|
|
ic.id = ValueRemat::FromConstant(id->getValue());
|
|
} else {
|
|
RegisterID dataReg = frame.tempRegForData(id);
|
|
if (id->isTypeKnown())
|
|
ic.id = ValueRemat::FromKnownType(id->getKnownType(), dataReg);
|
|
else
|
|
ic.id = ValueRemat::FromRegisters(ic.typeReg, dataReg);
|
|
}
|
|
|
|
RESERVE_IC_SPACE(masm);
|
|
ic.fastPathStart = masm.label();
|
|
|
|
// Note: slow path here is safe, since the frame will not be modified.
|
|
RESERVE_OOL_SPACE(stubcc.masm);
|
|
ic.slowPathStart = stubcc.masm.label();
|
|
frame.sync(stubcc.masm, Uses(2));
|
|
|
|
if (id->mightBeType(JSVAL_TYPE_INT32)) {
|
|
// Always test the type first (see comment in PolyIC.h).
|
|
if (!id->isTypeKnown()) {
|
|
ic.typeGuard = masm.testInt32(Assembler::NotEqual, ic.typeReg);
|
|
stubcc.linkExitDirect(ic.typeGuard.get(), ic.slowPathStart);
|
|
}
|
|
|
|
// Guard on the clasp.
|
|
ic.claspGuard = masm.testObjClass(Assembler::NotEqual, ic.objReg, &js_ArrayClass);
|
|
stubcc.linkExitDirect(ic.claspGuard, ic.slowPathStart);
|
|
|
|
Int32Key key = id->isConstant()
|
|
? Int32Key::FromConstant(id->getValue().toInt32())
|
|
: Int32Key::FromRegister(ic.id.dataReg());
|
|
|
|
Assembler::FastArrayLoadFails fails =
|
|
masm.fastArrayLoad(ic.objReg, key, ic.typeReg, ic.objReg);
|
|
|
|
// Store the object back to sp[-1] for calls. This must occur after
|
|
// all guards because otherwise sp[-1] will be clobbered.
|
|
if (isCall) {
|
|
Address thisSlot = frame.addressOf(id);
|
|
masm.storeValueFromComponents(ImmType(JSVAL_TYPE_OBJECT), thisReg.reg(), thisSlot);
|
|
frame.freeReg(thisReg.reg());
|
|
}
|
|
|
|
stubcc.linkExitDirect(fails.rangeCheck, ic.slowPathStart);
|
|
stubcc.linkExitDirect(fails.holeCheck, ic.slowPathStart);
|
|
} else {
|
|
// The type is known to not be dense-friendly ahead of time, so always
|
|
// fall back to a slow path.
|
|
ic.claspGuard = masm.jump();
|
|
stubcc.linkExitDirect(ic.claspGuard, ic.slowPathStart);
|
|
}
|
|
|
|
stubcc.leave();
|
|
if (objTypeGuard.isSet())
|
|
objTypeGuard.get().linkTo(stubcc.masm.label(), &stubcc.masm);
|
|
#ifdef JS_POLYIC
|
|
passICAddress(&ic);
|
|
if (isCall)
|
|
ic.slowPathCall = OOL_STUBCALL(ic::CallElement);
|
|
else
|
|
ic.slowPathCall = OOL_STUBCALL(ic::GetElement);
|
|
#else
|
|
if (isCall)
|
|
ic.slowPathCall = OOL_STUBCALL(stubs::CallElem);
|
|
else
|
|
ic.slowPathCall = OOL_STUBCALL(stubs::GetElem);
|
|
#endif
|
|
|
|
ic.fastPathRejoin = masm.label();
|
|
|
|
frame.popn(2);
|
|
frame.pushRegs(ic.typeReg, ic.objReg);
|
|
if (isCall)
|
|
frame.pushSynced();
|
|
|
|
stubcc.rejoin(Changes(2));
|
|
|
|
#ifdef JS_POLYIC
|
|
if (!getElemICs.append(ic))
|
|
return false;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline bool
|
|
ReallySimpleStrictTest(FrameEntry *fe)
|
|
{
|
|
if (!fe->isTypeKnown())
|
|
return false;
|
|
JSValueType type = fe->getKnownType();
|
|
return type == JSVAL_TYPE_NULL || type == JSVAL_TYPE_UNDEFINED;
|
|
}
|
|
|
|
static inline bool
|
|
BooleanStrictTest(FrameEntry *fe)
|
|
{
|
|
return fe->isConstant() && fe->getKnownType() == JSVAL_TYPE_BOOLEAN;
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_stricteq(JSOp op)
|
|
{
|
|
FrameEntry *rhs = frame.peek(-1);
|
|
FrameEntry *lhs = frame.peek(-2);
|
|
|
|
Assembler::Condition cond = (op == JSOP_STRICTEQ) ? Assembler::Equal : Assembler::NotEqual;
|
|
|
|
/*
|
|
* NB: x64 can do full-Value comparisons. This is beneficial
|
|
* to do if the payload/type are not yet in registers.
|
|
*/
|
|
|
|
/* Constant-fold. */
|
|
if (lhs->isConstant() && rhs->isConstant()) {
|
|
JSBool b;
|
|
StrictlyEqual(cx, lhs->getValue(), rhs->getValue(), &b);
|
|
frame.popn(2);
|
|
frame.push(BooleanValue((op == JSOP_STRICTEQ) ? b : !b));
|
|
return;
|
|
}
|
|
|
|
if (frame.haveSameBacking(lhs, rhs)) {
|
|
/* False iff NaN. */
|
|
if (lhs->isTypeKnown() && lhs->isNotType(JSVAL_TYPE_DOUBLE)) {
|
|
frame.popn(2);
|
|
frame.push(BooleanValue(op == JSOP_STRICTEQ));
|
|
return;
|
|
}
|
|
|
|
/* Assume NaN is in canonical form. */
|
|
RegisterID result = frame.allocReg(Registers::SingleByteRegs);
|
|
RegisterID treg = frame.tempRegForType(lhs);
|
|
|
|
Assembler::Condition oppositeCond = (op == JSOP_STRICTEQ) ? Assembler::NotEqual : Assembler::Equal;
|
|
|
|
#if defined JS_CPU_X86 || defined JS_CPU_ARM
|
|
static const int CanonicalNaNType = 0x7FF80000;
|
|
masm.setPtr(oppositeCond, treg, Imm32(CanonicalNaNType), result);
|
|
#elif defined JS_CPU_X64
|
|
static const void *CanonicalNaNType = (void *)0x7FF8000000000000;
|
|
masm.move(ImmPtr(CanonicalNaNType), JSC::X86Registers::r11);
|
|
masm.setPtr(oppositeCond, treg, JSC::X86Registers::r11, result);
|
|
#endif
|
|
|
|
frame.popn(2);
|
|
frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, result);
|
|
return;
|
|
}
|
|
|
|
/* Comparison against undefined or null is super easy. */
|
|
bool lhsTest;
|
|
if ((lhsTest = ReallySimpleStrictTest(lhs)) || ReallySimpleStrictTest(rhs)) {
|
|
FrameEntry *test = lhsTest ? rhs : lhs;
|
|
FrameEntry *known = lhsTest ? lhs : rhs;
|
|
|
|
if (test->isTypeKnown()) {
|
|
frame.popn(2);
|
|
frame.push(BooleanValue((test->getKnownType() == known->getKnownType()) ==
|
|
(op == JSOP_STRICTEQ)));
|
|
return;
|
|
}
|
|
|
|
/* This is only true if the other side is |null|. */
|
|
RegisterID result = frame.allocReg(Registers::SingleByteRegs);
|
|
#if defined JS_CPU_X86 || defined JS_CPU_ARM
|
|
JSValueTag mask = known->getKnownTag();
|
|
if (frame.shouldAvoidTypeRemat(test))
|
|
masm.set32(cond, masm.tagOf(frame.addressOf(test)), Imm32(mask), result);
|
|
else
|
|
masm.set32(cond, frame.tempRegForType(test), Imm32(mask), result);
|
|
#elif defined JS_CPU_X64
|
|
RegisterID maskReg = frame.allocReg();
|
|
masm.move(ImmTag(known->getKnownTag()), maskReg);
|
|
|
|
RegisterID r = frame.tempRegForType(test);
|
|
masm.setPtr(cond, r, maskReg, result);
|
|
|
|
frame.freeReg(maskReg);
|
|
#endif
|
|
frame.popn(2);
|
|
frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, result);
|
|
return;
|
|
}
|
|
|
|
/* Hardcoded booleans are easy too. */
|
|
if ((lhsTest = BooleanStrictTest(lhs)) || BooleanStrictTest(rhs)) {
|
|
FrameEntry *test = lhsTest ? rhs : lhs;
|
|
|
|
if (test->isTypeKnown() && test->isNotType(JSVAL_TYPE_BOOLEAN)) {
|
|
frame.popn(2);
|
|
frame.push(BooleanValue(op == JSOP_STRICTNE));
|
|
return;
|
|
}
|
|
|
|
if (test->isConstant()) {
|
|
frame.popn(2);
|
|
const Value &L = lhs->getValue();
|
|
const Value &R = rhs->getValue();
|
|
frame.push(BooleanValue((L.toBoolean() == R.toBoolean()) == (op == JSOP_STRICTEQ)));
|
|
return;
|
|
}
|
|
|
|
RegisterID data = frame.copyDataIntoReg(test);
|
|
|
|
RegisterID result = data;
|
|
if (!(Registers::maskReg(data) & Registers::SingleByteRegs))
|
|
result = frame.allocReg(Registers::SingleByteRegs);
|
|
|
|
Jump notBoolean;
|
|
if (!test->isTypeKnown())
|
|
notBoolean = frame.testBoolean(Assembler::NotEqual, test);
|
|
|
|
/* Do a dynamic test. */
|
|
bool val = lhsTest ? lhs->getValue().toBoolean() : rhs->getValue().toBoolean();
|
|
masm.set32(cond, data, Imm32(val), result);
|
|
|
|
if (!test->isTypeKnown()) {
|
|
Jump done = masm.jump();
|
|
notBoolean.linkTo(masm.label(), &masm);
|
|
masm.move(Imm32((op == JSOP_STRICTNE)), result);
|
|
done.linkTo(masm.label(), &masm);
|
|
}
|
|
|
|
if (data != result)
|
|
frame.freeReg(data);
|
|
|
|
frame.popn(2);
|
|
frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, result);
|
|
return;
|
|
}
|
|
|
|
/* Is it impossible that both Values are ints? */
|
|
if ((lhs->isTypeKnown() && lhs->isNotType(JSVAL_TYPE_INT32)) ||
|
|
(rhs->isTypeKnown() && rhs->isNotType(JSVAL_TYPE_INT32))) {
|
|
prepareStubCall(Uses(2));
|
|
|
|
if (op == JSOP_STRICTEQ)
|
|
INLINE_STUBCALL(stubs::StrictEq);
|
|
else
|
|
INLINE_STUBCALL(stubs::StrictNe);
|
|
|
|
frame.popn(2);
|
|
frame.pushSyncedType(JSVAL_TYPE_BOOLEAN);
|
|
return;
|
|
}
|
|
|
|
#ifndef JS_CPU_ARM
|
|
/* Try an integer fast-path. */
|
|
bool needStub = false;
|
|
if (!lhs->isTypeKnown()) {
|
|
Jump j = frame.testInt32(Assembler::NotEqual, lhs);
|
|
stubcc.linkExit(j, Uses(2));
|
|
needStub = true;
|
|
}
|
|
|
|
if (!rhs->isTypeKnown() && !frame.haveSameBacking(lhs, rhs)) {
|
|
Jump j = frame.testInt32(Assembler::NotEqual, rhs);
|
|
stubcc.linkExit(j, Uses(2));
|
|
needStub = true;
|
|
}
|
|
|
|
FrameEntry *test = lhs->isConstant() ? rhs : lhs;
|
|
FrameEntry *other = lhs->isConstant() ? lhs : rhs;
|
|
|
|
/* ReturnReg is safely usable with set32, since %ah can be accessed. */
|
|
RegisterID resultReg = Registers::ReturnReg;
|
|
frame.takeReg(resultReg);
|
|
RegisterID testReg = frame.tempRegForData(test);
|
|
frame.pinReg(testReg);
|
|
|
|
JS_ASSERT(resultReg != testReg);
|
|
|
|
/* Set boolean in resultReg. */
|
|
if (other->isConstant()) {
|
|
masm.set32(cond, testReg, Imm32(other->getValue().toInt32()), resultReg);
|
|
} else if (frame.shouldAvoidDataRemat(other)) {
|
|
masm.set32(cond, testReg, frame.addressOf(other), resultReg);
|
|
} else {
|
|
RegisterID otherReg = frame.tempRegForData(other);
|
|
|
|
JS_ASSERT(otherReg != resultReg);
|
|
JS_ASSERT(otherReg != testReg);
|
|
|
|
masm.set32(cond, testReg, otherReg, resultReg);
|
|
}
|
|
|
|
frame.unpinReg(testReg);
|
|
|
|
if (needStub) {
|
|
stubcc.leave();
|
|
if (op == JSOP_STRICTEQ)
|
|
OOL_STUBCALL(stubs::StrictEq);
|
|
else
|
|
OOL_STUBCALL(stubs::StrictNe);
|
|
}
|
|
|
|
frame.popn(2);
|
|
frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, resultReg);
|
|
|
|
if (needStub)
|
|
stubcc.rejoin(Changes(1));
|
|
#else
|
|
/* TODO: Port set32() logic to ARM. */
|
|
prepareStubCall(Uses(2));
|
|
|
|
if (op == JSOP_STRICTEQ)
|
|
INLINE_STUBCALL(stubs::StrictEq);
|
|
else
|
|
INLINE_STUBCALL(stubs::StrictNe);
|
|
|
|
frame.popn(2);
|
|
frame.pushSyncedType(JSVAL_TYPE_BOOLEAN);
|
|
return;
|
|
#endif
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_pos()
|
|
{
|
|
FrameEntry *top = frame.peek(-1);
|
|
|
|
if (top->isTypeKnown()) {
|
|
if (top->getKnownType() <= JSVAL_TYPE_INT32)
|
|
return;
|
|
prepareStubCall(Uses(1));
|
|
INLINE_STUBCALL(stubs::Pos);
|
|
frame.pop();
|
|
frame.pushSynced();
|
|
return;
|
|
}
|
|
|
|
frame.giveOwnRegs(top);
|
|
|
|
Jump j;
|
|
if (frame.shouldAvoidTypeRemat(top))
|
|
j = masm.testNumber(Assembler::NotEqual, frame.addressOf(top));
|
|
else
|
|
j = masm.testNumber(Assembler::NotEqual, frame.tempRegForType(top));
|
|
stubcc.linkExit(j, Uses(1));
|
|
|
|
stubcc.leave();
|
|
OOL_STUBCALL(stubs::Pos);
|
|
|
|
stubcc.rejoin(Changes(1));
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_initmethod()
|
|
{
|
|
#ifdef DEBUG
|
|
FrameEntry *obj = frame.peek(-2);
|
|
#endif
|
|
JSAtom *atom = script->getAtom(fullAtomIndex(PC));
|
|
|
|
/* Initializers with INITMETHOD are not fast yet. */
|
|
JS_ASSERT(!obj->initializerObject());
|
|
|
|
prepareStubCall(Uses(2));
|
|
masm.move(ImmPtr(atom), Registers::ArgReg1);
|
|
INLINE_STUBCALL(stubs::InitMethod);
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_initprop()
|
|
{
|
|
FrameEntry *obj = frame.peek(-2);
|
|
FrameEntry *fe = frame.peek(-1);
|
|
JSAtom *atom = script->getAtom(fullAtomIndex(PC));
|
|
|
|
JSObject *baseobj = obj->initializerObject();
|
|
|
|
if (!baseobj) {
|
|
prepareStubCall(Uses(2));
|
|
masm.move(ImmPtr(atom), Registers::ArgReg1);
|
|
INLINE_STUBCALL(stubs::InitProp);
|
|
return;
|
|
}
|
|
|
|
JSObject *holder;
|
|
JSProperty *prop = NULL;
|
|
#ifdef DEBUG
|
|
int res =
|
|
#endif
|
|
js_LookupPropertyWithFlags(cx, baseobj, ATOM_TO_JSID(atom),
|
|
JSRESOLVE_QUALIFIED, &holder, &prop);
|
|
JS_ASSERT(res >= 0 && prop && holder == baseobj);
|
|
|
|
RegisterID objReg = frame.copyDataIntoReg(obj);
|
|
masm.loadPtr(Address(objReg, offsetof(JSObject, slots)), objReg);
|
|
|
|
/* Perform the store. */
|
|
Shape *shape = (Shape *) prop;
|
|
frame.storeTo(fe, Address(objReg, shape->slot * sizeof(Value)));
|
|
frame.freeReg(objReg);
|
|
}
|
|
|
|
void
|
|
mjit::Compiler::jsop_initelem()
|
|
{
|
|
FrameEntry *obj = frame.peek(-3);
|
|
FrameEntry *id = frame.peek(-2);
|
|
FrameEntry *fe = frame.peek(-1);
|
|
|
|
/*
|
|
* The initialized index is always a constant, but we won't remember which
|
|
* constant if there are branches inside the code computing the initializer
|
|
* expression (e.g. the expression uses the '?' operator). Slow path those
|
|
* cases, as well as those where INITELEM is used on an object initializer
|
|
* or a non-fast array initializer.
|
|
*/
|
|
if (!id->isConstant() || !obj->initializerArray()) {
|
|
JSOp next = JSOp(PC[JSOP_INITELEM_LENGTH]);
|
|
|
|
prepareStubCall(Uses(3));
|
|
masm.move(Imm32(next == JSOP_ENDINIT ? 1 : 0), Registers::ArgReg1);
|
|
INLINE_STUBCALL(stubs::InitElem);
|
|
return;
|
|
}
|
|
|
|
JS_ASSERT(id->getValue().isInt32());
|
|
|
|
if (fe->isConstant() && fe->getValue().isMagic(JS_ARRAY_HOLE)) {
|
|
/* The array already has the correct length, nothing to do. */
|
|
return;
|
|
}
|
|
|
|
RegisterID objReg = frame.copyDataIntoReg(obj);
|
|
masm.loadPtr(Address(objReg, offsetof(JSObject, slots)), objReg);
|
|
|
|
/* Perform the store. */
|
|
frame.storeTo(fe, Address(objReg, id->getValue().toInt32() * sizeof(Value)));
|
|
frame.freeReg(objReg);
|
|
}
|