Commit fc8c4ef2 authored by Z Nguyen-Huu's avatar Z Nguyen-Huu Committed by Commit Bot

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: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62402}
parent 9f8561be
......@@ -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",
......
......@@ -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)) {
......
......@@ -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) \
......
......@@ -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);
......
......@@ -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
......@@ -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
......@@ -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;
......
......@@ -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;
......
......@@ -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;
......
......@@ -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;
......
......@@ -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');
}
}
......
// 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);
}
}
}
......@@ -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';
......
......@@ -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
......@@ -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.
......
......@@ -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);
......
......@@ -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());
......
......@@ -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) \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment