Bug 1376799 - Optimize Object.prototype.toString. r=evilpie

--HG--
extra : rebase_source : 01f1dfcdf9bb991c8c40cf8dc9b521501456350f
This commit is contained in:
Jan de Mooij 2017-06-29 11:13:41 -07:00
Родитель 8ebd97f178
Коммит f660d24cb3
4 изменённых файлов: 192 добавлений и 137 удалений

Просмотреть файл

@ -16,6 +16,7 @@
#include "jit/InlinableNatives.h"
#include "js/UniquePtr.h"
#include "vm/AsyncFunction.h"
#include "vm/RegExpObject.h"
#include "vm/StringBuffer.h"
#include "jsobjinlines.h"
@ -432,6 +433,61 @@ js::ObjectToSource(JSContext* cx, HandleObject obj)
}
#endif /* JS_HAS_TOSOURCE */
static bool
GetBuiltinTagSlow(JSContext* cx, HandleObject obj, MutableHandleString builtinTag)
{
// Step 4.
bool isArray;
if (!IsArray(cx, obj, &isArray))
return false;
// Step 5.
if (isArray) {
builtinTag.set(cx->names().objectArray);
return true;
}
// Steps 6-13.
ESClass cls;
if (!GetBuiltinClass(cx, obj, &cls))
return false;
switch (cls) {
case ESClass::String:
builtinTag.set(cx->names().objectString);
return true;
case ESClass::Arguments:
builtinTag.set(cx->names().objectArguments);
return true;
case ESClass::Error:
builtinTag.set(cx->names().objectError);
return true;
case ESClass::Boolean:
builtinTag.set(cx->names().objectBoolean);
return true;
case ESClass::Number:
builtinTag.set(cx->names().objectNumber);
return true;
case ESClass::Date:
builtinTag.set(cx->names().objectDate);
return true;
case ESClass::RegExp:
builtinTag.set(cx->names().objectRegExp);
return true;
default:
if (obj->isCallable()) {
// Non-standard: Prevent <object> from showing up as Function.
RootedObject unwrapped(cx, CheckedUnwrap(obj));
if (!unwrapped || !unwrapped->getClass()->isDOMClass()) {
builtinTag.set(cx->names().objectFunction);
return true;
}
}
builtinTag.set(nullptr);
return true;
}
}
// ES6 19.1.3.6
bool
js::obj_toString(JSContext* cx, unsigned argc, Value* vp)
@ -455,53 +511,63 @@ js::obj_toString(JSContext* cx, unsigned argc, Value* vp)
if (!obj)
return false;
// Step 4.
bool isArray;
if (!IsArray(cx, obj, &isArray))
return false;
// Step 5.
RootedString builtinTag(cx);
if (isArray) {
builtinTag = cx->names().objectArray;
} else {
// Steps 6-13.
ESClass cls;
if (!GetBuiltinClass(cx, obj, &cls))
const Class* clasp = obj->getClass();
if (MOZ_UNLIKELY(clasp->isProxy())) {
if (!GetBuiltinTagSlow(cx, obj, &builtinTag))
return false;
} else {
// Optimize the non-proxy case to bypass GetBuiltinClass.
if (clasp == &PlainObject::class_ || clasp == &UnboxedPlainObject::class_) {
// This is not handled by GetBuiltinTagSlow, but this case is by far
// the most common so we optimize it here.
builtinTag = cx->names().objectObject;
switch (cls) {
case ESClass::String:
} else if (clasp == &ArrayObject::class_ || clasp == &UnboxedArrayObject::class_) {
builtinTag = cx->names().objectArray;
} else if (clasp == &JSFunction::class_) {
builtinTag = cx->names().objectFunction;
} else if (clasp == &StringObject::class_) {
builtinTag = cx->names().objectString;
break;
case ESClass::Arguments:
builtinTag = cx->names().objectArguments;
break;
case ESClass::Error:
builtinTag = cx->names().objectError;
break;
case ESClass::Boolean:
builtinTag = cx->names().objectBoolean;
break;
case ESClass::Number:
} else if (clasp == &NumberObject::class_) {
builtinTag = cx->names().objectNumber;
break;
case ESClass::Date:
} else if (clasp == &BooleanObject::class_) {
builtinTag = cx->names().objectBoolean;
} else if (clasp == &DateObject::class_) {
builtinTag = cx->names().objectDate;
break;
case ESClass::RegExp:
} else if (clasp == &RegExpObject::class_) {
builtinTag = cx->names().objectRegExp;
break;
default:
if (obj->isCallable()) {
// Non-standard: Prevent <object> from showing up as Function.
RootedObject unwrapped(cx, CheckedUnwrap(obj));
if (!unwrapped || !unwrapped->getClass()->isDOMClass())
builtinTag = cx->names().objectFunction;
}
break;
} else if (obj->is<ArgumentsObject>()) {
builtinTag = cx->names().objectArguments;
} else if (obj->is<ErrorObject>()) {
builtinTag = cx->names().objectError;
} else if (obj->isCallable() && !obj->getClass()->isDOMClass()) {
// Non-standard: Prevent <object> from showing up as Function.
builtinTag = cx->names().objectFunction;
}
#ifdef DEBUG
// Assert this fast path is correct and matches BuiltinTagSlow. The
// only exception is the PlainObject case: we special-case it here
// because it's so common, but BuiltinTagSlow doesn't handle this.
RootedString builtinTagSlow(cx);
if (!GetBuiltinTagSlow(cx, obj, &builtinTagSlow))
return false;
if (clasp == &PlainObject::class_ || clasp == &UnboxedPlainObject::class_)
MOZ_ASSERT(!builtinTagSlow);
else
MOZ_ASSERT(builtinTagSlow == builtinTag);
#endif
}
// Step 14.
// Currently omitted for non-standard fallback.
@ -515,22 +581,16 @@ js::obj_toString(JSContext* cx, unsigned argc, Value* vp)
// Non-standard (bug 1277801): Use ClassName as a fallback in the interim
if (!builtinTag) {
const char* className = GetObjectClassName(cx, obj);
// "[object Object]" is by far the most common case at this point,
// so we optimize it here.
if (strcmp(className, "Object") == 0) {
builtinTag = cx->names().objectObject;
} else {
StringBuffer sb(cx);
if (!sb.append("[object ") || !sb.append(className, strlen(className)) ||
!sb.append("]"))
{
return false;
}
builtinTag = sb.finishAtom();
if (!builtinTag)
return false;
StringBuffer sb(cx);
if (!sb.append("[object ") || !sb.append(className, strlen(className)) ||
!sb.append("]"))
{
return false;
}
builtinTag = sb.finishAtom();
if (!builtinTag)
return false;
}
args.rval().setString(builtinTag);

Просмотреть файл

@ -2201,56 +2201,6 @@ JS::IdentifyStandardConstructor(JSObject* obj)
return JSProto_Null;
}
bool
JSObject::isCallable() const
{
if (is<JSFunction>())
return true;
return callHook() != nullptr;
}
bool
JSObject::isConstructor() const
{
if (is<JSFunction>()) {
const JSFunction& fun = as<JSFunction>();
return fun.isConstructor();
}
return constructHook() != nullptr;
}
JSNative
JSObject::callHook() const
{
const js::Class* clasp = getClass();
if (JSNative call = clasp->getCall())
return call;
if (is<js::ProxyObject>()) {
const js::ProxyObject& p = as<js::ProxyObject>();
if (p.handler()->isCallable(const_cast<JSObject*>(this)))
return js::proxy_Call;
}
return nullptr;
}
JSNative
JSObject::constructHook() const
{
const js::Class* clasp = getClass();
if (JSNative construct = clasp->getConstruct())
return construct;
if (is<js::ProxyObject>()) {
const js::ProxyObject& p = as<js::ProxyObject>();
if (p.handler()->isConstructor(const_cast<JSObject*>(this)))
return js::proxy_Construct;
}
return nullptr;
}
bool
js::LookupProperty(JSContext* cx, HandleObject obj, js::HandleId id,
MutableHandleObject objp, MutableHandle<PropertyResult> propp)
@ -3025,18 +2975,6 @@ js::UnwatchProperty(JSContext* cx, HandleObject obj, HandleId id)
return UnwatchGuts(cx, obj, id);
}
const char*
js::GetObjectClassName(JSContext* cx, HandleObject obj)
{
assertSameCompartment(cx, obj);
if (obj->is<ProxyObject>())
return Proxy::className(cx, obj);
return obj->getClass()->name;
}
/* * */
extern bool

Просмотреть файл

@ -516,10 +516,10 @@ class JSObject : public js::gc::Cell
/*
* Back to generic stuff.
*/
bool isCallable() const;
bool isConstructor() const;
JSNative callHook() const;
JSNative constructHook() const;
MOZ_ALWAYS_INLINE bool isCallable() const;
MOZ_ALWAYS_INLINE bool isConstructor() const;
MOZ_ALWAYS_INLINE JSNative callHook() const;
MOZ_ALWAYS_INLINE JSNative constructHook() const;
MOZ_ALWAYS_INLINE void finalize(js::FreeOp* fop);
@ -683,23 +683,6 @@ JSObject::writeBarrierPost(void* cellp, JSObject* prev, JSObject* next)
buffer->unputCell(static_cast<js::gc::Cell**>(cellp));
}
namespace js {
inline bool
IsCallable(const Value& v)
{
return v.isObject() && v.toObject().isCallable();
}
// ES6 rev 24 (2014 April 27) 7.2.5 IsConstructor
inline bool
IsConstructor(const Value& v)
{
return v.isObject() && v.toObject().isConstructor();
}
} /* namespace js */
class JSValueArray {
public:
const js::Value* array;
@ -1087,7 +1070,7 @@ ToPrimitive(JSContext* cx, JSType preferredType, MutableHandleValue vp)
* toString support. (This isn't called GetClassName because there's a macro in
* <windows.h> with that name.)
*/
extern const char*
MOZ_ALWAYS_INLINE const char*
GetObjectClassName(JSContext* cx, HandleObject obj);
/*

Просмотреть файл

@ -823,6 +823,80 @@ InitClass(JSContext* cx, HandleObject obj, HandleObject parent_proto,
NativeObject** ctorp = nullptr,
gc::AllocKind ctorKind = gc::AllocKind::FUNCTION);
MOZ_ALWAYS_INLINE const char*
GetObjectClassName(JSContext* cx, HandleObject obj)
{
assertSameCompartment(cx, obj);
if (obj->is<ProxyObject>())
return Proxy::className(cx, obj);
return obj->getClass()->name;
}
inline bool
IsCallable(const Value& v)
{
return v.isObject() && v.toObject().isCallable();
}
// ES6 rev 24 (2014 April 27) 7.2.5 IsConstructor
inline bool
IsConstructor(const Value& v)
{
return v.isObject() && v.toObject().isConstructor();
}
} /* namespace js */
MOZ_ALWAYS_INLINE bool
JSObject::isCallable() const
{
if (is<JSFunction>())
return true;
return callHook() != nullptr;
}
MOZ_ALWAYS_INLINE bool
JSObject::isConstructor() const
{
if (is<JSFunction>()) {
const JSFunction& fun = as<JSFunction>();
return fun.isConstructor();
}
return constructHook() != nullptr;
}
MOZ_ALWAYS_INLINE JSNative
JSObject::callHook() const
{
const js::Class* clasp = getClass();
if (JSNative call = clasp->getCall())
return call;
if (is<js::ProxyObject>()) {
const js::ProxyObject& p = as<js::ProxyObject>();
if (p.handler()->isCallable(const_cast<JSObject*>(this)))
return js::proxy_Call;
}
return nullptr;
}
MOZ_ALWAYS_INLINE JSNative
JSObject::constructHook() const
{
const js::Class* clasp = getClass();
if (JSNative construct = clasp->getConstruct())
return construct;
if (is<js::ProxyObject>()) {
const js::ProxyObject& p = as<js::ProxyObject>();
if (p.handler()->isConstructor(const_cast<JSObject*>(this)))
return js::proxy_Construct;
}
return nullptr;
}
#endif /* jsobjinlines_h */