/* | |
* Copyright (C) 1999-2000 Harri Porten (porten@kde.org) | |
* Copyright (C) 2002, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. | |
* | |
* This library is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU Library General Public | |
* License as published by the Free Software Foundation; either | |
* version 2 of the License, or (at your option) any later version. | |
* | |
* This library is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
* Library General Public License for more details. | |
* | |
* You should have received a copy of the GNU Library General Public License | |
* along with this library; see the file COPYING.LIB. If not, write to | |
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
* Boston, MA 02110-1301, USA. | |
* | |
*/ | |
#ifndef Operations_h | |
#define Operations_h | |
#include "ExceptionHelpers.h" | |
#include "Interpreter.h" | |
#include "JSImmediate.h" | |
#include "JSNumberCell.h" | |
#include "JSString.h" | |
namespace JSC { | |
NEVER_INLINE JSValue jsAddSlowCase(CallFrame*, JSValue, JSValue); | |
JSValue jsTypeStringForValue(CallFrame*, JSValue); | |
bool jsIsObjectType(JSValue); | |
bool jsIsFunctionType(JSValue); | |
ALWAYS_INLINE JSValue jsString(ExecState* exec, JSString* s1, JSString* s2) | |
{ | |
unsigned length1 = s1->length(); | |
if (!length1) | |
return s2; | |
unsigned length2 = s2->length(); | |
if (!length2) | |
return s1; | |
if ((length1 + length2) < length1) | |
return throwOutOfMemoryError(exec); | |
unsigned fiberCount = s1->fiberCount() + s2->fiberCount(); | |
JSGlobalData* globalData = &exec->globalData(); | |
if (fiberCount <= JSString::s_maxInternalRopeLength) | |
return new (globalData) JSString(globalData, fiberCount, s1, s2); | |
JSString::RopeBuilder ropeBuilder(fiberCount); | |
if (UNLIKELY(ropeBuilder.isOutOfMemory())) | |
return throwOutOfMemoryError(exec); | |
ropeBuilder.append(s1); | |
ropeBuilder.append(s2); | |
return new (globalData) JSString(globalData, ropeBuilder.release()); | |
} | |
ALWAYS_INLINE JSValue jsString(ExecState* exec, const UString& u1, JSString* s2) | |
{ | |
unsigned length1 = u1.size(); | |
if (!length1) | |
return s2; | |
unsigned length2 = s2->length(); | |
if (!length2) | |
return jsString(exec, u1); | |
if ((length1 + length2) < length1) | |
return throwOutOfMemoryError(exec); | |
unsigned fiberCount = 1 + s2->fiberCount(); | |
JSGlobalData* globalData = &exec->globalData(); | |
if (fiberCount <= JSString::s_maxInternalRopeLength) | |
return new (globalData) JSString(globalData, fiberCount, u1, s2); | |
JSString::RopeBuilder ropeBuilder(fiberCount); | |
if (UNLIKELY(ropeBuilder.isOutOfMemory())) | |
return throwOutOfMemoryError(exec); | |
ropeBuilder.append(u1); | |
ropeBuilder.append(s2); | |
return new (globalData) JSString(globalData, ropeBuilder.release()); | |
} | |
ALWAYS_INLINE JSValue jsString(ExecState* exec, JSString* s1, const UString& u2) | |
{ | |
unsigned length1 = s1->length(); | |
if (!length1) | |
return jsString(exec, u2); | |
unsigned length2 = u2.size(); | |
if (!length2) | |
return s1; | |
if ((length1 + length2) < length1) | |
return throwOutOfMemoryError(exec); | |
unsigned fiberCount = s1->fiberCount() + 1; | |
JSGlobalData* globalData = &exec->globalData(); | |
if (fiberCount <= JSString::s_maxInternalRopeLength) | |
return new (globalData) JSString(globalData, fiberCount, s1, u2); | |
JSString::RopeBuilder ropeBuilder(fiberCount); | |
if (UNLIKELY(ropeBuilder.isOutOfMemory())) | |
return throwOutOfMemoryError(exec); | |
ropeBuilder.append(s1); | |
ropeBuilder.append(u2); | |
return new (globalData) JSString(globalData, ropeBuilder.release()); | |
} | |
ALWAYS_INLINE JSValue jsString(ExecState* exec, Register* strings, unsigned count) | |
{ | |
ASSERT(count >= 3); | |
unsigned fiberCount = 0; | |
for (unsigned i = 0; i < count; ++i) { | |
JSValue v = strings[i].jsValue(); | |
if (LIKELY(v.isString())) | |
fiberCount += asString(v)->fiberCount(); | |
else | |
++fiberCount; | |
} | |
JSGlobalData* globalData = &exec->globalData(); | |
if (fiberCount == 3) | |
return new (globalData) JSString(exec, strings[0].jsValue(), strings[1].jsValue(), strings[2].jsValue()); | |
JSString::RopeBuilder ropeBuilder(fiberCount); | |
if (UNLIKELY(ropeBuilder.isOutOfMemory())) | |
return throwOutOfMemoryError(exec); | |
unsigned length = 0; | |
bool overflow = false; | |
for (unsigned i = 0; i < count; ++i) { | |
JSValue v = strings[i].jsValue(); | |
if (LIKELY(v.isString())) | |
ropeBuilder.append(asString(v)); | |
else | |
ropeBuilder.append(v.toString(exec)); | |
unsigned newLength = ropeBuilder.length(); | |
if (newLength < length) | |
overflow = true; | |
length = newLength; | |
} | |
if (overflow) | |
return throwOutOfMemoryError(exec); | |
return new (globalData) JSString(globalData, ropeBuilder.release()); | |
} | |
ALWAYS_INLINE JSValue jsString(ExecState* exec, JSValue thisValue, const ArgList& args) | |
{ | |
unsigned fiberCount = 0; | |
if (LIKELY(thisValue.isString())) | |
fiberCount += asString(thisValue)->fiberCount(); | |
else | |
++fiberCount; | |
for (unsigned i = 0; i < args.size(); ++i) { | |
JSValue v = args.at(i); | |
if (LIKELY(v.isString())) | |
fiberCount += asString(v)->fiberCount(); | |
else | |
++fiberCount; | |
} | |
JSString::RopeBuilder ropeBuilder(fiberCount); | |
if (UNLIKELY(ropeBuilder.isOutOfMemory())) | |
return throwOutOfMemoryError(exec); | |
if (LIKELY(thisValue.isString())) | |
ropeBuilder.append(asString(thisValue)); | |
else | |
ropeBuilder.append(thisValue.toString(exec)); | |
unsigned length = 0; | |
bool overflow = false; | |
for (unsigned i = 0; i < args.size(); ++i) { | |
JSValue v = args.at(i); | |
if (LIKELY(v.isString())) | |
ropeBuilder.append(asString(v)); | |
else | |
ropeBuilder.append(v.toString(exec)); | |
unsigned newLength = ropeBuilder.length(); | |
if (newLength < length) | |
overflow = true; | |
length = newLength; | |
} | |
if (overflow) | |
return throwOutOfMemoryError(exec); | |
JSGlobalData* globalData = &exec->globalData(); | |
return new (globalData) JSString(globalData, ropeBuilder.release()); | |
} | |
// ECMA 11.9.3 | |
inline bool JSValue::equal(ExecState* exec, JSValue v1, JSValue v2) | |
{ | |
if (v1.isInt32() && v2.isInt32()) | |
return v1 == v2; | |
return equalSlowCase(exec, v1, v2); | |
} | |
ALWAYS_INLINE bool JSValue::equalSlowCaseInline(ExecState* exec, JSValue v1, JSValue v2) | |
{ | |
do { | |
if (v1.isNumber() && v2.isNumber()) | |
return v1.uncheckedGetNumber() == v2.uncheckedGetNumber(); | |
bool s1 = v1.isString(); | |
bool s2 = v2.isString(); | |
if (s1 && s2) | |
return asString(v1)->value(exec) == asString(v2)->value(exec); | |
if (v1.isUndefinedOrNull()) { | |
if (v2.isUndefinedOrNull()) | |
return true; | |
if (!v2.isCell()) | |
return false; | |
return v2.asCell()->structure()->typeInfo().masqueradesAsUndefined(); | |
} | |
if (v2.isUndefinedOrNull()) { | |
if (!v1.isCell()) | |
return false; | |
return v1.asCell()->structure()->typeInfo().masqueradesAsUndefined(); | |
} | |
if (v1.isObject()) { | |
if (v2.isObject()) | |
return v1 == v2; | |
JSValue p1 = v1.toPrimitive(exec); | |
if (exec->hadException()) | |
return false; | |
v1 = p1; | |
if (v1.isInt32() && v2.isInt32()) | |
return v1 == v2; | |
continue; | |
} | |
if (v2.isObject()) { | |
JSValue p2 = v2.toPrimitive(exec); | |
if (exec->hadException()) | |
return false; | |
v2 = p2; | |
if (v1.isInt32() && v2.isInt32()) | |
return v1 == v2; | |
continue; | |
} | |
if (s1 || s2) { | |
double d1 = v1.toNumber(exec); | |
double d2 = v2.toNumber(exec); | |
return d1 == d2; | |
} | |
if (v1.isBoolean()) { | |
if (v2.isNumber()) | |
return static_cast<double>(v1.getBoolean()) == v2.uncheckedGetNumber(); | |
} else if (v2.isBoolean()) { | |
if (v1.isNumber()) | |
return v1.uncheckedGetNumber() == static_cast<double>(v2.getBoolean()); | |
} | |
return v1 == v2; | |
} while (true); | |
} | |
// ECMA 11.9.3 | |
ALWAYS_INLINE bool JSValue::strictEqualSlowCaseInline(ExecState* exec, JSValue v1, JSValue v2) | |
{ | |
ASSERT(v1.isCell() && v2.isCell()); | |
if (v1.asCell()->isString() && v2.asCell()->isString()) | |
return asString(v1)->value(exec) == asString(v2)->value(exec); | |
return v1 == v2; | |
} | |
inline bool JSValue::strictEqual(ExecState* exec, JSValue v1, JSValue v2) | |
{ | |
if (v1.isInt32() && v2.isInt32()) | |
return v1 == v2; | |
if (v1.isNumber() && v2.isNumber()) | |
return v1.uncheckedGetNumber() == v2.uncheckedGetNumber(); | |
if (!v1.isCell() || !v2.isCell()) | |
return v1 == v2; | |
return strictEqualSlowCaseInline(exec, v1, v2); | |
} | |
ALWAYS_INLINE bool jsLess(CallFrame* callFrame, JSValue v1, JSValue v2) | |
{ | |
if (v1.isInt32() && v2.isInt32()) | |
return v1.asInt32() < v2.asInt32(); | |
double n1; | |
double n2; | |
if (v1.getNumber(n1) && v2.getNumber(n2)) | |
return n1 < n2; | |
JSGlobalData* globalData = &callFrame->globalData(); | |
if (isJSString(globalData, v1) && isJSString(globalData, v2)) | |
return asString(v1)->value(callFrame) < asString(v2)->value(callFrame); | |
JSValue p1; | |
JSValue p2; | |
bool wasNotString1 = v1.getPrimitiveNumber(callFrame, n1, p1); | |
bool wasNotString2 = v2.getPrimitiveNumber(callFrame, n2, p2); | |
if (wasNotString1 | wasNotString2) | |
return n1 < n2; | |
return asString(p1)->value(callFrame) < asString(p2)->value(callFrame); | |
} | |
inline bool jsLessEq(CallFrame* callFrame, JSValue v1, JSValue v2) | |
{ | |
if (v1.isInt32() && v2.isInt32()) | |
return v1.asInt32() <= v2.asInt32(); | |
double n1; | |
double n2; | |
if (v1.getNumber(n1) && v2.getNumber(n2)) | |
return n1 <= n2; | |
JSGlobalData* globalData = &callFrame->globalData(); | |
if (isJSString(globalData, v1) && isJSString(globalData, v2)) | |
return !(asString(v2)->value(callFrame) < asString(v1)->value(callFrame)); | |
JSValue p1; | |
JSValue p2; | |
bool wasNotString1 = v1.getPrimitiveNumber(callFrame, n1, p1); | |
bool wasNotString2 = v2.getPrimitiveNumber(callFrame, n2, p2); | |
if (wasNotString1 | wasNotString2) | |
return n1 <= n2; | |
return !(asString(p2)->value(callFrame) < asString(p1)->value(callFrame)); | |
} | |
// Fast-path choices here are based on frequency data from SunSpider: | |
// <times> Add case: <t1> <t2> | |
// --------------------------- | |
// 5626160 Add case: 3 3 (of these, 3637690 are for immediate values) | |
// 247412 Add case: 5 5 | |
// 20900 Add case: 5 6 | |
// 13962 Add case: 5 3 | |
// 4000 Add case: 3 5 | |
ALWAYS_INLINE JSValue jsAdd(CallFrame* callFrame, JSValue v1, JSValue v2) | |
{ | |
double left = 0.0, right; | |
if (v1.getNumber(left) && v2.getNumber(right)) | |
return jsNumber(callFrame, left + right); | |
if (v1.isString()) { | |
return v2.isString() | |
? jsString(callFrame, asString(v1), asString(v2)) | |
: jsString(callFrame, asString(v1), v2.toPrimitiveString(callFrame)); | |
} | |
// All other cases are pretty uncommon | |
return jsAddSlowCase(callFrame, v1, v2); | |
} | |
inline size_t normalizePrototypeChain(CallFrame* callFrame, JSValue base, JSValue slotBase, const Identifier& propertyName, size_t& slotOffset) | |
{ | |
JSCell* cell = asCell(base); | |
size_t count = 0; | |
while (slotBase != cell) { | |
JSValue v = cell->structure()->prototypeForLookup(callFrame); | |
// If we didn't find slotBase in base's prototype chain, then base | |
// must be a proxy for another object. | |
if (v.isNull()) | |
return 0; | |
cell = asCell(v); | |
// Since we're accessing a prototype in a loop, it's a good bet that it | |
// should not be treated as a dictionary. | |
if (cell->structure()->isDictionary()) { | |
asObject(cell)->flattenDictionaryObject(); | |
if (slotBase == cell) | |
slotOffset = cell->structure()->get(propertyName); | |
} | |
++count; | |
} | |
ASSERT(count); | |
return count; | |
} | |
inline size_t normalizePrototypeChain(CallFrame* callFrame, JSCell* base) | |
{ | |
size_t count = 0; | |
while (1) { | |
JSValue v = base->structure()->prototypeForLookup(callFrame); | |
if (v.isNull()) | |
return count; | |
base = asCell(v); | |
// Since we're accessing a prototype in a loop, it's a good bet that it | |
// should not be treated as a dictionary. | |
if (base->structure()->isDictionary()) | |
asObject(base)->flattenDictionaryObject(); | |
++count; | |
} | |
} | |
ALWAYS_INLINE JSValue resolveBase(CallFrame* callFrame, Identifier& property, ScopeChainNode* scopeChain) | |
{ | |
ScopeChainIterator iter = scopeChain->begin(); | |
ScopeChainIterator next = iter; | |
++next; | |
ScopeChainIterator end = scopeChain->end(); | |
ASSERT(iter != end); | |
PropertySlot slot; | |
JSObject* base; | |
while (true) { | |
base = *iter; | |
if (next == end || base->getPropertySlot(callFrame, property, slot)) | |
return base; | |
iter = next; | |
++next; | |
} | |
ASSERT_NOT_REACHED(); | |
return JSValue(); | |
} | |
} // namespace JSC | |
#endif // Operations_h |