Implement fastpath for proxy trap setPrototypeOf

ObjectSetPrototypeOf and ReflectSetPrototypeOf are now Torque builtins (previously CPP) and the Proxy path is implemented completely in Torque while everything else calls into runtime (and is thus a bit slower than previously).

Perf improvement in micro-benchmark JSTests/Proxies
Before:
SetPrototypeOfWithoutTrap-Proxies(Score): 120
SetPrototypeOfWithTrap-Proxies(Score): 112

After:
SetPrototypeOfWithoutTrap-Proxies(Score): 131
SetPrototypeOfWithTrap-Proxies(Score): 127

Bug: v8:6664
Change-Id: I630096e1964c91d1ec39e19f380a2e9e948de4bb
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1669787
Commit-Queue: Z Nguyen-Huu <duongn@microsoft.com>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: Maya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62402}
This commit is contained in:
Z Nguyen-Huu 2019-06-26 23:12:16 -07:00 committed by Commit Bot
parent 9f8561be3d
commit fc8c4ef27e
18 changed files with 194 additions and 71 deletions

View File

@ -964,6 +964,7 @@ torque_files = [
"src/builtins/proxy-revocable.tq",
"src/builtins/proxy-revoke.tq",
"src/builtins/proxy-set-property.tq",
"src/builtins/proxy-set-prototype-of.tq",
"src/builtins/proxy.tq",
"src/builtins/reflect.tq",
"src/builtins/regexp-replace.tq",

View File

@ -1006,6 +1006,8 @@ const kCalledNonCallable: constexpr MessageTemplate
generates 'MessageTemplate::kCalledNonCallable';
const kCalledOnNullOrUndefined: constexpr MessageTemplate
generates 'MessageTemplate::kCalledOnNullOrUndefined';
const kProtoObjectOrNull: constexpr MessageTemplate
generates 'MessageTemplate::kProtoObjectOrNull';
const kInvalidOffset: constexpr MessageTemplate
generates 'MessageTemplate::kInvalidOffset';
const kInvalidTypedArrayLength: constexpr MessageTemplate
@ -2834,6 +2836,8 @@ macro NumberIsNaN(number: Number): bool {
extern macro GotoIfForceSlowPath() labels Taken;
extern macro BranchIfToBooleanIsTrue(Object): never
labels Taken, NotTaken;
extern macro BranchIfToBooleanIsFalse(Object): never
labels Taken, NotTaken;
macro ToBoolean(obj: Object): bool {
if (BranchIfToBooleanIsTrue(obj)) {

View File

@ -726,7 +726,6 @@ namespace internal {
CPP(ObjectGetOwnPropertyDescriptors) \
TFJ(ObjectGetOwnPropertyNames, 1, kReceiver, kObject) \
CPP(ObjectGetOwnPropertySymbols) \
CPP(ObjectSetPrototypeOf) \
TFJ(ObjectIs, 2, kReceiver, kLeft, kRight) \
CPP(ObjectIsFrozen) \
CPP(ObjectIsSealed) \
@ -826,7 +825,6 @@ namespace internal {
TFJ(ReflectHas, 2, kReceiver, kTarget, kKey) \
CPP(ReflectOwnKeys) \
CPP(ReflectSet) \
CPP(ReflectSetPrototypeOf) \
\
/* RegExp */ \
CPP(RegExpCapture1Getter) \

View File

@ -218,39 +218,6 @@ BUILTIN(ObjectFreeze) {
return *object;
}
// ES6 section 19.1.2.21 Object.setPrototypeOf ( O, proto )
BUILTIN(ObjectSetPrototypeOf) {
HandleScope scope(isolate);
// 1. Let O be ? RequireObjectCoercible(O).
Handle<Object> object = args.atOrUndefined(isolate, 1);
if (object->IsNullOrUndefined(isolate)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kCalledOnNullOrUndefined,
isolate->factory()->NewStringFromAsciiChecked(
"Object.setPrototypeOf")));
}
// 2. If Type(proto) is neither Object nor Null, throw a TypeError exception.
Handle<Object> proto = args.atOrUndefined(isolate, 2);
if (!proto->IsNull(isolate) && !proto->IsJSReceiver()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kProtoObjectOrNull, proto));
}
// 3. If Type(O) is not Object, return O.
if (!object->IsJSReceiver()) return *object;
Handle<JSReceiver> receiver = Handle<JSReceiver>::cast(object);
// 4. Let status be ? O.[[SetPrototypeOf]](proto).
// 5. If status is false, throw a TypeError exception.
MAYBE_RETURN(JSReceiver::SetPrototype(receiver, proto, true, kThrowOnError),
ReadOnlyRoots(isolate).exception());
// 6. Return O.
return *receiver;
}
// ES6 section B.2.2.1.1 get Object.prototype.__proto__
BUILTIN(ObjectPrototypeGetProto) {
HandleScope scope(isolate);

View File

@ -168,30 +168,5 @@ BUILTIN(ReflectSet) {
return *isolate->factory()->ToBoolean(result.FromJust());
}
// ES6 section 26.1.14 Reflect.setPrototypeOf
BUILTIN(ReflectSetPrototypeOf) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
Handle<Object> target = args.at(1);
Handle<Object> proto = args.at(2);
if (!target->IsJSReceiver()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kCalledOnNonObject,
isolate->factory()->NewStringFromAsciiChecked(
"Reflect.setPrototypeOf")));
}
if (!proto->IsJSReceiver() && !proto->IsNull(isolate)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kProtoObjectOrNull, proto));
}
Maybe<bool> result = JSReceiver::SetPrototype(
Handle<JSReceiver>::cast(target), proto, true, kDontThrow);
MAYBE_RETURN(result, ReadOnlyRoots(isolate).exception());
return *isolate->factory()->ToBoolean(result.FromJust());
}
} // namespace internal
} // namespace v8

