Bug 1384562 - Add OrdinaryToPrimitive fast path for stringifying plain objects. r=iain

The Ember subtest in Speedometer hits this a lot for some logging code and this is
likely to also affect similar JS code elsewhere.

This optimization is written so that even if it doesn't apply, there's little overhead
in most cases because we do the `toString` lookup only once.

Differential Revision: https://phabricator.services.mozilla.com/D160748
This commit is contained in:
Jan de Mooij 2022-11-01 13:15:45 +00:00
Родитель 8dd1f60e09
Коммит 1b17a8466f
2 изменённых файлов: 52 добавлений и 8 удалений

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

@ -0,0 +1,25 @@
// Test for OrdinaryToPrimitive called on a plain object with hint "string".
function assertStr(o, s) {
assertEq(String(o), s);
}
function test() {
assertStr({x: 1}, "[object Object]");
assertStr({[Symbol.toStringTag]: "Foo"}, "[object Foo]");
assertStr({toString() { return 123; }}, "123");
assertStr({toString: Math.abs}, "NaN");
assertStr({x: "hello", toString() { return this.x; }}, "hello");
let c = 0;
let fun = () => "hi-" + ++c;
assertStr({toString: fun}, "hi-1");
assertStr({toString: "foo", valueOf: fun}, "hi-2");
assertStr({toString() { return {}; }, valueOf: fun}, "hi-3");
let proto = {};
proto[Symbol.toStringTag] = null;
assertStr(Object.create(proto), "[object Object]");
proto[Symbol.toStringTag] = "Bar";
assertStr(Object.create(proto), "[object Bar]");
}
test();

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

@ -22,6 +22,7 @@
#include "builtin/BigInt.h"
#include "builtin/MapObject.h"
#include "builtin/Object.h"
#include "builtin/String.h"
#include "builtin/Symbol.h"
#include "builtin/WeakSetObject.h"
@ -2372,17 +2373,37 @@ bool JS::OrdinaryToPrimitive(JSContext* cx, HandleObject obj, JSType hint,
if (hint == JSTYPE_STRING) {
id = NameToId(cx->names().toString);
/* Optimize (new String(...)).toString(). */
bool calledToString = false;
if (clasp == &StringObject::class_) {
// Optimize (new String(...)).toString().
StringObject* nobj = &obj->as<StringObject>();
if (HasNativeMethodPure(nobj, cx->names().toString, str_toString, cx)) {
vp.setString(nobj->unbox());
return true;
}
} else if (clasp == &PlainObject::class_) {
JSFunction* fun;
if (GetPropertyPure(cx, obj, id, vp.address()) &&
IsFunctionObject(vp, &fun)) {
// Common case: we have a toString function. Try to short-circuit if
// it's Object.prototype.toString and there's no @@toStringTag.
if (fun->maybeNative() == obj_toString &&
!MaybeHasInterestingSymbolProperty(
cx, obj, cx->wellKnownSymbols().toStringTag)) {
vp.setString(cx->names().objectObject);
return true;
}
if (!js::Call(cx, vp, obj, vp)) {
return false;
}
calledToString = true;
}
}
if (!MaybeCallMethod(cx, obj, id, vp)) {
return false;
if (!calledToString) {
if (!MaybeCallMethod(cx, obj, id, vp)) {
return false;
}
}
if (vp.isPrimitive()) {
return true;
@ -2398,17 +2419,15 @@ bool JS::OrdinaryToPrimitive(JSContext* cx, HandleObject obj, JSType hint,
} else {
id = NameToId(cx->names().valueOf);
/* Optimize new String(...).valueOf(). */
if (clasp == &StringObject::class_) {
// Optimize new String(...).valueOf().
StringObject* nobj = &obj->as<StringObject>();
if (HasNativeMethodPure(nobj, cx->names().valueOf, str_toString, cx)) {
vp.setString(nobj->unbox());
return true;
}
}
/* Optimize new Number(...).valueOf(). */
if (clasp == &NumberObject::class_) {
} else if (clasp == &NumberObject::class_) {
// Optimize new Number(...).valueOf().
NumberObject* nobj = &obj->as<NumberObject>();
if (HasNativeMethodPure(nobj, cx->names().valueOf, num_valueOf, cx)) {
vp.setNumber(nobj->unbox());