Commit c5c50e18 authored by peterwmwong's avatar peterwmwong Committed by Commit Bot

[builtins] Port WeakMap/WeakSet constructor to CSA

- Remove weak-collection.js
- Adds TFJ builtins for WeakSet and WeakMap constructors
- Unified helpers and constructor behavior into a BaseCollectionsAssembler
- Fast paths for...
  - unmodified constructor function
  - argument is a fast JS array
  - entries are fast JS arrays, for Map/WeakMap
  - no arguments passed

Quick benchmarks shows significant improvements (1.12x - 5.7x!) for ALL collection constructors (weak and non-weak):
https://github.com/peterwmwong/v8-perf/blob/master/weakcollection-constructor/README.md

More could be done for performance.  Currently we always call out to JS to add entries, if we knew the prototype was unmodified, we could call the builtins directly.

Bug: v8:5049, v8:6604
Change-Id: Id7912c1eed5bcf512df7fd6238f04166a8a5937e
Reviewed-on: https://chromium-review.googlesource.com/760385Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#49343}
parent 87aeb5eb
......@@ -569,7 +569,6 @@ action("js2c") {
"src/js/v8natives.js",
"src/js/array.js",
"src/js/typedarray.js",
"src/js/weak-collection.js",
"src/js/messages.js",
"src/js/spread.js",
"src/js/proxy.js",
......
......@@ -3300,11 +3300,18 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
}
{ // -- W e a k M a p
Handle<JSFunction> cons =
InstallFunction(global, "WeakMap", JS_WEAK_MAP_TYPE, JSWeakMap::kSize,
0, factory->the_hole_value(), Builtins::kIllegal);
Handle<JSFunction> cons = InstallFunction(
global, "WeakMap", JS_WEAK_MAP_TYPE, JSWeakMap::kSize, 0,
factory->the_hole_value(), Builtins::kWeakMapConstructor);
InstallWithIntrinsicDefaultProto(isolate, cons,
Context::JS_WEAK_MAP_FUN_INDEX);
Handle<SharedFunctionInfo> shared(cons->shared(), isolate);
shared->SetConstructStub(*BUILTIN_CODE(isolate, JSBuiltinsConstructStub));
shared->set_instance_class_name(isolate->heap()->WeakMap_string());
shared->DontAdaptArguments();
shared->set_length(0);
// Setup %WeakMapPrototype%.
Handle<JSObject> prototype(JSObject::cast(cons->instance_prototype()));
......@@ -3322,11 +3329,18 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
}
{ // -- W e a k S e t
Handle<JSFunction> cons =
InstallFunction(global, "WeakSet", JS_WEAK_SET_TYPE, JSWeakSet::kSize,
0, factory->the_hole_value(), Builtins::kIllegal);
Handle<JSFunction> cons = InstallFunction(
global, "WeakSet", JS_WEAK_SET_TYPE, JSWeakSet::kSize, 0,
factory->the_hole_value(), Builtins::kWeakSetConstructor);
InstallWithIntrinsicDefaultProto(isolate, cons,
Context::JS_WEAK_SET_FUN_INDEX);
Handle<SharedFunctionInfo> shared(cons->shared(), isolate);
shared->SetConstructStub(*BUILTIN_CODE(isolate, JSBuiltinsConstructStub));
shared->set_instance_class_name(isolate->heap()->WeakSet_string());
shared->DontAdaptArguments();
shared->set_length(0);
// Setup %WeakSetPrototype%.
Handle<JSObject> prototype(JSObject::cast(cons->instance_prototype()));
......
......@@ -15,22 +15,452 @@ namespace internal {
using compiler::Node;
template <class T>
using TNode = compiler::TNode<T>;
template <class T>
using TVariable = compiler::TypedCodeAssemblerVariable<T>;
class CollectionsBuiltinsAssembler : public CodeStubAssembler {
class BaseCollectionsAssembler : public CodeStubAssembler {
public:
explicit CollectionsBuiltinsAssembler(compiler::CodeAssemblerState* state)
explicit BaseCollectionsAssembler(compiler::CodeAssemblerState* state)
: CodeStubAssembler(state) {}
virtual ~BaseCollectionsAssembler() {}
protected:
Node* AllocateJSMap(Node* js_map_function);
enum Variant { kMap, kSet };
// Adds an entry to a collection. For Maps, properly handles extracting the
// key and value from the entry (see LoadKeyValue()).
TNode<Object> AddConstructorEntry(Variant variant, TNode<Context> context,
TNode<Object> collection,
TNode<Object> add_function,
TNode<Object> key_value,
Label* if_exception = nullptr,
TVariable<Object>* var_exception = nullptr);
// Adds constructor entries to a collection. Choosing a fast path when
// possible.
void AddConstructorEntries(Variant variant, TNode<Context> context,
TNode<Context> native_context,
TNode<Object> collection,
TNode<Object> initial_entries,
TNode<BoolT> is_fast_jsarray);
// Fast path for adding constructor entries. Assumes the entries are a fast
// JS array (see CodeStubAssembler::BranchIfFastJSArray()).
void AddConstructorEntriesFromFastJSArray(Variant variant,
TNode<Context> context,
TNode<Object> collection,
TNode<JSArray> fast_jsarray);
// Adds constructor entries to a collection using the iterator protocol.
void AddConstructorEntriesFromIterable(Variant variant,
TNode<Context> context,
TNode<Context> native_context,
TNode<Object> collection,
TNode<Object> iterable);
// Constructs a collection instance. Choosing a fast path when possible.
TNode<Object> AllocateJSCollection(TNode<Context> context,
TNode<Context> native_context,
int constructor_function_index,
TNode<Object> new_target);
// Fast path for constructing a collection instance if the constructor
// function has not been modified.
TNode<Object> AllocateJSCollectionFast(TNode<HeapObject> constructor);
// Fallback for constructing a collection instance if the constructor function
// has been modified.
TNode<Object> AllocateJSCollectionSlow(TNode<Context> context,
TNode<HeapObject> constructor,
TNode<Object> new_target);
// Allocates the backing store for a collection.
virtual TNode<Object> AllocateTable(Variant variant, TNode<Context> context,
TNode<IntPtrT> at_least_space_for) = 0;
// Main entry point for a collection constructor builtin.
void GenerateConstructor(Variant variant,
const int constructor_function_index,
Handle<String> constructor_function_name,
int collection_tableoffset);
// Retrieves the collection function that adds an entry. `set` for Maps and
// `add` for Sets.
TNode<Object> GetAddFunction(Variant variant, TNode<Context> context,
TNode<Object> collection);
// Estimates the number of entries the collection will have after adding the
// entries passed in the constructor. AllocateTable() can use this to avoid
// the time of growing/rehashing when adding the constructor entries.
TNode<IntPtrT> EstimatedInitialSize(TNode<Object> initial_entries,
TNode<BoolT> is_fast_jsarray);
void GotoIfNotJSReceiver(Node* const obj, Label* if_not_receiver);
// Loads key and value variables with the first and second elements of an
// array. If the array lacks 2 elements, undefined is used.
void LoadKeyValue(TNode<Context> context, TNode<Object> maybe_array,
TVariable<Object>* key, TVariable<Object>* value,
Label* if_exception = nullptr,
TVariable<Object>* var_exception = nullptr);
};
TNode<Object> BaseCollectionsAssembler::AddConstructorEntry(
Variant variant, TNode<Context> context, TNode<Object> collection,
TNode<Object> add_function, TNode<Object> key_value, Label* if_exception,
TVariable<Object>* var_exception) {
if (variant == kMap) {
Label exit(this), if_notobject(this, Label::kDeferred);
GotoIfNotJSReceiver(key_value, &if_notobject);
TVARIABLE(Object, key);
TVARIABLE(Object, value);
LoadKeyValue(context, key_value, &key, &value, if_exception, var_exception);
Node* key_n = key;
Node* value_n = value;
TNode<Object> add_call =
UncheckedCast<Object>(CallJS(CodeFactory::Call(isolate()), context,
add_function, collection, key_n, value_n));
Goto(&exit);
BIND(&if_notobject);
{
Node* ret = CallRuntime(
Runtime::kThrowTypeError, context,
SmiConstant(MessageTemplate::kIteratorValueNotAnObject), key_value);
if (if_exception != nullptr) {
DCHECK(var_exception != nullptr);
GotoIfException(ret, if_exception, var_exception);
}
Unreachable();
}
BIND(&exit);
return add_call;
} else { // variant == kSet
DCHECK(variant == kSet);
return UncheckedCast<Object>(CallJS(CodeFactory::Call(isolate()), context,
add_function, collection, key_value));
}
}
void BaseCollectionsAssembler::AddConstructorEntries(
Variant variant, TNode<Context> context, TNode<Context> native_context,
TNode<Object> collection, TNode<Object> initial_entries,
TNode<BoolT> is_fast_jsarray) {
Label exit(this), slow_loop(this, Label::kDeferred);
GotoIf(IsNullOrUndefined(initial_entries), &exit);
GotoIfNot(is_fast_jsarray, &slow_loop);
AddConstructorEntriesFromFastJSArray(variant, context, collection,
UncheckedCast<JSArray>(initial_entries));
Goto(&exit);
BIND(&slow_loop);
{
AddConstructorEntriesFromIterable(variant, context, native_context,
collection, initial_entries);
Goto(&exit);
}
BIND(&exit);
}
void BaseCollectionsAssembler::AddConstructorEntriesFromFastJSArray(
Variant variant, TNode<Context> context, TNode<Object> collection,
TNode<JSArray> fast_jsarray) {
TNode<FixedArrayBase> elements = LoadElements(fast_jsarray);
TNode<Int32T> elements_kind = LoadMapElementsKind(LoadMap(fast_jsarray));
TNode<IntPtrT> length = SmiUntag(LoadFastJSArrayLength(fast_jsarray));
TNode<Object> add_func = GetAddFunction(variant, context, collection);
CSA_ASSERT(this, IsFastJSArray(fast_jsarray, context));
CSA_ASSERT(this, IsFastElementsKind(elements_kind));
CSA_ASSERT(this, IntPtrGreaterThanOrEqual(length, IntPtrConstant(0)));
Label exit(this), if_doubles(this), if_smiorobjects(this);
Branch(IsFastSmiOrTaggedElementsKind(elements_kind), &if_smiorobjects,
&if_doubles);
BIND(&if_smiorobjects);
{
auto set_entry = [&](Node* index) {
TNode<Object> element = CAST(LoadFixedArrayElement(elements, index));
AddConstructorEntry(variant, context, collection, add_func, element);
};
BuildFastLoop(IntPtrConstant(0), length, set_entry, 1,
ParameterMode::INTPTR_PARAMETERS, IndexAdvanceMode::kPost);
Goto(&exit);
}
BIND(&if_doubles);
{
// A Map constructor requires entries to be arrays (ex. [key, value]),
// so a FixedDoubleArray can never succeed.
if (variant == kMap) {
TNode<Float64T> element =
UncheckedCast<Float64T>(LoadFixedDoubleArrayElement(
elements, IntPtrConstant(0), MachineType::Float64(), 0,
INTPTR_PARAMETERS));
ThrowTypeError(context, MessageTemplate::kIteratorValueNotAnObject,
AllocateHeapNumberWithValue(element));
} else {
auto set_entry = [&](Node* index) {
TNode<Float64T> element =
UncheckedCast<Float64T>(LoadFixedDoubleArrayElement(
elements, index, MachineType::Float64(), 0, INTPTR_PARAMETERS));
AddConstructorEntry(kSet, context, collection, add_func,
AllocateHeapNumberWithValue(element));
};
BuildFastLoop(IntPtrConstant(0), length, set_entry, 1,
ParameterMode::INTPTR_PARAMETERS, IndexAdvanceMode::kPost);
Goto(&exit);
}
}
BIND(&exit);
}
void BaseCollectionsAssembler::AddConstructorEntriesFromIterable(
Variant variant, TNode<Context> context, TNode<Context> native_context,
TNode<Object> collection, TNode<Object> iterable) {
Label exit(this), loop(this), if_exception(this, Label::kDeferred);
CSA_ASSERT(this, Word32BinaryNot(IsNullOrUndefined(iterable)));
TNode<Object> add_func = GetAddFunction(variant, context, collection);
IteratorBuiltinsAssembler iterator_assembler(this->state());
TNode<Object> iterator =
CAST(iterator_assembler.GetIterator(context, iterable));
CSA_ASSERT(this, Word32BinaryNot(IsUndefined(iterator)));
TNode<Object> fast_iterator_result_map =
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
TVARIABLE(Object, var_exception);
Goto(&loop);
BIND(&loop);
{
TNode<Object> next = CAST(iterator_assembler.IteratorStep(
context, iterator, &exit, fast_iterator_result_map));
TNode<Object> next_value = CAST(iterator_assembler.IteratorValue(
context, next, fast_iterator_result_map));
TNode<Object> add_result =
AddConstructorEntry(variant, context, collection, add_func, next_value,
&if_exception, &var_exception);
GotoIfException(add_result, &if_exception, &var_exception);
Goto(&loop);
}
BIND(&if_exception);
{
iterator_assembler.IteratorCloseOnException(context, iterator,
&var_exception);
}
BIND(&exit);
}
TNode<Object> BaseCollectionsAssembler::AllocateJSCollection(
TNode<Context> context, TNode<Context> native_context,
int constructor_function_index, TNode<Object> new_target) {
TNode<HeapObject> constructor =
CAST(LoadContextElement(native_context, constructor_function_index));
TNode<BoolT> is_target_unmodified = WordEqual(constructor, new_target);
return Select<Object>(is_target_unmodified,
[=] { return AllocateJSCollectionFast(constructor); },
[=] {
return AllocateJSCollectionSlow(context, constructor,
new_target);
},
MachineRepresentation::kTagged);
}
TNode<Object> BaseCollectionsAssembler::AllocateJSCollectionFast(
TNode<HeapObject> constructor) {
CSA_ASSERT(this, IsConstructorMap(LoadMap(constructor)));
TNode<Object> initial_map =
LoadObjectField(constructor, JSFunction::kPrototypeOrInitialMapOffset);
return CAST(AllocateJSObjectFromMap(initial_map));
}
TNode<Object> BaseCollectionsAssembler::AllocateJSCollectionSlow(
TNode<Context> context, TNode<HeapObject> constructor,
TNode<Object> new_target) {
ConstructorBuiltinsAssembler constructor_assembler(this->state());
return CAST(constructor_assembler.EmitFastNewObject(context, constructor,
new_target));
}
void BaseCollectionsAssembler::GenerateConstructor(
Variant variant, const int constructor_function_index,
Handle<String> constructor_function_name, int collection_tableoffset) {
const int kIterableArg = 0;
CodeStubArguments args(
this, ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount)));
TNode<Object> iterable = args.GetOptionalArgumentValue(kIterableArg);
TNode<Object> new_target = CAST(Parameter(BuiltinDescriptor::kNewTarget));
TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
Label if_undefined(this, Label::kDeferred);
GotoIf(IsUndefined(new_target), &if_undefined);
TNode<BoolT> is_fast_jsarray = IsFastJSArray(iterable, context);
TNode<IntPtrT> at_least_space_for =
EstimatedInitialSize(iterable, is_fast_jsarray);
TNode<Context> native_context = LoadNativeContext(context);
TNode<Object> collection = AllocateJSCollection(
context, native_context, constructor_function_index, new_target);
TNode<Object> table = AllocateTable(variant, context, at_least_space_for);
StoreObjectField(collection, collection_tableoffset, table);
AddConstructorEntries(variant, context, native_context, collection, iterable,
is_fast_jsarray);
Return(collection);
BIND(&if_undefined);
ThrowTypeError(context, MessageTemplate::kConstructorNotFunction,
HeapConstant(constructor_function_name));
}
TNode<Object> BaseCollectionsAssembler::GetAddFunction(
Variant variant, TNode<Context> context, TNode<Object> collection) {
// TODO(pwong): Consider calling the builtin directly when the prototype is
// unmodified. This will require tracking WeakMap/WeakSet prototypes on the
// native context.
Handle<String> add_func_name = variant == kMap
? isolate()->factory()->set_string()
: isolate()->factory()->add_string();
TNode<Object> add_func =
CAST(GetProperty(context, collection, add_func_name));
Label exit(this), if_notcallable(this, Label::kDeferred);
GotoIf(TaggedIsSmi(add_func), &if_notcallable);
GotoIfNot(IsCallable(add_func), &if_notcallable);
Goto(&exit);
BIND(&if_notcallable);
ThrowTypeError(context, MessageTemplate::kPropertyNotFunction, add_func,
HeapConstant(add_func_name), collection);
BIND(&exit);
return add_func;
}
TNode<IntPtrT> BaseCollectionsAssembler::EstimatedInitialSize(
TNode<Object> initial_entries, TNode<BoolT> is_fast_jsarray) {
return Select<IntPtrT>(
is_fast_jsarray,
[=] { return SmiUntag(LoadFastJSArrayLength(CAST(initial_entries))); },
[=] { return IntPtrConstant(0); }, MachineType::PointerRepresentation());
}
void BaseCollectionsAssembler::GotoIfNotJSReceiver(Node* const obj,
Label* if_not_receiver) {
GotoIf(TaggedIsSmi(obj), if_not_receiver);
GotoIfNot(IsJSReceiver(obj), if_not_receiver);
}
void BaseCollectionsAssembler::LoadKeyValue(TNode<Context> context,
TNode<Object> maybe_array,
TVariable<Object>* key,
TVariable<Object>* value,
Label* if_exception,
TVariable<Object>* var_exception) {
Label exit(this), if_fast(this), if_slow(this, Label::kDeferred);
BranchIfFastJSArray(maybe_array, context, &if_fast, &if_slow);
BIND(&if_fast);
{
TNode<JSArray> array = CAST(maybe_array);
TNode<Smi> length = LoadFastJSArrayLength(array);
TNode<FixedArrayBase> elements = LoadElements(array);
TNode<Int32T> elements_kind = LoadMapElementsKind(LoadMap(array));
Label if_smiorobjects(this), if_doubles(this);
Branch(IsFastSmiOrTaggedElementsKind(elements_kind), &if_smiorobjects,
&if_doubles);
BIND(&if_smiorobjects);
{
Label if_one(this), if_two(this);
GotoIf(SmiGreaterThan(length, SmiConstant(1)), &if_two);
GotoIf(SmiEqual(length, SmiConstant(1)), &if_one);
{ // empty array
*key = UndefinedConstant();
*value = UndefinedConstant();
Goto(&exit);
}
BIND(&if_one);
{
*key = CAST(LoadFixedArrayElement(elements, 0));
*value = UndefinedConstant();
Goto(&exit);
}
BIND(&if_two);
{
*key = CAST(LoadFixedArrayElement(elements, 0));
*value = CAST(LoadFixedArrayElement(elements, 1));
Goto(&exit);
}
}
BIND(&if_doubles);
{
Label if_one(this), if_two(this);
GotoIf(SmiGreaterThan(length, SmiConstant(1)), &if_two);
GotoIf(SmiEqual(length, SmiConstant(1)), &if_one);
{ // empty array
*key = UndefinedConstant();
*value = UndefinedConstant();
Goto(&exit);
}
BIND(&if_one);
{
*key = AllocateHeapNumberWithValue(LoadFixedDoubleArrayElement(
elements, IntPtrConstant(0), MachineType::Float64(), 0,
INTPTR_PARAMETERS));
*value = UndefinedConstant();
Goto(&exit);
}
BIND(&if_two);
{
*key = AllocateHeapNumberWithValue(LoadFixedDoubleArrayElement(
elements, IntPtrConstant(0), MachineType::Float64(), 0,
INTPTR_PARAMETERS));
*value = AllocateHeapNumberWithValue(LoadFixedDoubleArrayElement(
elements, IntPtrConstant(1), MachineType::Float64(), 0,
INTPTR_PARAMETERS));
Goto(&exit);
}
}
}
BIND(&if_slow);
{
*key = UncheckedCast<Object>(
GetProperty(context, maybe_array, isolate()->factory()->zero_string()));
if (if_exception != nullptr) {
DCHECK(var_exception != nullptr);
GotoIfException(*key, if_exception, var_exception);
}
*value = UncheckedCast<Object>(
GetProperty(context, maybe_array, isolate()->factory()->one_string()));
if (if_exception != nullptr) {
DCHECK(var_exception != nullptr);
GotoIfException(*value, if_exception, var_exception);
}
Goto(&exit);
}
BIND(&exit);
}
class CollectionsBuiltinsAssembler : public BaseCollectionsAssembler {
public:
explicit CollectionsBuiltinsAssembler(compiler::CodeAssemblerState* state)
: BaseCollectionsAssembler(state) {}
protected:
template <typename CollectionType>
Node* AllocateOrderedHashTable();
Node* AllocateJSCollection(Node* js_map_function);
template <typename IteratorType>
Node* AllocateJSCollectionIterator(Node* context, int map_index,
Node* collection);
TNode<Object> AllocateTable(Variant variant, TNode<Context> context,
TNode<IntPtrT> at_least_space_for);
Node* GetHash(Node* const key);
Node* CallGetHashRaw(Node* const key);
Node* CallGetOrCreateHashRaw(Node* const key);
......@@ -193,19 +623,6 @@ Node* CollectionsBuiltinsAssembler::AllocateOrderedHashTable() {
return table;
}
Node* CollectionsBuiltinsAssembler::AllocateJSCollection(
Node* js_map_function) {
CSA_ASSERT(this, IsConstructorMap(LoadMap(js_map_function)));
Node* const initial_map = LoadObjectField(
js_map_function, JSFunction::kPrototypeOrInitialMapOffset);
Node* const instance = AllocateJSObjectFromMap(initial_map);
StoreObjectFieldRoot(instance, JSMap::kTableOffset,
Heap::kUndefinedValueRootIndex);
return instance;
}
template <typename IteratorType>
Node* CollectionsBuiltinsAssembler::AllocateJSCollectionIterator(
Node* context, int map_index, Node* collection) {
......@@ -224,225 +641,21 @@ Node* CollectionsBuiltinsAssembler::AllocateJSCollectionIterator(
return iterator;
}
TF_BUILTIN(MapConstructor, CollectionsBuiltinsAssembler) {
const int kIterableArg = 0;
Node* argc =
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
CodeStubArguments args(this, argc);
Node* const iterable = args.GetOptionalArgumentValue(kIterableArg);
Node* const new_target = Parameter(BuiltinDescriptor::kNewTarget);
Node* const context = Parameter(BuiltinDescriptor::kContext);
Label if_target_is_undefined(this, Label::kDeferred);
GotoIf(IsUndefined(new_target), &if_target_is_undefined);
Node* const native_context = LoadNativeContext(context);
Node* const js_map_fun =
LoadContextElement(native_context, Context::JS_MAP_FUN_INDEX);
VARIABLE(var_result, MachineRepresentation::kTagged);
Label init(this), exit(this), if_targetisnotmodified(this),
if_targetismodified(this);
Branch(WordEqual(js_map_fun, new_target), &if_targetisnotmodified,
&if_targetismodified);
BIND(&if_targetisnotmodified);
{
Node* const instance = AllocateJSCollection(js_map_fun);
var_result.Bind(instance);
Goto(&init);
}
BIND(&if_targetismodified);
{
ConstructorBuiltinsAssembler constructor_assembler(this->state());
Node* const instance = constructor_assembler.EmitFastNewObject(
context, js_map_fun, new_target);
var_result.Bind(instance);
Goto(&init);
}
BIND(&init);
Node* table = AllocateOrderedHashTable<OrderedHashMap>();
StoreObjectField(var_result.value(), JSMap::kTableOffset, table);
GotoIf(Word32Or(IsUndefined(iterable), IsNull(iterable)), &exit);
Label if_notcallable(this);
// TODO(gsathya): Add fast path for unmodified maps.
Node* const adder = GetProperty(context, var_result.value(),
isolate()->factory()->set_string());
GotoIf(TaggedIsSmi(adder), &if_notcallable);
GotoIfNot(IsCallable(adder), &if_notcallable);
IteratorBuiltinsAssembler iterator_assembler(this->state());
Node* const iterator = iterator_assembler.GetIterator(context, iterable);
GotoIf(IsUndefined(iterator), &exit);
Node* const fast_iterator_result_map =
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant());
Label loop(this), if_notobject(this), if_exception(this);
Goto(&loop);
BIND(&loop);
{
Node* const next = iterator_assembler.IteratorStep(
context, iterator, &exit, fast_iterator_result_map);
Node* const next_value = iterator_assembler.IteratorValue(
context, next, fast_iterator_result_map);
GotoIf(TaggedIsSmi(next_value), &if_notobject);
GotoIfNot(IsJSReceiver(next_value), &if_notobject);
Node* const k =
GetProperty(context, next_value, isolate()->factory()->zero_string());
GotoIfException(k, &if_exception, &var_exception);
Node* const v =
GetProperty(context, next_value, isolate()->factory()->one_string());
GotoIfException(v, &if_exception, &var_exception);
Node* add_call = CallJS(CodeFactory::Call(isolate()), context, adder,
var_result.value(), k, v);
GotoIfException(add_call, &if_exception, &var_exception);
Goto(&loop);
BIND(&if_notobject);
{
Node* ret = CallRuntime(
Runtime::kThrowTypeError, context,
SmiConstant(MessageTemplate::kIteratorValueNotAnObject), next_value);
GotoIfException(ret, &if_exception, &var_exception);
Unreachable();
}
}
BIND(&if_exception);
{
iterator_assembler.IteratorCloseOnException(context, iterator,
&var_exception);
}
BIND(&if_notcallable);
{
Node* const receiver_str = HeapConstant(isolate()->factory()->add_string());
ThrowTypeError(context, MessageTemplate::kPropertyNotFunction, adder,
receiver_str, var_result.value());
}
BIND(&if_target_is_undefined);
ThrowTypeError(context, MessageTemplate::kConstructorNotFunction,
HeapConstant(isolate()->factory()->Map_string()));
TNode<Object> CollectionsBuiltinsAssembler::AllocateTable(
Variant variant, TNode<Context> context,
TNode<IntPtrT> at_least_space_for) {
return CAST(variant == kMap ? AllocateOrderedHashTable<OrderedHashMap>()
: AllocateOrderedHashTable<OrderedHashSet>());
}
BIND(&exit);
args.PopAndReturn(var_result.value());
TF_BUILTIN(MapConstructor, CollectionsBuiltinsAssembler) {
GenerateConstructor(kMap, Context::JS_MAP_FUN_INDEX,
isolate()->factory()->Map_string(), JSMap::kTableOffset);
}
TF_BUILTIN(SetConstructor, CollectionsBuiltinsAssembler) {
const int kIterableArg = 0;
Node* argc =
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
CodeStubArguments args(this, argc);
Node* const iterable = args.GetOptionalArgumentValue(kIterableArg);
Node* const new_target = Parameter(BuiltinDescriptor::kNewTarget);
Node* const context = Parameter(BuiltinDescriptor::kContext);
Label if_target_is_undefined(this, Label::kDeferred);
GotoIf(IsUndefined(new_target), &if_target_is_undefined);
Node* const native_context = LoadNativeContext(context);
Node* const js_set_fun =
LoadContextElement(native_context, Context::JS_SET_FUN_INDEX);
VARIABLE(var_result, MachineRepresentation::kTagged);
Label init(this), exit(this), if_targetisnotmodified(this),
if_targetismodified(this);
Branch(WordEqual(js_set_fun, new_target), &if_targetisnotmodified,
&if_targetismodified);
BIND(&if_targetisnotmodified);
{
Node* const instance = AllocateJSCollection(js_set_fun);
var_result.Bind(instance);
Goto(&init);
}
BIND(&if_targetismodified);
{
ConstructorBuiltinsAssembler constructor_assembler(this->state());
Node* const instance = constructor_assembler.EmitFastNewObject(
context, js_set_fun, new_target);
var_result.Bind(instance);
Goto(&init);
}
BIND(&init);
Node* table = AllocateOrderedHashTable<OrderedHashSet>();
StoreObjectField(var_result.value(), JSSet::kTableOffset, table);
GotoIf(Word32Or(IsUndefined(iterable), IsNull(iterable)), &exit);
Label if_notcallable(this);
// TODO(gsathya): Add fast path for unmodified maps.
Node* const adder = GetProperty(context, var_result.value(),
isolate()->factory()->add_string());
GotoIf(TaggedIsSmi(adder), &if_notcallable);
GotoIfNot(IsCallable(adder), &if_notcallable);
IteratorBuiltinsAssembler iterator_assembler(this->state());
Node* const iterator = iterator_assembler.GetIterator(context, iterable);
GotoIf(IsUndefined(iterator), &exit);
Node* const fast_iterator_result_map =
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant());
Label loop(this), if_notobject(this), if_exception(this);
Goto(&loop);
BIND(&loop);
{
Node* const next = iterator_assembler.IteratorStep(
context, iterator, &exit, fast_iterator_result_map);
Node* const next_value = iterator_assembler.IteratorValue(
context, next, fast_iterator_result_map);
Node* add_call = CallJS(CodeFactory::Call(isolate()), context, adder,
var_result.value(), next_value);
GotoIfException(add_call, &if_exception, &var_exception);
Goto(&loop);
}
BIND(&if_exception);
{
iterator_assembler.IteratorCloseOnException(context, iterator,
&var_exception);
}
BIND(&if_notcallable);
ThrowTypeError(context, MessageTemplate::kPropertyNotFunction, adder,
HeapConstant(isolate()->factory()->add_string()),
var_result.value());
BIND(&if_target_is_undefined);
ThrowTypeError(context, MessageTemplate::kConstructorNotFunction,
HeapConstant(isolate()->factory()->Set_string()));
BIND(&exit);
args.PopAndReturn(var_result.value());
GenerateConstructor(kSet, Context::JS_SET_FUN_INDEX,
isolate()->factory()->Set_string(), JSSet::kTableOffset);
}
Node* CollectionsBuiltinsAssembler::CallGetOrCreateHashRaw(Node* const key) {
......@@ -1710,16 +1923,19 @@ TF_BUILTIN(FindOrderedHashMapEntry, CollectionsBuiltinsAssembler) {
Return(SmiConstant(-1));
}
class WeakCollectionsBuiltinsAssembler : public CodeStubAssembler {
class WeakCollectionsBuiltinsAssembler : public BaseCollectionsAssembler {
public:
explicit WeakCollectionsBuiltinsAssembler(compiler::CodeAssemblerState* state)
: CodeStubAssembler(state) {}
: BaseCollectionsAssembler(state) {}
protected:
void AddEntry(TNode<Object> table, TNode<IntPtrT> key_index,
TNode<Object> key, TNode<Object> value,
TNode<IntPtrT> number_of_elements);
TNode<Object> AllocateTable(Variant variant, TNode<Context> context,
TNode<IntPtrT> at_least_space_for);
// Generates and sets the identity for a JSRececiver.
TNode<Smi> CreateIdentityHash(TNode<Object> receiver);
TNode<IntPtrT> EntryMask(TNode<IntPtrT> capacity);
......@@ -1778,6 +1994,33 @@ void WeakCollectionsBuiltinsAssembler::AddEntry(
SmiFromWord(number_of_elements), SKIP_WRITE_BARRIER);
}
TNode<Object> WeakCollectionsBuiltinsAssembler::AllocateTable(
Variant variant, TNode<Context> context,
TNode<IntPtrT> at_least_space_for) {
// See HashTable::New().
CSA_ASSERT(this,
IntPtrLessThanOrEqual(IntPtrConstant(0), at_least_space_for));
TNode<IntPtrT> capacity = HashTableComputeCapacity(at_least_space_for);
// See HashTable::NewInternal().
TNode<IntPtrT> length = KeyIndexFromEntry(capacity);
TNode<Object> table = CAST(AllocateFixedArray(HOLEY_ELEMENTS, length));
// See BaseShape::GetMap().
StoreMapNoWriteBarrier(table, Heap::kHashTableMapRootIndex);
StoreFixedArrayElement(table, ObjectHashTable::kNumberOfElementsIndex,
SmiConstant(0), SKIP_WRITE_BARRIER);
StoreFixedArrayElement(table, ObjectHashTable::kNumberOfDeletedElementsIndex,
SmiConstant(0), SKIP_WRITE_BARRIER);
StoreFixedArrayElement(table, ObjectHashTable::kCapacityIndex,
SmiFromWord(capacity), SKIP_WRITE_BARRIER);
TNode<IntPtrT> start = KeyIndexFromEntry(IntPtrConstant(0));
FillFixedArrayWithValue(HOLEY_ELEMENTS, table, start, length,
Heap::kUndefinedValueRootIndex);
return table;
}
TNode<Smi> WeakCollectionsBuiltinsAssembler::CreateIdentityHash(
TNode<Object> key) {
TNode<ExternalReference> function_addr = ExternalConstant(
......@@ -1948,14 +2191,25 @@ TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::ValueIndexFromKeyIndex(
ObjectHashTable::kEntryKeyIndex));
}
TF_BUILTIN(WeakMapConstructor, WeakCollectionsBuiltinsAssembler) {
GenerateConstructor(kMap, Context::JS_WEAK_MAP_FUN_INDEX,
isolate()->factory()->WeakMap_string(),
JSWeakMap::kTableOffset);
}
TF_BUILTIN(WeakSetConstructor, WeakCollectionsBuiltinsAssembler) {
GenerateConstructor(kSet, Context::JS_WEAK_SET_FUN_INDEX,
isolate()->factory()->WeakSet_string(),
JSWeakSet::kTableOffset);
}
TF_BUILTIN(WeakMapLookupHashIndex, WeakCollectionsBuiltinsAssembler) {
TNode<Object> table = CAST(Parameter(Descriptor::kTable));
TNode<Object> key = CAST(Parameter(Descriptor::kKey));
Label if_not_found(this);
GotoIf(TaggedIsSmi(key), &if_not_found);
GotoIfNot(IsJSReceiver(key), &if_not_found);
GotoIfNotJSReceiver(key, &if_not_found);
TNode<IntPtrT> hash = LoadJSReceiverIdentityHash(key, &if_not_found);
TNode<IntPtrT> capacity = LoadTableCapacity(table);
......@@ -2020,8 +2274,7 @@ TF_BUILTIN(WeakCollectionDelete, WeakCollectionsBuiltinsAssembler) {
Label call_runtime(this), if_not_found(this);
GotoIf(TaggedIsSmi(key), &if_not_found);
GotoIfNot(IsJSReceiver(key), &if_not_found);
GotoIfNotJSReceiver(key, &if_not_found);
TNode<IntPtrT> hash = LoadJSReceiverIdentityHash(key, &if_not_found);
TNode<Object> table = LoadTable(collection);
......@@ -2105,7 +2358,7 @@ TF_BUILTIN(WeakMapPrototypeDelete, CodeStubAssembler) {
Return(CallBuiltin(Builtins::kWeakCollectionDelete, context, receiver, key));
}
TF_BUILTIN(WeakMapPrototypeSet, CodeStubAssembler) {
TF_BUILTIN(WeakMapPrototypeSet, WeakCollectionsBuiltinsAssembler) {
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
TNode<Object> key = CAST(Parameter(Descriptor::kKey));
......@@ -2115,8 +2368,7 @@ TF_BUILTIN(WeakMapPrototypeSet, CodeStubAssembler) {
"WeakMap.prototype.set");
Label throw_invalid_key(this);
GotoIf(TaggedIsSmi(key), &throw_invalid_key);
GotoIfNot(IsJSReceiver(key), &throw_invalid_key);
GotoIfNotJSReceiver(key, &throw_invalid_key);
Return(
CallBuiltin(Builtins::kWeakCollectionSet, context, receiver, key, value));
......@@ -2125,7 +2377,7 @@ TF_BUILTIN(WeakMapPrototypeSet, CodeStubAssembler) {
ThrowTypeError(context, MessageTemplate::kInvalidWeakMapKey, key);
}
TF_BUILTIN(WeakSetPrototypeAdd, CodeStubAssembler) {
TF_BUILTIN(WeakSetPrototypeAdd, WeakCollectionsBuiltinsAssembler) {
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
TNode<Object> value = CAST(Parameter(Descriptor::kValue));
......@@ -2134,8 +2386,7 @@ TF_BUILTIN(WeakSetPrototypeAdd, CodeStubAssembler) {
"WeakSet.prototype.add");
Label throw_invalid_value(this);
GotoIf(TaggedIsSmi(value), &throw_invalid_value);
GotoIfNot(IsJSReceiver(value), &throw_invalid_value);
GotoIfNotJSReceiver(value, &throw_invalid_value);
Return(CallBuiltin(Builtins::kWeakCollectionSet, context, receiver, value,
TrueConstant()));
......
......@@ -1084,6 +1084,7 @@ namespace internal {
TFC(ThrowWasmTrapFuncSigMismatch, WasmRuntimeCall, 1) \
\
/* WeakMap */ \
TFJ(WeakMapConstructor, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
TFS(WeakMapLookupHashIndex, kTable, kKey) \
TFJ(WeakMapGet, 1, kKey) \
TFJ(WeakMapHas, 1, kKey) \
......@@ -1091,6 +1092,7 @@ namespace internal {
TFJ(WeakMapPrototypeDelete, 1, kKey) \
\
/* WeakSet */ \
TFJ(WeakSetConstructor, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
TFJ(WeakSetHas, 1, kKey) \
TFJ(WeakSetPrototypeAdd, 1, kValue) \
TFJ(WeakSetPrototypeDelete, 1, kValue) \
......
......@@ -851,6 +851,25 @@ void CodeStubAssembler::BranchIfJSObject(Node* object, Label* if_true,
Branch(IsJSObject(object), if_true, if_false);
}
TNode<BoolT> CodeStubAssembler::IsFastJSArray(SloppyTNode<Object> object,
SloppyTNode<Context> context) {
Label if_true(this), if_false(this, Label::kDeferred), exit(this);
BranchIfFastJSArray(object, context, &if_true, &if_false);
TVARIABLE(BoolT, var_result);
BIND(&if_true);
{
var_result = ReinterpretCast<BoolT>(Int32Constant(1));
Goto(&exit);
}
BIND(&if_false);
{
var_result = ReinterpretCast<BoolT>(Int32Constant(0));
Goto(&exit);
}
BIND(&exit);
return var_result;
}
void CodeStubAssembler::BranchIfFastJSArray(Node* object, Node* context,
Label* if_true, Label* if_false) {
// Bailout if receiver is a Smi.
......@@ -6034,8 +6053,8 @@ template Node* CodeStubAssembler::EntryToIndex<NumberDictionary>(Node*, int);
// This must be kept in sync with HashTableBase::ComputeCapacity().
TNode<IntPtrT> CodeStubAssembler::HashTableComputeCapacity(
SloppyTNode<IntPtrT> at_least_space_for) {
Node* capacity = IntPtrRoundUpToPowerOfTwo32(IntPtrAdd(
at_least_space_for, WordShr(at_least_space_for, IntPtrConstant(1))));
Node* capacity = IntPtrRoundUpToPowerOfTwo32(
IntPtrAdd(at_least_space_for, WordShr(at_least_space_for, 1)));
return IntPtrMax(capacity, IntPtrConstant(HashTableBase::kMinCapacity));
}
......@@ -10521,6 +10540,11 @@ Node* CodeStubAssembler::IsFastElementsKind(Node* elements_kind) {
Int32Constant(LAST_FAST_ELEMENTS_KIND));
}
Node* CodeStubAssembler::IsFastSmiOrTaggedElementsKind(Node* elements_kind) {
return Uint32LessThanOrEqual(elements_kind,
Int32Constant(TERMINAL_FAST_ELEMENTS_KIND));
}
Node* CodeStubAssembler::IsHoleyFastElementsKind(Node* elements_kind) {
CSA_ASSERT(this, IsFastElementsKind(elements_kind));
......
......@@ -1043,6 +1043,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* IsDictionary(Node* object);
Node* IsExtensibleMap(Node* map);
Node* IsExternalStringInstanceType(Node* instance_type);
TNode<BoolT> IsFastJSArray(SloppyTNode<Object> object,
SloppyTNode<Context> context);
Node* IsFeedbackVector(Node* object);
Node* IsFixedArray(Node* object);
Node* IsFixedArrayWithKind(Node* object, ElementsKind kind);
......@@ -1119,6 +1121,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
// ElementsKind helpers:
Node* IsFastElementsKind(Node* elements_kind);
Node* IsFastSmiOrTaggedElementsKind(Node* elements_kind);
Node* IsHoleyFastElementsKind(Node* elements_kind);
Node* IsElementsKindGreaterThan(Node* target_kind,
ElementsKind reference_kind);
......
// Copyright 2012 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.
(function(global, utils) {
"use strict";
%CheckIsBootstrapping();
// -------------------------------------------------------------------
// Imports
var GlobalWeakMap = global.WeakMap;
var GlobalWeakSet = global.WeakSet;
var MathRandom = global.Math.random;
// -------------------------------------------------------------------
// Harmony WeakMap
function WeakMapConstructor(iterable) {
if (IS_UNDEFINED(new.target)) {
throw %make_type_error(kConstructorNotFunction, "WeakMap");
}
%WeakCollectionInitialize(this);
if (!IS_NULL_OR_UNDEFINED(iterable)) {
var adder = this.set;
if (!IS_CALLABLE(adder)) {
throw %make_type_error(kPropertyNotFunction, adder, 'set', this);
}
for (var nextItem of iterable) {
if (!IS_RECEIVER(nextItem)) {
throw %make_type_error(kIteratorValueNotAnObject, nextItem);
}
%_Call(adder, this, nextItem[0], nextItem[1]);
}
}
}
// -------------------------------------------------------------------
%SetCode(GlobalWeakMap, WeakMapConstructor);
%FunctionSetLength(GlobalWeakMap, 0);
// -------------------------------------------------------------------
// Harmony WeakSet
function WeakSetConstructor(iterable) {
if (IS_UNDEFINED(new.target)) {
throw %make_type_error(kConstructorNotFunction, "WeakSet");
}
%WeakCollectionInitialize(this);
if (!IS_NULL_OR_UNDEFINED(iterable)) {
var adder = this.add;
if (!IS_CALLABLE(adder)) {
throw %make_type_error(kPropertyNotFunction, adder, 'add', this);
}
for (var value of iterable) {
%_Call(adder, this, value);
}
}
}
// -------------------------------------------------------------------
%SetCode(GlobalWeakSet, WeakSetConstructor);
%FunctionSetLength(GlobalWeakSet, 0);
})
......@@ -2326,7 +2326,6 @@
'js/v8natives.js',
'js/array.js',
'js/typedarray.js',
'js/weak-collection.js',
'js/messages.js',
'js/spread.js',
'js/proxy.js',
......
......@@ -123,10 +123,26 @@ test(function() {
}, "Converting circular structure to JSON", TypeError);
// kConstructorNotFunction
test(function() {
Map();
}, "Constructor Map requires 'new'", TypeError);
test(function() {
Set();
}, "Constructor Set requires 'new'", TypeError);
test(function() {
Uint16Array(1);
}, "Constructor Uint16Array requires 'new'", TypeError);
test(function() {
WeakSet();
}, "Constructor WeakSet requires 'new'", TypeError);
test(function() {
WeakMap();
}, "Constructor WeakMap requires 'new'", TypeError);
// kDataViewNotArrayBuffer
test(function() {
new DataView(1);
......@@ -326,11 +342,26 @@ test(function() {
}, "Property description must be an object: 1", TypeError);
// kPropertyNotFunction
test(function() {
Map.prototype.set = 0;
new Map([[1, 2]]);
}, "'0' returned for property 'set' of object '#<Map>' is not a function", TypeError);
test(function() {
Set.prototype.add = 0;
new Set(1);
new Set([1]);
}, "'0' returned for property 'add' of object '#<Set>' is not a function", TypeError);
test(function() {
WeakMap.prototype.set = 0;
new WeakMap([[{}, 1]]);
}, "'0' returned for property 'set' of object '#<WeakMap>' is not a function", TypeError);
test(function() {
WeakSet.prototype.add = 0;
new WeakSet([{}]);
}, "'0' returned for property 'add' of object '#<WeakSet>' is not a function", TypeError);
// kProtoObjectOrNull
test(function() {
Object.setPrototypeOf({}, 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