View File

@ -16,6 +16,14 @@ namespace runtime {
extern transitioning runtime
JSReceiverGetPrototypeOf(implicit context: Context)(JSReceiver): Object;
extern transitioning runtime
JSReceiverSetPrototypeOfThrow(implicit context: Context)(JSReceiver, Object):
Object;
extern transitioning runtime
JSReceiverSetPrototypeOfDontThrow(implicit context:
Context)(JSReceiver, Object): Object;
} // namespace runtime
namespace object {
@ -34,7 +42,7 @@ namespace object {
const objectJSProxy = Cast<JSProxy>(objectJSReceiver)
otherwise return runtime::JSReceiverPreventExtensionsThrow(
objectJSReceiver);
const _status: Object = proxy::ProxyPreventExtensions(objectJSProxy, True);
proxy::ProxyPreventExtensions(objectJSProxy, True);
return objectJSReceiver;
}
@ -61,6 +69,27 @@ namespace object {
otherwise return runtime::JSReceiverGetPrototypeOf(object);
return proxy::ProxyGetPrototypeOf(objectJSProxy);
}
transitioning macro
ObjectSetPrototypeOfThrow(implicit context: Context)(
object: Object, proto: Object): Object {
const objectJSReceiver = Cast<JSReceiver>(object) otherwise return object;
const objectJSProxy = Cast<JSProxy>(objectJSReceiver)
otherwise return runtime::JSReceiverSetPrototypeOfThrow(
objectJSReceiver, proto);
proxy::ProxySetPrototypeOf(objectJSProxy, proto, True);
return objectJSReceiver;
}
transitioning macro
ObjectSetPrototypeOfDontThrow(implicit context: Context)(
object: Object, proto: Object): Object {
const objectJSReceiver = Cast<JSReceiver>(object) otherwise return False;
const objectJSProxy = Cast<JSProxy>(objectJSReceiver)
otherwise return runtime::JSReceiverSetPrototypeOfDontThrow(
objectJSReceiver, proto);
return proxy::ProxySetPrototypeOf(objectJSProxy, proto, False);
}
} // namespace object
namespace object_isextensible {
@ -86,3 +115,25 @@ namespace object_getprototypeof {
return object::ObjectGetPrototypeOf(object);
}
} // namespace object_getprototypeof
namespace object_setprototypeof {
// ES6 section 19.1.2.21 Object.setPrototypeOf ( O, proto )
transitioning javascript builtin ObjectSetPrototypeOf(
js-implicit context:
Context)(_receiver: Object, object: Object, proto: Object): Object {
// 1. Set O to ? RequireObjectCoercible(O).
if (IsNullOrUndefined(object)) {
ThrowTypeError(kCalledOnNullOrUndefined, 'Object.setPrototypeOf');
}
// 2. If Type(proto) is neither Object nor Null, throw a TypeError
// exception.
// 3. If Type(O) is not Object, return O.
// 4. Let status be ? O.[[SetPrototypeOf]](proto).
// 5. If status is false, throw a TypeError exception.
// 6. Return O.
if (proto == Null || Is<JSReceiver>(proto)) {
return object::ObjectSetPrototypeOfThrow(object, proto);
}
ThrowTypeError(kProtoObjectOrNull, proto);
}
} // namespace object_setprototypeof

