Commit 7f50476b authored by Maya Lekova's avatar Maya Lekova Committed by Commit Bot

[builtins] Introduce CallProxy builtin based on CSA

- Add more conformance tests for proxy call and calling undetectable
- This improves the performance of calling a proxy by ~5x

Bug: v8:6558, v8:6557
Change-Id: I5fe78d7ca703cfe86a2a14e39f0b6d88bb8c8e03
Reviewed-on: https://chromium-review.googlesource.com/570023Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Commit-Queue: Maya Lekova <mslekova@google.com>
Cr-Commit-Position: refs/heads/master@{#46673}
parent 38769995
......@@ -2442,17 +2442,13 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) {
__ tst(r4, Operand(1 << Map::kIsCallable));
__ b(eq, &non_callable);
// Check if target is a proxy and call CallProxy external builtin
__ cmp(r5, Operand(JS_PROXY_TYPE));
__ b(ne, &non_function);
// 1. Runtime fallback for Proxy [[Call]].
__ Push(r1);
// Increase the arguments size to include the pushed function and the
// existing receiver on the stack.
__ add(r0, r0, Operand(2));
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
__ mov(r5, Operand(ExternalReference(Builtins::kCallProxy, masm->isolate())));
__ ldr(r5, MemOperand(r5));
__ add(pc, r5, Operand(Code::kHeaderSize - kHeapObjectTag));
// 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception).
......
......@@ -2535,17 +2535,14 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) {
__ Ldrb(x4, FieldMemOperand(x4, Map::kBitFieldOffset));
__ TestAndBranchIfAllClear(x4, 1 << Map::kIsCallable, &non_callable);
// Check if target is a proxy and call CallProxy external builtin
__ Cmp(x5, JS_PROXY_TYPE);
__ B(ne, &non_function);
// 1. Runtime fallback for Proxy [[Call]].
__ Push(x1);
// Increase the arguments size to include the pushed function and the
// existing receiver on the stack.
__ Add(x0, x0, Operand(2));
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
__ Mov(x5, ExternalReference(Builtins::kCallProxy, masm->isolate()));
__ Ldr(x5, MemOperand(x5));
__ Add(x6, x5, Code::kHeaderSize - kHeapObjectTag);
__ Br(x6);
// 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception).
......
......@@ -68,6 +68,9 @@ namespace internal {
ASM(Call_ReceiverIsNullOrUndefined) \
ASM(Call_ReceiverIsNotNullOrUndefined) \
ASM(Call_ReceiverIsAny) \
\
/* ES6 section 9.5.12[[Call]] ( thisArgument, argumentsList ) */ \
TFC(CallProxy, CallTrampoline, 1) \
ASM(CallVarargs) \
TFC(CallWithSpread, CallWithSpread, 1) \
TFC(CallWithArrayLike, CallWithArrayLike, 1) \
......
......@@ -51,8 +51,7 @@ class ProxiesCodeStubAssembler : public CodeStubAssembler {
Node* nativeContext = LoadNativeContext(context);
GotoIf(IsCallable(target), &callable_target);
Goto(&none_target);
Branch(IsCallable(target), &callable_target, &none_target);
BIND(&callable_target);
{
......@@ -87,6 +86,32 @@ class ProxiesCodeStubAssembler : public CodeStubAssembler {
return proxy;
}
Node* AllocateJSArrayForCodeStubArguments(Node* context,
CodeStubArguments& args, Node* argc,
ParameterMode mode) {
Node* array = nullptr;
Node* elements = nullptr;
Node* native_context = LoadNativeContext(context);
Node* array_map = LoadJSArrayElementsMap(PACKED_ELEMENTS, native_context);
Node* argc_smi = ParameterToTagged(argc, mode);
std::tie(array, elements) = AllocateUninitializedJSArrayWithElements(
PACKED_ELEMENTS, array_map, argc_smi, nullptr, argc, INTPTR_PARAMETERS);
StoreMapNoWriteBarrier(elements, Heap::kFixedArrayMapRootIndex);
StoreObjectFieldNoWriteBarrier(elements, FixedArrayBase::kLengthOffset,
argc_smi);
VARIABLE(index, MachineType::PointerRepresentation());
index.Bind(IntPtrConstant(FixedArrayBase::kHeaderSize - kHeapObjectTag));
VariableList list({&index}, zone());
args.ForEach(list, [this, elements, &index](Node* arg) {
StoreNoWriteBarrier(MachineRepresentation::kTagged, elements,
index.value(), arg);
Increment(index, kPointerSize);
});
return array;
}
};
// ES6 section 26.2.1.1 Proxy ( target, handler ) for the [[Construct]] case.
......@@ -123,5 +148,68 @@ TF_BUILTIN(ProxyConstructor_ConstructStub, ProxiesCodeStubAssembler) {
ThrowTypeError(context, MessageTemplate::kProxyHandlerOrTargetRevoked);
}
TF_BUILTIN(CallProxy, ProxiesCodeStubAssembler) {
Node* argc = Parameter(Descriptor::kActualArgumentsCount);
Node* argc_ptr = ChangeInt32ToIntPtr(argc);
Node* proxy = Parameter(Descriptor::kFunction);
Node* context = Parameter(Descriptor::kContext);
CSA_ASSERT(this, IsJSProxy(proxy));
CSA_ASSERT(this, IsCallable(proxy));
Label throw_proxy_handler_revoked(this, Label::kDeferred),
trap_undefined(this), trap_defined(this, Label::kDeferred);
// 1. Let handler be the value of the [[ProxyHandler]] internal slot of O.
Node* handler = LoadObjectField(proxy, JSProxy::kHandlerOffset);
// 2. If handler is null, throw a TypeError exception.
CSA_ASSERT(this, Word32Or(IsJSReceiver(handler), IsNull(handler)));
GotoIf(IsNull(handler), &throw_proxy_handler_revoked);
// 3. Assert: Type(handler) is Object.
CSA_ASSERT(this, IsJSReceiver(handler));
// 4. Let target be the value of the [[ProxyTarget]] internal slot of O.
Node* target = LoadObjectField(proxy, JSProxy::kTargetOffset);
// 5. Let trap be ? GetMethod(handler, "apply").
Handle<Name> trap_name = factory()->apply_string();
Node* trap = GetProperty(context, handler, trap_name);
// 6. If trap is undefined, then
GotoIf(IsUndefined(trap), &trap_undefined);
Branch(IsNull(trap), &trap_undefined, &trap_defined);
BIND(&trap_defined);
{
CodeStubArguments args(this, argc_ptr);
Node* receiver = args.GetReceiver();
// 7. Let argArray be CreateArrayFromList(argumentsList).
Node* array = AllocateJSArrayForCodeStubArguments(context, args, argc_ptr,
INTPTR_PARAMETERS);
// 8. Return Call(trap, handler, «target, thisArgument, argArray»).
Node* result = CallJS(CodeFactory::Call(isolate()), context, trap, handler,
target, receiver, array);
args.PopAndReturn(result);
}
BIND(&trap_undefined);
{
// 6.a. Return Call(target, thisArgument, argumentsList).
TailCallStub(CodeFactory::Call(isolate()), context, target, argc);
}
BIND(&throw_proxy_handler_revoked);
{
CallRuntime(Runtime::kThrowTypeError, context,
SmiConstant(MessageTemplate::kProxyRevoked),
StringConstant("apply"));
Unreachable();
}
}
} // namespace internal
} // namespace v8
......@@ -2612,24 +2612,19 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) {
__ j(equal, masm->isolate()->builtins()->CallBoundFunction(),
RelocInfo::CODE_TARGET);
// Check if target has a [[Call]] internal method.
// Check if target is a proxy and call CallProxy external builtin
__ test_b(FieldOperand(ecx, Map::kBitFieldOffset),
Immediate(1 << Map::kIsCallable));
__ j(zero, &non_callable);
// Call CallProxy external builtin
__ CmpInstanceType(ecx, JS_PROXY_TYPE);
__ j(not_equal, &non_function);
// 1. Runtime fallback for Proxy [[Call]].
__ PopReturnAddressTo(ecx);
__ Push(edi);
__ PushReturnAddressFrom(ecx);
// Increase the arguments size to include the pushed function and the
// existing receiver on the stack.
__ add(eax, Immediate(2));
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
__ mov(ecx, Operand::StaticVariable(
ExternalReference(Builtins::kCallProxy, masm->isolate())));
__ lea(ecx, FieldOperand(ecx, Code::kHeaderSize));
__ jmp(ecx);
// 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception).
......
......@@ -2427,16 +2427,11 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) {
__ And(t1, t1, Operand(1 << Map::kIsCallable));
__ Branch(&non_callable, eq, t1, Operand(zero_reg));
// Check if target is a proxy and call CallProxy external builtin
__ Branch(&non_function, ne, t2, Operand(JS_PROXY_TYPE));
// 1. Runtime fallback for Proxy [[Call]].
__ Push(a1);
// Increase the arguments size to include the pushed function and the
// existing receiver on the stack.
__ Addu(a0, a0, 2);
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
__ li(t2, Operand(ExternalReference(Builtins::kCallProxy, masm->isolate())));
__ lw(t2, MemOperand(t2));
__ Jump(t2, Operand(Code::kHeaderSize - kHeapObjectTag));
// 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception).
......
......@@ -2452,15 +2452,10 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) {
__ Branch(&non_callable, eq, t1, Operand(zero_reg));
__ Branch(&non_function, ne, t2, Operand(JS_PROXY_TYPE));
// 1. Runtime fallback for Proxy [[Call]].
__ Push(a1);
// Increase the arguments size to include the pushed function and the
// existing receiver on the stack.
__ Daddu(a0, a0, 2);
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
__ li(t2, Operand(ExternalReference(Builtins::kCallProxy, masm->isolate())));
__ Ld(t2, MemOperand(t2));
__ Daddu(t2, t2, Operand(Code::kHeaderSize - kHeapObjectTag));
__ Jump(t2);
// 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception).
......
......@@ -2633,22 +2633,12 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode,
__ TestBit(r7, Map::kIsCallable, r0);
__ beq(&non_callable, cr0);
// Check if target is a proxy and call CallProxy external builtin
__ cmpi(r8, Operand(JS_PROXY_TYPE));
__ bne(&non_function);
// 0. Prepare for tail call if necessary.
if (tail_call_mode == TailCallMode::kAllow) {
PrepareForTailCall(masm, r3, r6, r7, r8);
}
// 1. Runtime fallback for Proxy [[Call]].
__ Push(r4);
// Increase the arguments size to include the pushed function and the
// existing receiver on the stack.
__ addi(r3, r3, Operand(2));
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
__ mov(r8, Operand(ExternalReference(Builtins::kCallProxy, masm->isolate())));
__ LoadP(r8, MemOperand(r8));
__ addi(r8, r8, Code::kHeaderSize - kHeapObjectTag);
__ JumpToJSEntry(r8);
// 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception).
......
......@@ -2712,19 +2712,13 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) {
Immediate(1 << Map::kIsCallable));
__ j(zero, &non_callable);
// Check if target is a proxy and call CallProxy external builtin
__ CmpInstanceType(rcx, JS_PROXY_TYPE);
__ j(not_equal, &non_function);
// 1. Runtime fallback for Proxy [[Call]].
__ PopReturnAddressTo(kScratchRegister);
__ Push(rdi);
__ PushReturnAddressFrom(kScratchRegister);
// Increase the arguments size to include the pushed function and the
// existing receiver on the stack.
__ addp(rax, Immediate(2));
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
__ Load(rcx, ExternalReference(Builtins::kCallProxy, masm->isolate()));
__ leap(rcx, FieldOperand(rcx, Code::kHeaderSize));
__ jmp(rcx);
// 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception).
......
......@@ -2568,24 +2568,14 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode,
Immediate(1 << Map::kIsCallable));
__ j(zero, &non_callable);
// Check if target is a proxy and call CallProxy external builtin
__ CmpInstanceType(ecx, JS_PROXY_TYPE);
__ j(not_equal, &non_function);
// 0. Prepare for tail call if necessary.
if (tail_call_mode == TailCallMode::kAllow) {
PrepareForTailCall(masm, eax, ebx, ecx, edx);
}
// 1. Runtime fallback for Proxy [[Call]].
__ PopReturnAddressTo(ecx);
__ Push(edi);
__ PushReturnAddressFrom(ecx);
// Increase the arguments size to include the pushed function and the
// existing receiver on the stack.
__ add(eax, Immediate(2));
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
__ mov(ecx, Operand::StaticVariable(
ExternalReference(Builtins::kCallProxy, masm->isolate())));
__ lea(ecx, FieldOperand(ecx, Code::kHeaderSize));
__ jmp(ecx);
// 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception).
......
......@@ -370,9 +370,14 @@ void ExternalReferenceTable::AddBuiltins(Isolate* isolate) {
const char* name;
};
static const BuiltinEntry builtins[] = {
#define BUILTIN_LIST_EXTERNAL_REFS(DEF) \
BUILTIN_LIST_C(DEF) \
BUILTIN_LIST_A(DEF) \
DEF(CallProxy)
#define DEF_ENTRY(Name, ...) {Builtins::k##Name, "Builtin_" #Name},
BUILTIN_LIST_C(DEF_ENTRY) BUILTIN_LIST_A(DEF_ENTRY)
BUILTIN_LIST_EXTERNAL_REFS(DEF_ENTRY)
#undef DEF_ENTRY
#undef BUILTIN_LIST_EXTERNAL_REFS
};
for (unsigned i = 0; i < arraysize(builtins); ++i) {
Add(isolate->builtins()->builtin_address(builtins[i].id), builtins[i].name);
......
......@@ -86,6 +86,133 @@
assertTrue(called_handler);
})();
(function testCallProxyTrapArrayArg() {
var called_target = false;
var called_handler = false;
var target = function(a, b) {
called_target = true;
assertArrayEquals([1, 2], a);
assertEquals(3, b);
}
var handler = {
apply: function(target, this_arg, args) {
target.apply(this_arg, args);
called_handler = true;
}
}
var proxy = new Proxy(target, handler);
assertFalse(called_target);
assertFalse(called_handler);
proxy([1,2], 3);
assertTrue(called_target);
assertTrue(called_handler);
})();
(function testCallProxyTrapObjectArg() {
var called_target = false;
var called_handler = false;
var target = function(o) {
called_target = true;
assertEquals({a: 1, b: 2}, o);
}
var handler = {
apply: function(target, this_arg, args) {
target.apply(this_arg, args);
called_handler = true;
}
}
var proxy = new Proxy(target, handler);
assertFalse(called_target);
assertFalse(called_handler);
proxy({a: 1, b: 2});
assertTrue(called_target);
assertTrue(called_handler);
})();
(function testCallProxyTrapGeneratorArg() {
function* gen() {
yield 1;
yield 2;
yield 3;
}
var called_target = false;
var called_handler = false;
var target = function(g) {
called_target = true;
assertArrayEquals([1,2,3], [...g]);
}
var handler = {
apply: function(target, this_arg, args) {
target.apply(this_arg, args);
called_handler = true;
}
}
var proxy = new Proxy(target, handler);
assertFalse(called_target);
assertFalse(called_handler);
proxy(gen());
assertTrue(called_target);
assertTrue(called_handler);
})();
(function testProxyTrapContext() {
var _target, _args, _handler, _context;
var target = function(a, b) { return a + b; };
var handler = {
apply: function(t, c, args) {
_handler = this;
_target = t;
_context = c;
_args = args;
}
};
var proxy = new Proxy(target, handler);
var context = {};
proxy.call(context, 1, 2);
assertEquals(_handler, handler);
assertEquals(_target, target);
assertEquals(_context, context);
assertEquals(_args.length, 2);
assertEquals(_args[0], 1);
assertEquals(_args[1], 2);
})();
(function testCallProxyNonCallableTrap() {
var called_target = false;
var target = function() {
called_target = true;
};
var handler = {
apply: 'non callable trap'
};
var proxy = new Proxy(target, handler);
assertThrows(function(){ proxy() }, TypeError);
assertFalse(called_target);
})();
(function testCallProxyNullTrap() {
var _args;
var target = function(a, b) {
_args = [a, b];
return a + b;
};
var handler = {
apply: null
};
var proxy = new Proxy(target, handler);
var result = proxy(1, 2);
assertEquals(result, 3);
assertEquals(_args.length, 2);
assertEquals(_args[0], 1);
assertEquals(_args[1], 2);
})();
(function testCallProxyNonCallableTarget() {
var values = [NaN, 1.5, 100, /RegExp/, "string", {}, [], Symbol(),
......
......@@ -72,16 +72,22 @@ function testFors() {
assertEquals(1, j);
}
function testCall() {
obj();
}
for (var j = 0; j < 5; j++) {
testCompares();
testIfs();
testWhiles();
testFors();
testCall();
if (j == 3) {
%OptimizeFunctionOnNextCall(testCompares);
%OptimizeFunctionOnNextCall(testIfs);
%OptimizeFunctionOnNextCall(testWhiles);
%OptimizeFunctionOnNextCall(testFors);
%OptimizeFunctionOnNextCall(testCall);
}
}
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