Commit 0875778f authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Teach TurboFan about the TypedArray constructor.

This introduces a new JSCreateTypedArray operator, backed by a dedicated
CreateTypedArray builtin, and adds support to lowering new TypedArray
calls to this operator. This way we avoid the overhead of going through
the generic construct stub machinery for hot code. This not only
recovers the performance regression on the typed array constructor
benchmarks, but even improves slightly beyond what we had in 6.6.

We might in the future try to fully inline the TypedArray constructor
into optimized code for certain cases.

Bug: chromium:820726, v8:7503, v8:7518
Change-Id: Ied465924d5695db576d533792f1db68456b9b5ea
Reviewed-on: https://chromium-review.googlesource.com/959010
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarPeter Marshall <petermarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51973}
parent bed02764
......@@ -1095,6 +1095,9 @@ namespace internal {
TFS(TypedArrayInitialize, kHolder, kLength, kElementSize, kInitialize) \
TFS(TypedArrayInitializeWithBuffer, kHolder, kLength, kBuffer, kElementSize, \
kByteOffset) \
/* ES #sec-typedarray-constructors */ \
TFS(CreateTypedArray, kTarget, kNewTarget, kArg1, kArg2, kArg3) \
TFJ(TypedArrayConstructorLazyDeoptContinuation, 1, kResult) \
TFJ(TypedArrayConstructor, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
CPP(TypedArrayPrototypeBuffer) \
/* ES6 #sec-get-%typedarray%.prototype.bytelength */ \
......
......@@ -630,107 +630,126 @@ void TypedArrayBuiltinsAssembler::ConstructByIterable(
element_size);
}
TF_BUILTIN(TypedArrayConstructor, TypedArrayBuiltinsAssembler) {
TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
// ES #sec-typedarray-constructors
TF_BUILTIN(CreateTypedArray, TypedArrayBuiltinsAssembler) {
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
TNode<JSFunction> target = CAST(Parameter(Descriptor::kTarget));
TNode<JSReceiver> new_target = CAST(Parameter(Descriptor::kNewTarget));
TNode<Object> arg1 = CAST(Parameter(Descriptor::kArg1));
TNode<Object> arg2 = CAST(Parameter(Descriptor::kArg2));
TNode<Object> arg3 = CAST(Parameter(Descriptor::kArg3));
// If NewTarget is undefined, throw a TypeError exception.
// All the TypedArray constructors have this as the first step:
// https://tc39.github.io/ecma262/#sec-typedarray-constructors
Node* target = LoadFromFrame(StandardFrameConstants::kFunctionOffset,
MachineType::TaggedPointer());
Node* new_target = Parameter(BuiltinDescriptor::kNewTarget);
Label throwtypeerror(this, Label::kDeferred), createtypedarray(this);
Branch(IsUndefined(new_target), &throwtypeerror, &createtypedarray);
CSA_ASSERT(this, IsConstructor(target));
CSA_ASSERT(this, IsJSReceiver(new_target));
BIND(&throwtypeerror);
Label if_arg1isbuffer(this), if_arg1istypedarray(this),
if_arg1isreceiver(this), if_arg1isnumber(this), return_result(this);
ConstructorBuiltinsAssembler constructor_assembler(this->state());
TNode<JSTypedArray> result = CAST(
constructor_assembler.EmitFastNewObject(context, target, new_target));
TNode<Smi> element_size =
SmiTag(GetTypedArrayElementSize(LoadElementsKind(result)));
GotoIf(TaggedIsSmi(arg1), &if_arg1isnumber);
GotoIf(IsJSArrayBuffer(arg1), &if_arg1isbuffer);
GotoIf(IsJSTypedArray(arg1), &if_arg1istypedarray);
GotoIf(IsJSReceiver(arg1), &if_arg1isreceiver);
Goto(&if_arg1isnumber);
// https://tc39.github.io/ecma262/#sec-typedarray-buffer-byteoffset-length
BIND(&if_arg1isbuffer);
{
Node* shared =
LoadObjectField(target, JSFunction::kSharedFunctionInfoOffset);
Node* name = LoadObjectField(shared, SharedFunctionInfo::kNameOffset);
ThrowTypeError(context, MessageTemplate::kConstructorNotFunction, name);
ConstructByArrayBuffer(context, result, CAST(arg1), arg2, arg3,
element_size);
Goto(&return_result);
}
BIND(&createtypedarray);
// https://tc39.github.io/ecma262/#sec-typedarray-typedarray
BIND(&if_arg1istypedarray);
{
Label if_arg1isbuffer(this), if_arg1istypedarray(this),
if_arg1isreceiver(this), if_arg1isnumber(this), done(this);
Node* argc =
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
CodeStubArguments args(this, argc);
TNode<Object> arg1 = args.GetOptionalArgumentValue(0);
TNode<Object> arg2 = args.GetOptionalArgumentValue(1);
TNode<Object> arg3 = args.GetOptionalArgumentValue(2);
ConstructorBuiltinsAssembler constructor_assembler(this->state());
TNode<JSTypedArray> holder = CAST(
constructor_assembler.EmitFastNewObject(context, target, new_target));
TNode<Smi> element_size =
SmiTag(GetTypedArrayElementSize(LoadElementsKind(holder)));
GotoIf(TaggedIsSmi(arg1), &if_arg1isnumber);
GotoIf(IsJSArrayBuffer(arg1), &if_arg1isbuffer);
GotoIf(IsJSTypedArray(arg1), &if_arg1istypedarray);
GotoIf(IsJSReceiver(arg1), &if_arg1isreceiver);
Goto(&if_arg1isnumber);
// https://tc39.github.io/ecma262/#sec-typedarray-buffer-byteoffset-length
BIND(&if_arg1isbuffer);
{
ConstructByArrayBuffer(context, holder, CAST(arg1), arg2, arg3,
element_size);
Goto(&done);
}
TNode<JSTypedArray> typed_array = CAST(arg1);
ConstructByTypedArray(context, result, typed_array, element_size);
Goto(&return_result);
}
// https://tc39.github.io/ecma262/#sec-typedarray-typedarray
BIND(&if_arg1istypedarray);
// https://tc39.github.io/ecma262/#sec-typedarray-object
BIND(&if_arg1isreceiver);
{
Label if_iteratorundefined(this), if_iteratornotcallable(this);
// Get iterator symbol
TNode<Object> iteratorFn =
CAST(GetMethod(context, arg1, isolate()->factory()->iterator_symbol(),
&if_iteratorundefined));
GotoIf(TaggedIsSmi(iteratorFn), &if_iteratornotcallable);
GotoIfNot(IsCallable(iteratorFn), &if_iteratornotcallable);
ConstructByIterable(context, result, CAST(arg1), iteratorFn, element_size);
Goto(&return_result);
BIND(&if_iteratorundefined);
{
TNode<JSTypedArray> typed_array = CAST(arg1);
ConstructByTypedArray(context, holder, typed_array, element_size);
Goto(&done);
}
TNode<HeapObject> array_like = CAST(arg1);
TNode<Object> initial_length =
GetProperty(context, arg1, LengthStringConstant());
// https://tc39.github.io/ecma262/#sec-typedarray-object
BIND(&if_arg1isreceiver);
{
Label if_iteratorundefined(this), if_iteratornotcallable(this);
// Get iterator symbol
TNode<Object> iteratorFn =
CAST(GetMethod(context, arg1, isolate()->factory()->iterator_symbol(),
&if_iteratorundefined));
GotoIf(TaggedIsSmi(iteratorFn), &if_iteratornotcallable);
GotoIfNot(IsCallable(iteratorFn), &if_iteratornotcallable);
ConstructByIterable(context, holder, CAST(arg1), iteratorFn,
element_size);
Goto(&done);
BIND(&if_iteratorundefined);
{
TNode<HeapObject> array_like = CAST(arg1);
TNode<Object> initial_length =
GetProperty(context, arg1, LengthStringConstant());
ConstructByArrayLike(context, holder, array_like, initial_length,
element_size);
Goto(&done);
}
BIND(&if_iteratornotcallable);
{ ThrowTypeError(context, MessageTemplate::kIteratorSymbolNonCallable); }
ConstructByArrayLike(context, result, array_like, initial_length,
element_size);
Goto(&return_result);
}
// The first argument was a number or fell through and is treated as
// a number. https://tc39.github.io/ecma262/#sec-typedarray-length
BIND(&if_arg1isnumber);
{
ConstructByLength(context, holder, arg1, element_size);
Goto(&done);
}
BIND(&if_iteratornotcallable);
{ ThrowTypeError(context, MessageTemplate::kIteratorSymbolNonCallable); }
}
BIND(&done);
{ args.PopAndReturn(holder); }
// The first argument was a number or fell through and is treated as
// a number. https://tc39.github.io/ecma262/#sec-typedarray-length
BIND(&if_arg1isnumber);
{
ConstructByLength(context, result, arg1, element_size);
Goto(&return_result);
}
BIND(&return_result);
Return(result);
}
TF_BUILTIN(TypedArrayConstructorLazyDeoptContinuation,
TypedArrayBuiltinsAssembler) {
Node* result = Parameter(Descriptor::kResult);
Return(result);
}
// ES #sec-typedarray-constructors
TF_BUILTIN(TypedArrayConstructor, TypedArrayBuiltinsAssembler) {
Node* context = Parameter(BuiltinDescriptor::kContext);
Node* target = LoadFromFrame(StandardFrameConstants::kFunctionOffset,
MachineType::TaggedPointer());
Node* new_target = Parameter(BuiltinDescriptor::kNewTarget);
Node* argc =
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
CodeStubArguments args(this, argc);
Node* arg1 = args.GetOptionalArgumentValue(0);
Node* arg2 = args.GetOptionalArgumentValue(1);
Node* arg3 = args.GetOptionalArgumentValue(2);
// If NewTarget is undefined, throw a TypeError exception.
// All the TypedArray constructors have this as the first step:
// https://tc39.github.io/ecma262/#sec-typedarray-constructors
Label throwtypeerror(this, Label::kDeferred);
GotoIf(IsUndefined(new_target), &throwtypeerror);
Node* result = CallBuiltin(Builtins::kCreateTypedArray, context, target,
new_target, arg1, arg2, arg3);
args.PopAndReturn(result);
BIND(&throwtypeerror);
{
Node* shared =
LoadObjectField(target, JSFunction::kSharedFunctionInfoOffset);
Node* name = LoadObjectField(shared, SharedFunctionInfo::kNameOffset);
ThrowTypeError(context, MessageTemplate::kConstructorNotFunction, name);
}
}
......
......@@ -3611,44 +3611,48 @@ Reduction JSCallReducer::ReduceJSConstruct(Node* node) {
// Don't inline cross native context.
if (function->native_context() != *native_context()) return NoChange();
// Check for the ArrayConstructor.
if (*function == function->native_context()->array_function()) {
// TODO(bmeurer): Deal with Array subclasses here.
Handle<AllocationSite> site;
// Turn the {node} into a {JSCreateArray} call.
for (int i = arity; i > 0; --i) {
NodeProperties::ReplaceValueInput(
node, NodeProperties::GetValueInput(node, i), i + 1);
}
NodeProperties::ReplaceValueInput(node, new_target, 1);
NodeProperties::ChangeOp(node, javascript()->CreateArray(arity, site));
return Changed(node);
}
// Check for the ObjectConstructor.
if (*function == function->native_context()->object_function()) {
// If no value is passed, we can immediately lower to a simple
// JSCreate and don't need to do any massaging of the {node}.
if (arity == 0) {
NodeProperties::ChangeOp(node, javascript()->Create());
// Check for known builtin functions.
switch (function->shared()->code()->builtin_index()) {
case Builtins::kArrayConstructor: {
// TODO(bmeurer): Deal with Array subclasses here.
Handle<AllocationSite> site;
// Turn the {node} into a {JSCreateArray} call.
for (int i = arity; i > 0; --i) {
NodeProperties::ReplaceValueInput(
node, NodeProperties::GetValueInput(node, i), i + 1);
}
NodeProperties::ReplaceValueInput(node, new_target, 1);
NodeProperties::ChangeOp(node,
javascript()->CreateArray(arity, site));
return Changed(node);
}
// Otherwise we can only lower to JSCreate if we know that
// the value parameter is ignored, which is only the case if
// the {new_target} and {target} are definitely not identical.
HeapObjectMatcher mnew_target(new_target);
if (mnew_target.HasValue() && *mnew_target.Value() != *function) {
// Drop the value inputs.
for (int i = arity; i > 0; --i) node->RemoveInput(i);
NodeProperties::ChangeOp(node, javascript()->Create());
return Changed(node);
case Builtins::kObjectConstructor: {
// If no value is passed, we can immediately lower to a simple
// JSCreate and don't need to do any massaging of the {node}.
if (arity == 0) {
NodeProperties::ChangeOp(node, javascript()->Create());
return Changed(node);
}
// Otherwise we can only lower to JSCreate if we know that
// the value parameter is ignored, which is only the case if
// the {new_target} and {target} are definitely not identical.
HeapObjectMatcher mnew_target(new_target);
if (mnew_target.HasValue() && *mnew_target.Value() != *function) {
// Drop the value inputs.
for (int i = arity; i > 0; --i) node->RemoveInput(i);
NodeProperties::ChangeOp(node, javascript()->Create());
return Changed(node);
}
break;
}
}
// Check for the PromiseConstructor
if (*function == function->native_context()->promise_function()) {
return ReducePromiseConstructor(node);
case Builtins::kPromiseConstructor:
return ReducePromiseConstructor(node);
case Builtins::kTypedArrayConstructor:
return ReduceTypedArrayConstructor(
node, handle(function->shared(), isolate()));
default:
break;
}
} else if (m.Value()->IsJSBoundFunction()) {
Handle<JSBoundFunction> function =
......@@ -5832,6 +5836,47 @@ Reduction JSCallReducer::ReducePromiseResolveTrampoline(Node* node) {
return Changed(node);
}
// ES #sec-typedarray-constructors
Reduction JSCallReducer::ReduceTypedArrayConstructor(
Node* node, Handle<SharedFunctionInfo> shared) {
DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode());
ConstructParameters const& p = ConstructParametersOf(node->op());
int arity = static_cast<int>(p.arity() - 2);
Node* target = NodeProperties::GetValueInput(node, 0);
Node* arg1 = (arity >= 1) ? NodeProperties::GetValueInput(node, 1)
: jsgraph()->UndefinedConstant();
Node* arg2 = (arity >= 2) ? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* arg3 = (arity >= 3) ? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
Node* new_target = NodeProperties::GetValueInput(node, arity + 1);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Insert a construct stub frame into the chain of frame states. This will
// reconstruct the proper frame when deoptimizing within the constructor.
frame_state = CreateArtificialFrameState(
node, frame_state, arity, BailoutId::ConstructStubInvoke(),
FrameStateType::kConstructStub, shared);
// This continuation just returns the newly created JSTypedArray. We
// pass the_hole as the receiver, just like the builtin construct stub
// does in this case.
Node* const parameters[] = {jsgraph()->TheHoleConstant()};
int const num_parameters = static_cast<int>(arraysize(parameters));
frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, Builtins::kTypedArrayConstructorLazyDeoptContinuation,
target, context, parameters, num_parameters, frame_state,
ContinuationFrameStateMode::LAZY);
Node* result =
graph()->NewNode(javascript()->CreateTypedArray(), target, new_target,
arg1, arg2, arg3, context, frame_state, effect, control);
return Replace(result);
}
Graph* JSCallReducer::graph() const { return jsgraph()->graph(); }
Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); }
......
......@@ -92,12 +92,9 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
Reduction ReduceArrayPrototypeShift(Node* node);
enum class ArrayIteratorKind { kArray, kTypedArray };
Reduction ReduceArrayIterator(Node* node, IterationKind kind);
Reduction ReduceTypedArrayIterator(Node* node, IterationKind kind);
Reduction ReduceArrayIteratorPrototypeNext(Node* node);
Reduction ReduceFastArrayIteratorNext(InstanceType type, Node* node,
IterationKind kind);
Reduction ReduceTypedArrayIteratorNext(InstanceType type, Node* node,
IterationKind kind);
Reduction ReduceCallOrConstructWithArrayLikeOrSpread(
Node* node, int arity, CallFrequency const& frequency,
......@@ -142,6 +139,9 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
Reduction ReducePromisePrototypeThen(Node* node);
Reduction ReducePromiseResolveTrampoline(Node* node);
Reduction ReduceTypedArrayConstructor(Node* node,
Handle<SharedFunctionInfo> shared);
Reduction ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason);
Reduction ReduceMathUnary(Node* node, const Operator* op);
......
......@@ -446,6 +446,13 @@ void JSGenericLowering::LowerJSCreatePromise(Node* node) {
UNREACHABLE(); // Eliminated in typed lowering.
}
void JSGenericLowering::LowerJSCreateTypedArray(Node* node) {
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
Callable callable =
Builtins::CallableFor(isolate(), Builtins::kCreateTypedArray);
ReplaceWithStubCall(node, callable, flags);
}
void JSGenericLowering::LowerJSCreateLiteralArray(Node* node) {
CreateLiteralParameters const& p = CreateLiteralParametersOf(node->op());
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
......
......@@ -598,6 +598,7 @@ CompareOperationHint CompareOperationHintOf(const Operator* op) {
V(CreateStringIterator, Operator::kEliminatable, 1, 1) \
V(CreateKeyValueArray, Operator::kEliminatable, 2, 1) \
V(CreatePromise, Operator::kEliminatable, 0, 1) \
V(CreateTypedArray, Operator::kNoProperties, 5, 1) \
V(HasProperty, Operator::kNoProperties, 2, 1) \
V(HasInPrototypeChain, Operator::kNoProperties, 2, 1) \
V(OrdinaryHasInstance, Operator::kNoProperties, 2, 1) \
......
......@@ -695,6 +695,7 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
const Operator* CreateStringIterator();
const Operator* CreateKeyValueArray();
const Operator* CreatePromise();
const Operator* CreateTypedArray();
const Operator* CreateLiteralArray(Handle<ConstantElementsPair> constant,
VectorSlotPair const& feedback,
int literal_flags, int number_of_elements);
......
......@@ -144,6 +144,7 @@
V(JSCreateStringIterator) \
V(JSCreateKeyValueArray) \
V(JSCreatePromise) \
V(JSCreateTypedArray) \
V(JSCreateLiteralArray) \
V(JSCreateEmptyLiteralArray) \
V(JSCreateLiteralObject) \
......
......@@ -71,6 +71,7 @@ bool OperatorProperties::HasFrameStateInput(const Operator* op) {
case IrOpcode::kJSCreate:
case IrOpcode::kJSCreateArguments:
case IrOpcode::kJSCreateArray:
case IrOpcode::kJSCreateTypedArray:
case IrOpcode::kJSCreateLiteralArray:
case IrOpcode::kJSCreateLiteralObject:
case IrOpcode::kJSCreateLiteralRegExp:
......
......@@ -1241,6 +1241,10 @@ Type* Typer::Visitor::TypeJSCreatePromise(Node* node) {
return Type::OtherObject();
}
Type* Typer::Visitor::TypeJSCreateTypedArray(Node* node) {
return Type::OtherObject();
}
Type* Typer::Visitor::TypeJSCreateLiteralArray(Node* node) {
return Type::Array();
}
......
......@@ -693,6 +693,10 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
// Type is OtherObject.
CheckTypeIs(node, Type::OtherObject());
break;
case IrOpcode::kJSCreateTypedArray:
// Type is OtherObject.
CheckTypeIs(node, Type::OtherObject());
break;
case IrOpcode::kJSCreateLiteralArray:
// Type is Array.
CheckTypeIs(node, Type::Array());
......
// Copyright 2018 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.
// Flags: --allow-natives-syntax --harmony-bigint
const limit = %MaxSmi() + 1;
(function() {
function foo() {
try { new Int8Array(limit); } catch (e) { return e.stack; }
}
assertTrue(/new Int8Array/.test(foo()));
assertTrue(/new Int8Array/.test(foo()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(/new Int8Array/.test(foo()));
})();
(function() {
function foo() {
try { new Uint8Array(limit); } catch (e) { return e.stack; }
}
assertTrue(/new Uint8Array/.test(foo()));
assertTrue(/new Uint8Array/.test(foo()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(/new Uint8Array/.test(foo()));
})();
(function() {
function foo() {
try { new Uint8ClampedArray(limit); } catch (e) { return e.stack; }
}
assertTrue(/new Uint8ClampedArray/.test(foo()));
assertTrue(/new Uint8ClampedArray/.test(foo()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(/new Uint8ClampedArray/.test(foo()));
})();
(function() {
function foo() {
try { new Int16Array(limit); } catch (e) { return e.stack; }
}
assertTrue(/new Int16Array/.test(foo()));
assertTrue(/new Int16Array/.test(foo()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(/new Int16Array/.test(foo()));
})();
(function() {
function foo() {
try { new Uint16Array(limit); } catch (e) { return e.stack; }
}
assertTrue(/new Uint16Array/.test(foo()));
assertTrue(/new Uint16Array/.test(foo()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(/new Uint16Array/.test(foo()));
})();
(function() {
function foo() {
try { new Int32Array(limit); } catch (e) { return e.stack; }
}
assertTrue(/new Int32Array/.test(foo()));
assertTrue(/new Int32Array/.test(foo()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(/new Int32Array/.test(foo()));
})();
(function() {
function foo() {
try { new Uint32Array(limit); } catch (e) { return e.stack; }
}
assertTrue(/new Uint32Array/.test(foo()));
assertTrue(/new Uint32Array/.test(foo()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(/new Uint32Array/.test(foo()));
})();
(function() {
function foo() {
try { new Float32Array(limit); } catch (e) { return e.stack; }
}
assertTrue(/new Float32Array/.test(foo()));
assertTrue(/new Float32Array/.test(foo()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(/new Float32Array/.test(foo()));
})();
(function() {
function foo() {
try { new Float64Array(limit); } catch (e) { return e.stack; }
}
assertTrue(/new Float64Array/.test(foo()));
assertTrue(/new Float64Array/.test(foo()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(/new Float64Array/.test(foo()));
})();
(function() {
function foo() {
try { new BigInt64Array(limit); } catch (e) { return e.stack; }
}
assertTrue(/new BigInt64Array/.test(foo()));
assertTrue(/new BigInt64Array/.test(foo()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(/new BigInt64Array/.test(foo()));
})();
(function() {
function foo() {
try { new BigUint64Array(limit); } catch (e) { return e.stack; }
}
assertTrue(/new BigUint64Array/.test(foo()));
assertTrue(/new BigUint64Array/.test(foo()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(/new BigUint64Array/.test(foo()));
})();
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