View File

@ -16,6 +16,7 @@ namespace proxy {
// 1. Let handler be O.[[ProxyHandler]].
// 2. If handler is null, throw a TypeError exception.
// 3. Assert: Type(handler) is Object.
assert(proxy.handler == Null || Is<JSReceiver>(proxy.handler));
const handler =
Cast<JSReceiver>(proxy.handler) otherwise ThrowProxyHandlerRevoked;

View File

@ -22,6 +22,7 @@ namespace proxy {
// 2. Let handler be O.[[ProxyHandler]].
// 3. If handler is null, throw a TypeError exception.
// 4. Assert: Type(handler) is Object.
assert(proxy.handler == Null || Is<JSReceiver>(proxy.handler));
const handler =
Cast<JSReceiver>(proxy.handler) otherwise ThrowProxyHandlerRevoked;

View File

@ -16,6 +16,7 @@ namespace proxy {
// 1. Let handler be O.[[ProxyHandler]].
// 2. If handler is null, throw a TypeError exception.
// 3. Assert: Type(handler) is Object.
assert(proxy.handler == Null || Is<JSReceiver>(proxy.handler));
const handler =
Cast<JSReceiver>(proxy.handler) otherwise ThrowProxyHandlerRevoked;

View File

@ -17,6 +17,7 @@ namespace proxy {
// 1. Let handler be O.[[ProxyHandler]].
// 2. If handler is null, throw a TypeError exception.
// 3. Assert: Type(handler) is Object.
assert(proxy.handler == Null || Is<JSReceiver>(proxy.handler));
const handler =
Cast<JSReceiver>(proxy.handler) otherwise ThrowProxyHandlerRevoked;

View File

@ -30,21 +30,20 @@ namespace proxy {
return Undefined;
}
// 2. Let handler be O.[[ProxyHandler]].
const handler: Object = proxy.handler;
try {
// 2. Let handler be O.[[ProxyHandler]].
// 3. If handler is null, throw a TypeError exception.
// 4. Assert: Type(handler) is Object.
const handlerJSReceiver =
Cast<JSReceiver>(handler) otherwise ThrowProxyHandlerRevoked;
assert(proxy.handler == Null || Is<JSReceiver>(proxy.handler));
const handler =
Cast<JSReceiver>(proxy.handler) otherwise ThrowProxyHandlerRevoked;
// 5. Let target be O.[[ProxyTarget]].
const target = UnsafeCast<JSReceiver>(proxy.target);
// 6. Let trap be ? GetMethod(handler, "set").
// 7. If trap is undefined, then (see 7.a below).
const trap: Callable = GetMethod(handlerJSReceiver, 'set')
const trap: Callable = GetMethod(handler, 'set')
otherwise goto TrapUndefined(target);
// 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler,
@ -61,8 +60,8 @@ namespace proxy {
// i. If targetDesc.[[Set]] is undefined, throw a TypeError
// exception.
// 12. Return true.
const trapResult = Call(
context, trap, handlerJSReceiver, target, name, value, receiverValue);
const trapResult =
Call(context, trap, handler, target, name, value, receiverValue);
if (BranchIfToBooleanIsTrue(trapResult)) {
CheckGetSetTrapResult(target, proxy, name, value, kProxySet);
return value;
@ -77,7 +76,6 @@ namespace proxy {
return value;
}
label ThrowProxyHandlerRevoked deferred {
assert(handler == Null);
ThrowTypeError(kProxyRevoked, 'set');
}
}

View File

@ -0,0 +1,77 @@
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include 'src/builtins/builtins-proxy-gen.h'
namespace proxy {
// ES #sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v
// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v
transitioning builtin
ProxySetPrototypeOf(implicit context: Context)(
proxy: JSProxy, proto: Object, doThrow: Boolean): Object {
PerformStackCheck();
const kTrapName: constexpr string = 'setPrototypeOf';
try {
// 1. Assert: Either Type(V) is Object or Type(V) is Null.
assert(proto == Null || Is<JSReceiver>(proto));
// 2. Let handler be O.[[ProxyHandler]].
// 3. If handler is null, throw a TypeError exception.
// 4. Assert: Type(handler) is Object.
assert(proxy.handler == Null || Is<JSReceiver>(proxy.handler));
const handler =
Cast<JSReceiver>(proxy.handler) otherwise ThrowProxyHandlerRevoked;
// 5. Let target be O.[[ProxyTarget]].
const target = proxy.target;
// 6. Let trap be ? GetMethod(handler, "setPrototypeOf").
// 7. If trap is undefined, then (see 7.a below).
const trap: Callable = GetMethod(handler, kTrapName)
otherwise goto TrapUndefined(target, proto);
// 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target, V
// »)).
const trapResult = Call(context, trap, handler, target, proto);
// 9. If booleanTrapResult is false, return false.
if (BranchIfToBooleanIsFalse(trapResult)) {
if (doThrow == True) {
ThrowTypeError(kProxyTrapReturnedFalsishFor, kTrapName);
}
return False;
}
// 10. Let extensibleTarget be ? IsExtensible(target).
// 11. If extensibleTarget is true, return true.
const extensibleTarget: Object = object::ObjectIsExtensible(target);
assert(extensibleTarget == True || extensibleTarget == False);
if (extensibleTarget == True) {
return True;
}
// 12. Let targetProto be ? target.[[GetPrototypeOf]]().
const targetProto = object::ObjectGetPrototypeOf(target);
// 13. If SameValue(V, targetProto) is false, throw a TypeError
// exception.
// 14. Return true.
if (BranchIfSameValue(proto, targetProto)) {
return True;
}
ThrowTypeError(kProxySetPrototypeOfNonExtensible);
}
label TrapUndefined(target: Object, proto: Object) {
// 7.a. Return ? target.[[SetPrototypeOf]]().
if (doThrow == True) {
return object::ObjectSetPrototypeOfThrow(target, proto);
}
return object::ObjectSetPrototypeOfDontThrow(target, proto);
}
label ThrowProxyHandlerRevoked deferred {
ThrowTypeError(kProxyRevoked, kTrapName);
}
}
}

View File

@ -42,6 +42,8 @@ namespace proxy {
generates 'MessageTemplate::kProxyGetPrototypeOfInvalid';
const kProxyGetPrototypeOfNonExtensible: constexpr MessageTemplate
generates 'MessageTemplate::kProxyGetPrototypeOfNonExtensible';
const kProxySetPrototypeOfNonExtensible: constexpr MessageTemplate
generates 'MessageTemplate::kProxySetPrototypeOfNonExtensible';
const kProxyGet: constexpr int31
generates 'JSProxy::AccessKind::kGet';

View File

@ -30,4 +30,16 @@ namespace reflect {
otherwise ThrowTypeError(kCalledOnNonObject, 'Reflect.getPrototypeOf');
return object::JSReceiverGetPrototypeOf(objectJSReceiver);
}
// ES6 section 26.1.14 Reflect.setPrototypeOf
transitioning javascript builtin ReflectSetPrototypeOf(
js-implicit context:
Context)(_receiver: Object, object: Object, proto: Object): Object {
const objectJSReceiver = Cast<JSReceiver>(object)
otherwise ThrowTypeError(kCalledOnNonObject, 'Reflect.setPrototypeOf');
if (proto == Null || Is<JSReceiver>(proto)) {
return object::ObjectSetPrototypeOfDontThrow(objectJSReceiver, proto);
}
ThrowTypeError(kProtoObjectOrNull, proto);
}
} // namespace reflect

View File

@ -796,6 +796,12 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
// otherwise goes to {if_false}.
void BranchIfToBooleanIsTrue(Node* value, Label* if_true, Label* if_false);
// Branches to {if_false} if ToBoolean applied to {value} yields false,
// otherwise goes to {if_true}.
void BranchIfToBooleanIsFalse(Node* value, Label* if_false, Label* if_true) {
BranchIfToBooleanIsTrue(value, if_true, if_false);
}
void BranchIfJSReceiver(Node* object, Label* if_true, Label* if_false);
// Branches to {if_true} when --force-slow-path flag has been passed.

View File

@ -1449,7 +1449,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Builtins::kObjectGetPrototypeOf, 1, true);
native_context()->set_object_get_prototype_of(*object_get_prototype_of);
SimpleInstallFunction(isolate_, object_function, "setPrototypeOf",
Builtins::kObjectSetPrototypeOf, 2, false);
Builtins::kObjectSetPrototypeOf, 2, true);
SimpleInstallFunction(isolate_, object_function, "isExtensible",
Builtins::kObjectIsExtensible, 1, true);

View File

@ -546,6 +546,32 @@ RUNTIME_FUNCTION(Runtime_JSReceiverGetPrototypeOf) {
JSReceiver::GetPrototype(isolate, receiver));
}
RUNTIME_FUNCTION(Runtime_JSReceiverSetPrototypeOfThrow) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, object, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, proto, 1);
MAYBE_RETURN(JSReceiver::SetPrototype(object, proto, true, kThrowOnError),
ReadOnlyRoots(isolate).exception());
return *object;
}
RUNTIME_FUNCTION(Runtime_JSReceiverSetPrototypeOfDontThrow) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, object, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, proto, 1);
Maybe<bool> result =
JSReceiver::SetPrototype(object, proto, true, kDontThrow);
MAYBE_RETURN(result, ReadOnlyRoots(isolate).exception());
return *isolate->factory()->ToBoolean(result.FromJust());
}
RUNTIME_FUNCTION(Runtime_GetProperty) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());

View File

@ -303,6 +303,8 @@ namespace internal {
F(JSReceiverPreventExtensionsDontThrow, 1, 1) \
F(JSReceiverPreventExtensionsThrow, 1, 1) \
F(JSReceiverGetPrototypeOf, 1, 1) \
F(JSReceiverSetPrototypeOfDontThrow, 2, 1) \
F(JSReceiverSetPrototypeOfThrow, 2, 1) \
F(NewObject, 2, 1) \
F(ObjectCreate, 2, 1) \
F(ObjectEntries, 1, 1) \