Permalink
Browse files

Reland "[builtins] Implement Object.fromEntries"

This is a reland of a533647

Original change's description:
> [builtins] Implement Object.fromEntries
> 
> Adds the Object.fromEntries() method behind
> --harmony-object-from-entries.
> 
> 
> Includes an initial implementation of the new experimental builtin
> Object.fromEntries implemented by Daniel Clifford, and
> has been modified by Caitlin Potter to support a fast case to skip
> the iterator protocol when it can be done unobservably in common cases.
> 
> There are some incidental changes: A number of CSA macros have been
> updated to use TNodes, and some Context arguments have been
> re-arranged to be implicit in Torque.
> 
> 
> There are also a number of mjsunit tests written mirroring and
> expanding on the test262 tests.
> 
> BUG=v8:8021
> 
> Change-Id: I1c12bee8a2f98c6297b77d5d723910a5e3b630cc
> Co-authored-by: Daniel Clifford <danno@chromium.org>
> Co-authored-by: Caitlin Potter <caitp@igalia.com>
> Reviewed-on: https://chromium-review.googlesource.com/c/1337585
> Commit-Queue: Daniel Clifford <danno@chromium.org>
> Reviewed-by: Daniel Clifford <danno@chromium.org>
> Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#57667}

Bug: v8:8021
Change-Id: I706e2d87bfc2f688e833c1b7d40ca82f5d80f5a2
Reviewed-on: https://chromium-review.googlesource.com/c/1346630
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Commit-Queue: Caitlin Potter <caitp@igalia.com>
Cr-Commit-Position: refs/heads/master@{#57798}
  • Loading branch information...
caitp authored and Commit Bot committed Nov 22, 2018
1 parent 2418d22 commit 8a9cbdacadfd5161e138b61167f4945b30d3c5a7
@@ -958,9 +958,11 @@ torque_files = [
"src/builtins/array-splice.tq",
"src/builtins/array-unshift.tq",
"src/builtins/collections.tq",
"src/builtins/typed-array.tq",
"src/builtins/data-view.tq",
"src/builtins/object.tq",
"src/builtins/object-fromentries.tq",
"src/builtins/iterator.tq",
"src/builtins/typed-array.tq",
"test/torque/test-torque.tq",
"third_party/v8/builtins/array-sort.tq",
]
@@ -970,6 +972,7 @@ torque_namespaces = [
"array",
"collections",
"iterator",
"object",
"typed-array",
"data-view",
"test",
@@ -4848,6 +4848,12 @@ void Genesis::InitializeGlobal_harmony_intl_segmenter() {
#endif // V8_INTL_SUPPORT
void Genesis::InitializeGlobal_harmony_object_from_entries() {
if (!FLAG_harmony_object_from_entries) return;
SimpleInstallFunction(isolate(), isolate()->object_function(), "fromEntries",
Builtins::kObjectFromEntries, 1, false);
}
Handle<JSFunction> Genesis::CreateArrayBuffer(
Handle<String> name, ArrayBufferKind array_buffer_kind) {
// Create the %ArrayBufferPrototype%
@@ -41,10 +41,20 @@ type JSArgumentsObjectWithLength extends JSObject
generates 'TNode<JSArgumentsObjectWithLength>';
type JSArray extends JSArgumentsObjectWithLength
generates 'TNode<JSArray>';
// A HeapObject with a JSArray map, and either fast packed elements, or fast
// holey elements when the global NoElementsProtector is not invalidated.
transient type FastJSArray extends JSArray
generates 'TNode<JSArray>';
// A FastJSArray when the global ArraySpeciesProtector is not invalidated.
transient type FastJSArrayForCopy extends FastJSArray
generates 'TNode<JSArray>';
// A FastJSArray when the global ArrayIteratorProtector is not invalidated.
transient type FastJSArrayWithNoCustomIteration extends FastJSArray
generates 'TNode<JSArray>';
type JSFunction extends JSObject generates 'TNode<JSFunction>';
type JSBoundFunction extends JSObject generates 'TNode<JSBoundFunction>';
type Callable = JSFunction | JSBoundFunction | JSProxy;
@@ -72,6 +82,8 @@ const ARRAY_JOIN_STACK_INDEX: constexpr NativeContextSlot
generates 'Context::ARRAY_JOIN_STACK_INDEX';
const OBJECT_FUNCTION_INDEX: constexpr NativeContextSlot
generates 'Context::OBJECT_FUNCTION_INDEX';
const ITERATOR_RESULT_MAP_INDEX: constexpr NativeContextSlot
generates 'Context::ITERATOR_RESULT_MAP_INDEX';
extern operator '[]' macro LoadContextElement(
NativeContext, NativeContextSlot): Object;
extern operator '[]=' macro StoreContextElement(
@@ -907,6 +919,21 @@ Cast<FastJSArrayForCopy>(implicit context: Context)(o: Object):
}
}
UnsafeCast<FastJSArrayWithNoCustomIteration>(implicit context: Context)(
o: Object): FastJSArrayWithNoCustomIteration {
assert(BranchIfFastJSArrayWithNoCustomIteration(o));
return %RawCast<FastJSArrayWithNoCustomIteration>(o);
}
Cast<FastJSArrayWithNoCustomIteration>(implicit context: Context)(o: Object):
FastJSArrayWithNoCustomIteration labels CastError {
if (BranchIfFastJSArrayWithNoCustomIteration(o)) {
return UnsafeCast<FastJSArrayWithNoCustomIteration>(o);
} else {
goto CastError;
}
}
UnsafeCast<JSFunction>(implicit context: Context)(o: Object): JSFunction {
assert(IsJSFunction(Cast<HeapObject>(o) otherwise unreachable));
return %RawCast<JSFunction>(o);
@@ -947,6 +974,9 @@ extern macro BranchIfFastJSArray(Object, Context): never
labels Taken, NotTaken;
extern macro BranchIfFastJSArrayForCopy(Object, Context): never
labels Taken, NotTaken;
extern macro BranchIfFastJSArrayWithNoCustomIteration(
implicit context: Context)(Object): never labels Taken,
NotTaken;
extern macro BranchIfNotFastJSArray(Object, Context): never
labels Taken, NotTaken;
macro BranchIfNotFastJSArrayForCopy(implicit context: Context)(o: Object): never
@@ -1074,7 +1104,7 @@ extern macro CopyFixedArrayElements(
extern macro AllocateJSArray(constexpr ElementsKind, Map, intptr, Smi): JSArray;
extern macro AllocateJSArray(constexpr ElementsKind, Map, Smi, Smi): JSArray;
extern macro AllocateJSObjectFromMap(Map): HeapObject;
extern macro AllocateJSObjectFromMap(Map): JSObject;
extern operator '[]=' macro StoreFixedDoubleArrayElementSmi(
FixedDoubleArray, Smi, float64): void;
@@ -1191,9 +1221,22 @@ extern macro IsNumber(Object): bool;
extern macro IsExtensibleMap(Map): bool;
extern macro IsCustomElementsReceiverInstanceType(int32): bool;
extern macro IsFastJSArray(Object, Context): bool;
extern macro IsFastJSArrayWithNoCustomIteration(implicit context: Context)(
Object): bool;
extern macro Typeof(Object): Object;
extern macro LoadTargetFromFrame(): JSFunction;
macro IsJSReceiver(o: Object): bool {
typeswitch (o) {
case (JSReceiver): {
return true;
}
case (Object): {
return false;
}
}
}
// Return true iff number is NaN.
macro NumberIsNaN(number: Number): bool {
typeswitch (number) {
@@ -173,7 +173,7 @@ void BaseCollectionsAssembler::AddConstructorEntries(
Variant variant, TNode<Context> context, TNode<Context> native_context,
TNode<Object> collection, TNode<Object> initial_entries) {
TVARIABLE(BoolT, use_fast_loop,
IsFastJSArrayWithNoCustomIteration(initial_entries, context));
IsFastJSArrayWithNoCustomIteration(context, initial_entries));
TNode<IntPtrT> at_least_space_for =
EstimatedInitialSize(initial_entries, use_fast_loop.value());
Label allocate_table(this, &use_fast_loop), exit(this), fast_loop(this),
@@ -193,8 +193,8 @@ void BaseCollectionsAssembler::AddConstructorEntries(
TNode<JSArray> initial_entries_jsarray =
UncheckedCast<JSArray>(initial_entries);
#if DEBUG
CSA_ASSERT(this, IsFastJSArrayWithNoCustomIteration(initial_entries_jsarray,
context));
CSA_ASSERT(this, IsFastJSArrayWithNoCustomIteration(
context, initial_entries_jsarray));
TNode<Map> original_initial_entries_map = LoadMap(initial_entries_jsarray);
#endif
@@ -243,7 +243,7 @@ void BaseCollectionsAssembler::AddConstructorEntriesFromFastJSArray(
CSA_ASSERT(
this,
WordEqual(GetAddFunction(variant, native_context, collection), add_func));
CSA_ASSERT(this, IsFastJSArrayWithNoCustomIteration(fast_jsarray, context));
CSA_ASSERT(this, IsFastJSArrayWithNoCustomIteration(context, fast_jsarray));
TNode<IntPtrT> length = SmiUntag(LoadFastJSArrayLength(fast_jsarray));
CSA_ASSERT(this, IntPtrGreaterThanOrEqual(length, IntPtrConstant(0)));
CSA_ASSERT(
@@ -256,7 +256,7 @@ TF_BUILTIN(IterableToListMayPreserveHoles, IteratorBuiltinsAssembler) {
Label slow_path(this);
GotoIfNot(IsFastJSArrayWithNoCustomIteration(iterable, context), &slow_path);
GotoIfNot(IsFastJSArrayWithNoCustomIteration(context, iterable), &slow_path);
// The fast path will copy holes to the new array.
TailCallBuiltin(Builtins::kCloneFastJSArray, context, iterable);
@@ -270,7 +270,7 @@ void IteratorBuiltinsAssembler::FastIterableToList(
TVariable<Object>* var_result, Label* slow) {
Label done(this), check_string(this), check_map(this), check_set(this);
GotoIfNot(IsFastJSArrayWithNoCustomIteration(iterable, context),
GotoIfNot(IsFastJSArrayWithNoCustomIteration(context, iterable),
&check_string);
// Fast path for fast JSArray.
@@ -0,0 +1,14 @@
// 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.
#ifndef V8_BUILTINS_BUILTINS_OBJECT_GEN_H_
#define V8_BUILTINS_BUILTINS_OBJECT_GEN_H_
#include "src/code-stub-assembler.h"
namespace v8 {
namespace internal {} // namespace internal
} // namespace v8
#endif // V8_BUILTINS_BUILTINS_OBJECT_GEN_H_
@@ -0,0 +1,68 @@
// 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.
namespace object {
transitioning macro ObjectFromEntriesFastCase(implicit context: Context)(
iterable: Object): JSObject labels IfSlow {
typeswitch (iterable) {
case (array: FastJSArrayWithNoCustomIteration): {
const elements: FixedArray =
Cast<FixedArray>(array.elements) otherwise IfSlow;
const length: Smi = array.length;
const result: JSObject = AllocateEmptyJSObject();
for (let k: Smi = 0; k < length; ++k) {
const value: Object = array::LoadElementOrUndefined(elements, k);
const pair: KeyValuePair =
collections::LoadKeyValuePairNoSideEffects(value)
otherwise IfSlow;
// Bail out if ToPropertyKey will attempt to load and call
// Symbol.toPrimitive, toString, and valueOf, which could
// invalidate assumptions about the iterable.
if (IsJSReceiver(pair.key)) goto IfSlow;
CreateDataProperty(result, pair.key, pair.value);
}
return result;
}
case (Object): {
goto IfSlow;
}
}
}
transitioning javascript builtin
ObjectFromEntries(implicit context: Context)(receiver: Object): Object {
const iterable: Object = receiver;
try {
if (IsNullOrUndefined(iterable)) goto Throw;
return ObjectFromEntriesFastCase(iterable) otherwise IfSlow;
}
label IfSlow {
const result: JSObject = AllocateEmptyJSObject();
const fastIteratorResultMap: Map =
Cast<Map>(LoadNativeContext(context)[ITERATOR_RESULT_MAP_INDEX])
otherwise unreachable;
let i: iterator::IteratorRecord = iterator::GetIterator(iterable);
try {
assert(!IsNullOrUndefined(i.object));
while (true) {
const step: Object = iterator::IteratorStep(i, fastIteratorResultMap)
otherwise return result;
const iteratorValue: Object =
iterator::IteratorValue(step, fastIteratorResultMap);
const pair: KeyValuePair =
collections::LoadKeyValuePair(iteratorValue);
CreateDataProperty(result, pair.key, pair.value);
}
return result;
} catch (e) deferred {
iterator::IteratorCloseOnException(i, e);
}
}
label Throw deferred {
ThrowTypeError(context, kNotIterable);
}
}
} // namespace object
@@ -0,0 +1,12 @@
// 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.
namespace object {
macro AllocateEmptyJSObject(implicit context: Context)(): JSObject {
const objectFunction: JSFunction = GetObjectFunction();
const map: Map = Cast<Map>(objectFunction.prototype_or_initial_map)
otherwise unreachable;
return AllocateJSObjectFromMap(map);
}
}
@@ -1056,22 +1056,29 @@ TNode<BoolT> CodeStubAssembler::IsFastJSArray(SloppyTNode<Object> object,
return var_result.value();
}
TNode<BoolT> CodeStubAssembler::IsFastJSArrayWithNoCustomIteration(
TNode<Object> object, TNode<Context> context) {
Label if_false(this, Label::kDeferred), if_fast(this), exit(this);
TVARIABLE(BoolT, var_result);
BranchIfFastJSArray(object, context, &if_fast, &if_false, true);
void CodeStubAssembler::BranchIfFastJSArrayWithNoCustomIteration(
TNode<Context> context, TNode<Object> object, Label* if_true,
Label* if_false) {
Label if_fast(this);
BranchIfFastJSArray(object, context, &if_fast, if_false, true);
BIND(&if_fast);
{
// Check that the Array.prototype hasn't been modified in a way that would
// affect iteration.
Node* protector_cell = LoadRoot(RootIndex::kArrayIteratorProtector);
DCHECK(isolate()->heap()->array_iterator_protector()->IsPropertyCell());
var_result =
Branch(
WordEqual(LoadObjectField(protector_cell, PropertyCell::kValueOffset),
SmiConstant(Isolate::kProtectorValid));
Goto(&exit);
SmiConstant(Isolate::kProtectorValid)),
if_true, if_false);
}
}
TNode<BoolT> CodeStubAssembler::IsFastJSArrayWithNoCustomIteration(
TNode<Context> context, TNode<Object> object) {
Label if_false(this, Label::kDeferred), exit(this);
TVARIABLE(BoolT, var_result, Int32TrueConstant());
BranchIfFastJSArrayWithNoCustomIteration(context, object, &exit, &if_false);
BIND(&if_false);
{
var_result = Int32FalseConstant();
@@ -403,6 +403,28 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
return CAST(p_o);
}
TNode<JSArgumentsObjectWithLength> RawCastObjectToJSArgumentsObjectWithLength(
TNode<Object> p_o) {
return TNode<JSArgumentsObjectWithLength>::UncheckedCast(p_o);
}
TNode<JSArray> RawCastObjectToFastJSArray(TNode<Object> p_o) {
return TNode<JSArray>::UncheckedCast(p_o);
}
TNode<JSArray> RawCastObjectToFastJSArrayForCopy(TNode<Object> p_o) {
return TNode<JSArray>::UncheckedCast(p_o);
}
TNode<JSArray> RawCastObjectToFastJSArrayWithNoCustomIteration(
TNode<Object> p_o) {
return TNode<JSArray>::UncheckedCast(p_o);
}
TNode<JSFunction> RawCastObjectToJSFunction(TNode<Object> p_o) {
return TNode<JSFunction>::UncheckedCast(p_o);
}
Node* MatchesParameterMode(Node* value, ParameterMode mode);
#define PARAMETER_BINOP(OpName, IntPtrOpName, SmiOpName) \
@@ -790,6 +812,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
}
void BranchIfFastJSArrayForCopy(Node* object, Node* context, Label* if_true,
Label* if_false);
void BranchIfFastJSArrayWithNoCustomIteration(TNode<Context> context,
TNode<Object> object,
Label* if_true,
Label* if_false);
// Branches to {if_true} when --force-slow-path flag has been passed.
// It's used for testing to ensure that slow path implementation behave
@@ -2013,8 +2039,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<BoolT> IsExternalStringInstanceType(SloppyTNode<Int32T> instance_type);
TNode<BoolT> IsFastJSArray(SloppyTNode<Object> object,
SloppyTNode<Context> context);
TNode<BoolT> IsFastJSArrayWithNoCustomIteration(TNode<Object> object,
TNode<Context> context);
TNode<BoolT> IsFastJSArrayWithNoCustomIteration(TNode<Context> context,
TNode<Object> object);
TNode<BoolT> IsFeedbackCell(SloppyTNode<HeapObject> object);
TNode<BoolT> IsFeedbackVector(SloppyTNode<HeapObject> object);
TNode<BoolT> IsContext(SloppyTNode<HeapObject> object);
@@ -199,7 +199,8 @@ DEFINE_IMPLICATION(harmony_private_methods, harmony_private_fields)
V(harmony_await_optimization, "harmony await taking 1 tick") \
V(harmony_private_methods, "harmony private methods in class literals") \
V(harmony_regexp_sequence, "RegExp Unicode sequence properties") \
V(harmony_weak_refs, "harmony weak references")
V(harmony_weak_refs, "harmony weak references") \
V(harmony_object_from_entries, "harmony Object.fromEntries()")
#ifdef V8_INTL_SUPPORT
#define HARMONY_INPROGRESS(V) \
Oops, something went wrong.

0 comments on commit 8a9cbda

Please sign in to comment.