[torque] Port builtins/builtins-function-gen to Torque
- Ports FastFunctionPrototypeBind and FunctionPrototypeHasInstance to torque. Bug: v8:9891 Change-Id: Iaebaf3c6025907a1b7310c4e08200b4855c7ca6b Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2188929 Commit-Queue: Bill Budge <bbudge@chromium.org> Reviewed-by: Jakob Gruber <jgruber@chromium.org> Reviewed-by: Tobias Tebbi <tebbi@chromium.org> Cr-Commit-Position: refs/heads/master@{#67832}
This commit is contained in:
parent
a3d5ad8384
commit
4436f5e95b
2
BUILD.gn
2
BUILD.gn
@ -1038,6 +1038,7 @@ torque_files = [
|
||||
"src/builtins/finalization-registry.tq",
|
||||
"src/builtins/frames.tq",
|
||||
"src/builtins/frame-arguments.tq",
|
||||
"src/builtins/function.tq",
|
||||
"src/builtins/growable-fixed-array.tq",
|
||||
"src/builtins/ic-callable.tq",
|
||||
"src/builtins/ic.tq",
|
||||
@ -1632,7 +1633,6 @@ v8_source_set("v8_initializers") {
|
||||
"src/builtins/builtins-data-view-gen.h",
|
||||
"src/builtins/builtins-date-gen.cc",
|
||||
"src/builtins/builtins-debug-gen.cc",
|
||||
"src/builtins/builtins-function-gen.cc",
|
||||
"src/builtins/builtins-generator-gen.cc",
|
||||
"src/builtins/builtins-global-gen.cc",
|
||||
"src/builtins/builtins-handler-gen.cc",
|
||||
|
@ -394,6 +394,7 @@ extern macro LengthStringConstant(): String;
|
||||
extern macro MatchSymbolConstant(): Symbol;
|
||||
extern macro MessageStringConstant(): String;
|
||||
extern macro NanConstant(): NaN;
|
||||
extern macro NameStringConstant(): String;
|
||||
extern macro NullConstant(): Null;
|
||||
extern macro ReturnStringConstant(): String;
|
||||
extern macro TheHoleConstant(): TheHole;
|
||||
|
@ -503,11 +503,7 @@ namespace internal {
|
||||
CPP(FunctionConstructor) \
|
||||
ASM(FunctionPrototypeApply, JSTrampoline) \
|
||||
CPP(FunctionPrototypeBind) \
|
||||
/* ES6 #sec-function.prototype.bind */ \
|
||||
TFJ(FastFunctionPrototypeBind, kDontAdaptArgumentsSentinel) \
|
||||
ASM(FunctionPrototypeCall, JSTrampoline) \
|
||||
/* ES6 #sec-function.prototype-@@hasinstance */ \
|
||||
TFJ(FunctionPrototypeHasInstance, 1, kReceiver, kV) \
|
||||
/* ES6 #sec-function.prototype.tostring */ \
|
||||
CPP(FunctionPrototypeToString) \
|
||||
\
|
||||
|
@ -1,202 +0,0 @@
|
||||
// Copyright 2017 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-utils-gen.h"
|
||||
#include "src/builtins/builtins.h"
|
||||
#include "src/codegen/code-stub-assembler.h"
|
||||
#include "src/execution/frame-constants.h"
|
||||
#include "src/objects/api-callbacks.h"
|
||||
#include "src/objects/descriptor-array.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
TF_BUILTIN(FastFunctionPrototypeBind, CodeStubAssembler) {
|
||||
Label slow(this);
|
||||
|
||||
TNode<Int32T> argc =
|
||||
UncheckedCast<Int32T>(Parameter(Descriptor::kJSActualArgumentsCount));
|
||||
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
|
||||
TNode<Object> new_target = CAST(Parameter(Descriptor::kJSNewTarget));
|
||||
|
||||
CodeStubArguments args(this, argc);
|
||||
|
||||
// Check that receiver has instance type of JS_FUNCTION_TYPE
|
||||
TNode<Object> receiver = args.GetReceiver();
|
||||
GotoIf(TaggedIsSmi(receiver), &slow);
|
||||
|
||||
TNode<Map> receiver_map = LoadMap(CAST(receiver));
|
||||
{
|
||||
TNode<Uint16T> instance_type = LoadMapInstanceType(receiver_map);
|
||||
GotoIfNot(
|
||||
Word32Or(InstanceTypeEqual(instance_type, JS_FUNCTION_TYPE),
|
||||
InstanceTypeEqual(instance_type, JS_BOUND_FUNCTION_TYPE)),
|
||||
&slow);
|
||||
}
|
||||
|
||||
// Disallow binding of slow-mode functions. We need to figure out whether the
|
||||
// length and name property are in the original state.
|
||||
Comment("Disallow binding of slow-mode functions");
|
||||
GotoIf(IsDictionaryMap(receiver_map), &slow);
|
||||
|
||||
// Check whether the length and name properties are still present as
|
||||
// AccessorInfo objects. In that case, their value can be recomputed even if
|
||||
// the actual value on the object changes.
|
||||
Comment("Check descriptor array length");
|
||||
// Minimum descriptor array length required for fast path.
|
||||
const int min_nof_descriptors = i::Max(JSFunction::kLengthDescriptorIndex,
|
||||
JSFunction::kNameDescriptorIndex) +
|
||||
1;
|
||||
TNode<Int32T> nof_descriptors = LoadNumberOfOwnDescriptors(receiver_map);
|
||||
GotoIf(Int32LessThan(nof_descriptors, Int32Constant(min_nof_descriptors)),
|
||||
&slow);
|
||||
|
||||
// Check whether the length and name properties are still present as
|
||||
// AccessorInfo objects. In that case, their value can be recomputed even if
|
||||
// the actual value on the object changes.
|
||||
Comment("Check name and length properties");
|
||||
{
|
||||
TNode<DescriptorArray> descriptors = LoadMapDescriptors(receiver_map);
|
||||
const int length_index = JSFunction::kLengthDescriptorIndex;
|
||||
TNode<Name> maybe_length =
|
||||
LoadKeyByDescriptorEntry(descriptors, length_index);
|
||||
GotoIf(TaggedNotEqual(maybe_length, LengthStringConstant()), &slow);
|
||||
|
||||
TNode<Object> maybe_length_accessor =
|
||||
LoadValueByDescriptorEntry(descriptors, length_index);
|
||||
GotoIf(TaggedIsSmi(maybe_length_accessor), &slow);
|
||||
TNode<Map> length_value_map = LoadMap(CAST(maybe_length_accessor));
|
||||
GotoIfNot(IsAccessorInfoMap(length_value_map), &slow);
|
||||
|
||||
const int name_index = JSFunction::kNameDescriptorIndex;
|
||||
TNode<Name> maybe_name = LoadKeyByDescriptorEntry(descriptors, name_index);
|
||||
GotoIf(TaggedNotEqual(maybe_name, NameStringConstant()), &slow);
|
||||
|
||||
TNode<Object> maybe_name_accessor =
|
||||
LoadValueByDescriptorEntry(descriptors, name_index);
|
||||
GotoIf(TaggedIsSmi(maybe_name_accessor), &slow);
|
||||
TNode<Map> name_value_map = LoadMap(CAST(maybe_name_accessor));
|
||||
GotoIfNot(IsAccessorInfoMap(name_value_map), &slow);
|
||||
}
|
||||
|
||||
// Choose the right bound function map based on whether the target is
|
||||
// constructable.
|
||||
Comment("Choose the right bound function map");
|
||||
TVARIABLE(Map, bound_function_map);
|
||||
{
|
||||
Label with_constructor(this);
|
||||
TNode<NativeContext> native_context = LoadNativeContext(context);
|
||||
|
||||
Label map_done(this, &bound_function_map);
|
||||
GotoIf(IsConstructorMap(receiver_map), &with_constructor);
|
||||
|
||||
bound_function_map = CAST(LoadContextElement(
|
||||
native_context, Context::BOUND_FUNCTION_WITHOUT_CONSTRUCTOR_MAP_INDEX));
|
||||
Goto(&map_done);
|
||||
|
||||
BIND(&with_constructor);
|
||||
bound_function_map = CAST(LoadContextElement(
|
||||
native_context, Context::BOUND_FUNCTION_WITH_CONSTRUCTOR_MAP_INDEX));
|
||||
Goto(&map_done);
|
||||
|
||||
BIND(&map_done);
|
||||
}
|
||||
|
||||
// Verify that __proto__ matches that of a the target bound function.
|
||||
Comment("Verify that __proto__ matches target bound function");
|
||||
TNode<HeapObject> prototype = LoadMapPrototype(receiver_map);
|
||||
TNode<HeapObject> expected_prototype =
|
||||
LoadMapPrototype(bound_function_map.value());
|
||||
GotoIf(TaggedNotEqual(prototype, expected_prototype), &slow);
|
||||
|
||||
// Allocate the arguments array.
|
||||
Comment("Allocate the arguments array");
|
||||
TVARIABLE(FixedArray, argument_array);
|
||||
{
|
||||
Label empty_arguments(this);
|
||||
Label arguments_done(this, &argument_array);
|
||||
GotoIf(Uint32LessThanOrEqual(argc, Int32Constant(1)), &empty_arguments);
|
||||
TNode<IntPtrT> elements_length =
|
||||
Signed(ChangeUint32ToWord(Unsigned(Int32Sub(argc, Int32Constant(1)))));
|
||||
argument_array = CAST(AllocateFixedArray(PACKED_ELEMENTS, elements_length,
|
||||
kAllowLargeObjectAllocation));
|
||||
TVARIABLE(IntPtrT, index, IntPtrConstant(0));
|
||||
VariableList foreach_vars({&index}, zone());
|
||||
args.ForEach(
|
||||
foreach_vars,
|
||||
[&](TNode<Object> arg) {
|
||||
StoreFixedArrayElement(argument_array.value(), index.value(), arg);
|
||||
Increment(&index);
|
||||
},
|
||||
IntPtrConstant(1));
|
||||
Goto(&arguments_done);
|
||||
|
||||
BIND(&empty_arguments);
|
||||
argument_array = EmptyFixedArrayConstant();
|
||||
Goto(&arguments_done);
|
||||
|
||||
BIND(&arguments_done);
|
||||
}
|
||||
|
||||
// Determine bound receiver.
|
||||
Comment("Determine bound receiver");
|
||||
TVARIABLE(Object, bound_receiver);
|
||||
{
|
||||
Label has_receiver(this);
|
||||
Label receiver_done(this, &bound_receiver);
|
||||
GotoIf(Word32NotEqual(argc, Int32Constant(0)), &has_receiver);
|
||||
bound_receiver = UndefinedConstant();
|
||||
Goto(&receiver_done);
|
||||
|
||||
BIND(&has_receiver);
|
||||
bound_receiver = args.AtIndex(0);
|
||||
Goto(&receiver_done);
|
||||
|
||||
BIND(&receiver_done);
|
||||
}
|
||||
|
||||
// Allocate the resulting bound function.
|
||||
Comment("Allocate the resulting bound function");
|
||||
{
|
||||
TNode<HeapObject> bound_function = Allocate(JSBoundFunction::kHeaderSize);
|
||||
StoreMapNoWriteBarrier(bound_function, bound_function_map.value());
|
||||
StoreObjectFieldNoWriteBarrier(
|
||||
bound_function, JSBoundFunction::kBoundTargetFunctionOffset, receiver);
|
||||
StoreObjectFieldNoWriteBarrier(bound_function,
|
||||
JSBoundFunction::kBoundThisOffset,
|
||||
bound_receiver.value());
|
||||
StoreObjectFieldNoWriteBarrier(bound_function,
|
||||
JSBoundFunction::kBoundArgumentsOffset,
|
||||
argument_array.value());
|
||||
TNode<FixedArray> empty_fixed_array = EmptyFixedArrayConstant();
|
||||
StoreObjectFieldNoWriteBarrier(
|
||||
bound_function, JSObject::kPropertiesOrHashOffset, empty_fixed_array);
|
||||
StoreObjectFieldNoWriteBarrier(bound_function, JSObject::kElementsOffset,
|
||||
empty_fixed_array);
|
||||
|
||||
args.PopAndReturn(bound_function);
|
||||
}
|
||||
|
||||
BIND(&slow);
|
||||
{
|
||||
// We are not using Parameter(Descriptor::kJSTarget) and loading the value
|
||||
// from the current frame here in order to reduce register pressure on the
|
||||
// fast path.
|
||||
TNode<JSFunction> target = LoadTargetFromFrame();
|
||||
TailCallBuiltin(Builtins::kFunctionPrototypeBind, context, target,
|
||||
new_target, argc);
|
||||
}
|
||||
}
|
||||
|
||||
// ES6 #sec-function.prototype-@@hasinstance
|
||||
TF_BUILTIN(FunctionPrototypeHasInstance, CodeStubAssembler) {
|
||||
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
|
||||
TNode<Object> f = CAST(Parameter(Descriptor::kReceiver));
|
||||
TNode<Object> v = CAST(Parameter(Descriptor::kV));
|
||||
TNode<Oddball> result = OrdinaryHasInstance(context, f, v);
|
||||
Return(result);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
@ -659,6 +659,13 @@ Cast<CoverageInfo>(implicit context: Context)(o: HeapObject): CoverageInfo
|
||||
goto CastError;
|
||||
}
|
||||
|
||||
extern macro IsAccessorInfo(HeapObject): bool;
|
||||
Cast<AccessorInfo>(o: HeapObject): AccessorInfo
|
||||
labels CastError {
|
||||
if (IsAccessorInfo(o)) return %RawDownCast<AccessorInfo>(o);
|
||||
goto CastError;
|
||||
}
|
||||
|
||||
Cast<JSReceiver|Null>(o: HeapObject): JSReceiver|Null
|
||||
labels CastError {
|
||||
typeswitch (o) {
|
||||
@ -752,6 +759,21 @@ Cast<JSBoundFunction>(o: HeapObject): JSBoundFunction labels CastError {
|
||||
goto CastError;
|
||||
}
|
||||
|
||||
Cast<JSFunction|JSBoundFunction>(implicit context: Context)(o: Object):
|
||||
JSFunction|JSBoundFunction labels CastError {
|
||||
typeswitch (o) {
|
||||
case (o: JSFunction): {
|
||||
return o;
|
||||
}
|
||||
case (o: JSBoundFunction): {
|
||||
return o;
|
||||
}
|
||||
case (Object): {
|
||||
goto CastError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cast<PromiseCapability>(o: HeapObject): PromiseCapability labels CastError {
|
||||
if (IsPromiseCapability(o)) return %RawDownCast<PromiseCapability>(o);
|
||||
goto CastError;
|
||||
|
109
src/builtins/function.tq
Normal file
109
src/builtins/function.tq
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright 2020 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.
|
||||
|
||||
namespace function {
|
||||
|
||||
extern macro OrdinaryHasInstance(Context, Object, Object): JSAny;
|
||||
|
||||
// ES6 section 19.2.3.6 Function.prototype[@@hasInstance]
|
||||
javascript builtin FunctionPrototypeHasInstance(
|
||||
js-implicit context: NativeContext, receiver: JSAny)(value: JSAny): JSAny {
|
||||
return OrdinaryHasInstance(context, receiver, value);
|
||||
}
|
||||
|
||||
extern transitioning builtin
|
||||
FunctionPrototypeBind(implicit context: Context)(
|
||||
JSFunction, JSAny, int32): JSAny;
|
||||
|
||||
const kLengthDescriptorIndex:
|
||||
constexpr int32 generates 'JSFunction::kLengthDescriptorIndex';
|
||||
const kNameDescriptorIndex:
|
||||
constexpr int32 generates 'JSFunction::kNameDescriptorIndex';
|
||||
const kMinDescriptorsForFastBind:
|
||||
constexpr int31 generates 'JSFunction::kMinDescriptorsForFastBind';
|
||||
|
||||
macro CheckAccessor(implicit context: Context)(
|
||||
array: DescriptorArray, index: constexpr int32, name: Name) labels Slow {
|
||||
const descriptor: DescriptorEntry = array.descriptors[index];
|
||||
const key: Name|Undefined = descriptor.key;
|
||||
if (!TaggedEqual(key, name)) goto Slow;
|
||||
|
||||
// The descriptor value must be an AccessorInfo.
|
||||
Cast<AccessorInfo>(descriptor.value) otherwise goto Slow;
|
||||
}
|
||||
|
||||
// ES6 section 19.2.3.2 Function.prototype.bind
|
||||
transitioning javascript builtin
|
||||
FastFunctionPrototypeBind(
|
||||
js-implicit context: NativeContext, receiver: JSAny, newTarget: JSAny,
|
||||
target: JSFunction)(...arguments): JSAny {
|
||||
const argc: intptr = arguments.length;
|
||||
try {
|
||||
typeswitch (receiver) {
|
||||
case (fn: JSFunction|JSBoundFunction): {
|
||||
// Disallow binding of slow-mode functions. We need to figure out
|
||||
// whether the length and name property are in the original state.
|
||||
Comment('Disallow binding of slow-mode functions');
|
||||
if (IsDictionaryMap(fn.map)) goto Slow;
|
||||
|
||||
// Check whether the length and name properties are still present as
|
||||
// AccessorInfo objects. If so, their value can be recomputed even if
|
||||
// the actual value on the object changes.
|
||||
|
||||
if (fn.map.bit_field3.number_of_own_descriptors <
|
||||
kMinDescriptorsForFastBind) {
|
||||
goto Slow;
|
||||
}
|
||||
|
||||
const descriptors: DescriptorArray = fn.map.instance_descriptors;
|
||||
CheckAccessor(
|
||||
descriptors, kLengthDescriptorIndex, LengthStringConstant())
|
||||
otherwise Slow;
|
||||
CheckAccessor(descriptors, kNameDescriptorIndex, NameStringConstant())
|
||||
otherwise Slow;
|
||||
|
||||
// Choose the right bound function map based on whether the target is
|
||||
// constructable.
|
||||
|
||||
const boundFunctionMap: Map = UnsafeCast<Map>(
|
||||
IsConstructor(fn) ?
|
||||
context[NativeContextSlot::
|
||||
BOUND_FUNCTION_WITH_CONSTRUCTOR_MAP_INDEX] :
|
||||
context[NativeContextSlot::
|
||||
BOUND_FUNCTION_WITHOUT_CONSTRUCTOR_MAP_INDEX]);
|
||||
|
||||
// Verify that prototype matches that of the target bound function.
|
||||
|
||||
if (fn.map.prototype != boundFunctionMap.prototype) goto Slow;
|
||||
|
||||
// Allocate the arguments array.
|
||||
|
||||
const argumentsArray = arguments.length <= 1 ?
|
||||
kEmptyFixedArray :
|
||||
NewFixedArray(
|
||||
arguments.length - 1, ArgumentsIterator{arguments, current: 1});
|
||||
|
||||
const boundReceiver: JSAny = arguments[0];
|
||||
|
||||
const result = new JSBoundFunction{
|
||||
map: boundFunctionMap,
|
||||
properties_or_hash: kEmptyFixedArray,
|
||||
elements: kEmptyFixedArray,
|
||||
bound_target_function: fn,
|
||||
bound_this: boundReceiver,
|
||||
bound_arguments: argumentsArray
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
case (JSAny): {
|
||||
goto Slow;
|
||||
}
|
||||
}
|
||||
} label Slow {
|
||||
tail FunctionPrototypeBind(
|
||||
LoadTargetFromFrame(), newTarget, Convert<int32>(argc));
|
||||
}
|
||||
}
|
||||
} // namespace function
|
@ -52,6 +52,9 @@ extern enum NativeContextSlot extends intptr constexpr 'Context::Field' {
|
||||
STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX,
|
||||
|
||||
CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX,
|
||||
|
||||
BOUND_FUNCTION_WITH_CONSTRUCTOR_MAP_INDEX,
|
||||
BOUND_FUNCTION_WITHOUT_CONSTRUCTOR_MAP_INDEX,
|
||||
...
|
||||
}
|
||||
|
||||
|
@ -101,6 +101,9 @@ operator '[]=' macro StoreFixedArrayDirect(a: FixedArray, i: Smi, v: Object) {
|
||||
a.objects[i] = v;
|
||||
}
|
||||
|
||||
extern macro AllocateFixedArray(
|
||||
constexpr ElementsKind, intptr, constexpr AllocationFlag): FixedArrayBase;
|
||||
|
||||
extern macro AllocateZeroedFixedArray(intptr): FixedArray;
|
||||
extern macro AllocateZeroedFixedDoubleArray(intptr): FixedDoubleArray;
|
||||
extern macro CalculateNewElementsCapacity(Smi): Smi;
|
||||
|
@ -973,6 +973,8 @@ class JSFunction : public JSFunctionOrBoundFunction {
|
||||
static const int kNameDescriptorIndex = 1;
|
||||
// Home object descriptor index when function has a [[HomeObject]] slot.
|
||||
static const int kMaybeHomeObjectDescriptorIndex = 2;
|
||||
// Fast binding requires length and name accessors.
|
||||
static const int kMinDescriptorsForFastBind = 2;
|
||||
|
||||
// [context]: The context for this function.
|
||||
inline Context context();
|
||||
|
Loading…
Reference in New Issue
Block a user