Skip to content

Commit

Permalink
ES6: Add support for Map/Set forEach
Browse files Browse the repository at this point in the history
This implements MapIterator and SetIterator which matches
the same constructs in the ES6 spec. However, these 2
iterators are not exposed to user code yet. They are only
used internally to implement Map.prototype.forEach and
Set.prototype.forEach.

Each iterator has a reference to the OrderedHashTable where
it directly accesses the hash table's entries.

The OrderedHashTable has a reference to the newest iterator
and each iterator has a reference to the next and previous
iterator, effectively creating a double linked list.

When the OrderedHashTable is mutated (or replaced) all the
iterators are updated.

When the iterator iterates passed the end of the data table
it closes itself. Closed iterators no longer have a
reference to the OrderedHashTable and they are removed from
the double linked list. In the case of Map/Set forEach, we
manually call Close on the iterator in case an exception was
thrown so that the iterator never reached the end.

At this point the OrderedHashTable keeps all the non finished
iterators alive but since the only thing we currently expose
is forEach there are no unfinished iterators outside a forEach
call. Once we expose the iterators to user code we will need
to make the references from the OrderedHashTable to the
iterators weak and have some mechanism to close an iterator
when it is garbage collected.

BUG=1793, 2323
LOG=Y
R=adamk@chromium.org
TBR=mstarzinger@chromium.org

Review URL: https://codereview.chromium.org/238063009

Patch from Erik Arvidsson <arv@chromium.org>.

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@20857 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
  • Loading branch information
adamk@chromium.org committed Apr 17, 2014
1 parent df695e3 commit cb07c56
Show file tree
Hide file tree
Showing 22 changed files with 1,287 additions and 71 deletions.
2 changes: 1 addition & 1 deletion src/arm/full-codegen-arm.cc
Expand Up @@ -2278,7 +2278,7 @@ void FullCodeGenerator::EmitCreateIteratorResult(bool done) {
Label gc_required;
Label allocated;

Handle<Map> map(isolate()->native_context()->generator_result_map());
Handle<Map> map(isolate()->native_context()->iterator_result_map());

__ Allocate(map->instance_size(), r0, r2, r3, &gc_required, TAG_OBJECT);
__ jmp(&allocated);
Expand Down
2 changes: 1 addition & 1 deletion src/arm64/full-codegen-arm64.cc
Expand Up @@ -4706,7 +4706,7 @@ void FullCodeGenerator::EmitCreateIteratorResult(bool done) {
Label gc_required;
Label allocated;

Handle<Map> map(isolate()->native_context()->generator_result_map());
Handle<Map> map(isolate()->native_context()->iterator_result_map());

// Allocate and populate an object with this form: { value: VAL, done: DONE }

Expand Down
15 changes: 5 additions & 10 deletions src/array-iterator.js
Expand Up @@ -31,11 +31,6 @@
// in runtime.js:
// var $Array = global.Array;

var ARRAY_ITERATOR_KIND_KEYS = 1;
var ARRAY_ITERATOR_KIND_VALUES = 2;
var ARRAY_ITERATOR_KIND_ENTRIES = 3;
// The spec draft also has "sparse" but it is never used.

var arrayIteratorObjectSymbol = GLOBAL_PRIVATE("ArrayIterator#object");
var arrayIteratorNextIndexSymbol = GLOBAL_PRIVATE("ArrayIterator#next");
var arrayIterationKindSymbol = GLOBAL_PRIVATE("ArrayIterator#kind");
Expand Down Expand Up @@ -79,25 +74,25 @@ function ArrayIteratorNext() {

SET_PRIVATE(iterator, arrayIteratorNextIndexSymbol, index + 1);

if (itemKind == ARRAY_ITERATOR_KIND_VALUES)
if (itemKind == ITERATOR_KIND_VALUES)
return CreateIteratorResultObject(array[index], false);

if (itemKind == ARRAY_ITERATOR_KIND_ENTRIES)
if (itemKind == ITERATOR_KIND_ENTRIES)
return CreateIteratorResultObject([index, array[index]], false);

return CreateIteratorResultObject(index, false);
}

function ArrayEntries() {
return CreateArrayIterator(this, ARRAY_ITERATOR_KIND_ENTRIES);
return CreateArrayIterator(this, ITERATOR_KIND_ENTRIES);
}

function ArrayValues() {
return CreateArrayIterator(this, ARRAY_ITERATOR_KIND_VALUES);
return CreateArrayIterator(this, ITERATOR_KIND_VALUES);
}

function ArrayKeys() {
return CreateArrayIterator(this, ARRAY_ITERATOR_KIND_KEYS);
return CreateArrayIterator(this, ITERATOR_KIND_KEYS);
}

function SetUpArrayIterator() {
Expand Down
43 changes: 27 additions & 16 deletions src/bootstrapper.cc
Expand Up @@ -1297,6 +1297,16 @@ void Genesis::InitializeExperimentalGlobal() {
isolate()->initial_object_prototype(),
Builtins::kIllegal, true, true);
}
{ // -- S e t I t e r a t o r
Handle<Map> map = isolate()->factory()->NewMap(
JS_SET_ITERATOR_TYPE, JSSetIterator::kSize);
native_context()->set_set_iterator_map(*map);
}
{ // -- M a p I t e r a t o r
Handle<Map> map = isolate()->factory()->NewMap(
JS_MAP_ITERATOR_TYPE, JSMapIterator::kSize);
native_context()->set_map_iterator_map(*map);
}
}

if (FLAG_harmony_weak_collections) {
Expand Down Expand Up @@ -1351,37 +1361,38 @@ void Genesis::InitializeExperimentalGlobal() {
*generator_object_prototype);
native_context()->set_generator_object_prototype_map(
*generator_object_prototype_map);
}

if (FLAG_harmony_collections || FLAG_harmony_generators) {
// Collection forEach uses an iterator result object.
// Generators return iteraror result objects.

// Create a map for generator result objects.
ASSERT(object_function->initial_map()->inobject_properties() == 0);
STATIC_ASSERT(JSGeneratorObject::kResultPropertyCount == 2);
Handle<Map> generator_result_map = Map::Create(
Handle<JSFunction> object_function(native_context()->object_function());
ASSERT(object_function->initial_map()->inobject_properties() == 0);
Handle<Map> iterator_result_map = Map::Create(
object_function, JSGeneratorObject::kResultPropertyCount);
ASSERT(generator_result_map->inobject_properties() ==
ASSERT(iterator_result_map->inobject_properties() ==
JSGeneratorObject::kResultPropertyCount);
Map::EnsureDescriptorSlack(
generator_result_map, JSGeneratorObject::kResultPropertyCount);
iterator_result_map, JSGeneratorObject::kResultPropertyCount);

Handle<String> value_string = factory()->InternalizeOneByteString(
STATIC_ASCII_VECTOR("value"));
FieldDescriptor value_descr(value_string,
FieldDescriptor value_descr(isolate()->factory()->value_string(),
JSGeneratorObject::kResultValuePropertyIndex,
NONE,
Representation::Tagged());
generator_result_map->AppendDescriptor(&value_descr);
iterator_result_map->AppendDescriptor(&value_descr);

Handle<String> done_string = factory()->InternalizeOneByteString(
STATIC_ASCII_VECTOR("done"));
FieldDescriptor done_descr(done_string,
FieldDescriptor done_descr(isolate()->factory()->done_string(),
JSGeneratorObject::kResultDonePropertyIndex,
NONE,
Representation::Tagged());
generator_result_map->AppendDescriptor(&done_descr);
iterator_result_map->AppendDescriptor(&done_descr);

generator_result_map->set_unused_property_fields(0);
iterator_result_map->set_unused_property_fields(0);
ASSERT_EQ(JSGeneratorObject::kResultSize,
generator_result_map->instance_size());
native_context()->set_generator_result_map(*generator_result_map);
iterator_result_map->instance_size());
native_context()->set_iterator_result_map(*iterator_result_map);
}
}

Expand Down
60 changes: 54 additions & 6 deletions src/collection.js
Expand Up @@ -113,8 +113,29 @@ function SetClear() {
throw MakeTypeError('incompatible_method_receiver',
['Set.prototype.clear', this]);
}
// Replace the internal table with a new empty table.
%SetInitialize(this);
%SetClear(this);
}


function SetForEach(f, receiver) {
if (!IS_SET(this)) {
throw MakeTypeError('incompatible_method_receiver',
['Set.prototype.forEach', this]);
}

if (!IS_SPEC_FUNCTION(f)) {
throw MakeTypeError('called_non_callable', [f]);
}

var iterator = %SetCreateIterator(this, ITERATOR_KIND_VALUES);
var entry;
try {
while (!(entry = %SetIteratorNext(iterator)).done) {
%_CallFunction(receiver, entry.value, entry.value, this, f);
}
} finally {
%SetIteratorClose(iterator);
}
}


Expand All @@ -127,13 +148,16 @@ function SetUpSet() {
%FunctionSetPrototype($Set, new $Object());
%SetProperty($Set.prototype, "constructor", $Set, DONT_ENUM);

%FunctionSetLength(SetForEach, 1);

// Set up the non-enumerable functions on the Set prototype object.
InstallGetter($Set.prototype, "size", SetGetSize);
InstallFunctions($Set.prototype, DONT_ENUM, $Array(
"add", SetAdd,
"has", SetHas,
"delete", SetDelete,
"clear", SetClear
"clear", SetClear,
"forEach", SetForEach
));
}

Expand Down Expand Up @@ -202,8 +226,29 @@ function MapClear() {
throw MakeTypeError('incompatible_method_receiver',
['Map.prototype.clear', this]);
}
// Replace the internal table with a new empty table.
%MapInitialize(this);
%MapClear(this);
}


function MapForEach(f, receiver) {
if (!IS_MAP(this)) {
throw MakeTypeError('incompatible_method_receiver',
['Map.prototype.forEach', this]);
}

if (!IS_SPEC_FUNCTION(f)) {
throw MakeTypeError('called_non_callable', [f]);
}

var iterator = %MapCreateIterator(this, ITERATOR_KIND_ENTRIES);
var entry;
try {
while (!(entry = %MapIteratorNext(iterator)).done) {
%_CallFunction(receiver, entry.value[1], entry.value[0], this, f);
}
} finally {
%MapIteratorClose(iterator);
}
}


Expand All @@ -216,14 +261,17 @@ function SetUpMap() {
%FunctionSetPrototype($Map, new $Object());
%SetProperty($Map.prototype, "constructor", $Map, DONT_ENUM);

%FunctionSetLength(MapForEach, 1);

// Set up the non-enumerable functions on the Map prototype object.
InstallGetter($Map.prototype, "size", MapGetSize);
InstallFunctions($Map.prototype, DONT_ENUM, $Array(
"get", MapGet,
"set", MapSet,
"has", MapHas,
"delete", MapDelete,
"clear", MapClear
"clear", MapClear,
"forEach", MapForEach
));
}

Expand Down
8 changes: 6 additions & 2 deletions src/contexts.h
Expand Up @@ -191,7 +191,9 @@ enum BindingFlags {
V(STRICT_GENERATOR_FUNCTION_MAP_INDEX, Map, strict_generator_function_map) \
V(GENERATOR_OBJECT_PROTOTYPE_MAP_INDEX, Map, \
generator_object_prototype_map) \
V(GENERATOR_RESULT_MAP_INDEX, Map, generator_result_map)
V(ITERATOR_RESULT_MAP_INDEX, Map, iterator_result_map) \
V(MAP_ITERATOR_MAP_INDEX, Map, map_iterator_map) \
V(SET_ITERATOR_MAP_INDEX, Map, set_iterator_map)

// JSFunctions are pairs (context, function code), sometimes also called
// closures. A Context object is used to represent function contexts and
Expand Down Expand Up @@ -347,7 +349,9 @@ class Context: public FixedArray {
SLOPPY_GENERATOR_FUNCTION_MAP_INDEX,
STRICT_GENERATOR_FUNCTION_MAP_INDEX,
GENERATOR_OBJECT_PROTOTYPE_MAP_INDEX,
GENERATOR_RESULT_MAP_INDEX,
ITERATOR_RESULT_MAP_INDEX,
MAP_ITERATOR_MAP_INDEX,
SET_ITERATOR_MAP_INDEX,

// Properties from here are treated as weak references by the full GC.
// Scavenge treats them as strong references.
Expand Down
13 changes: 13 additions & 0 deletions src/factory.cc
Expand Up @@ -1326,6 +1326,18 @@ Handle<JSFunction> Factory::NewFunctionWithPrototype(Handle<String> name,
}


Handle<JSObject> Factory::NewIteratorResultObject(Handle<Object> value,
bool done) {
Handle<Map> map(isolate()->native_context()->iterator_result_map());
Handle<JSObject> result = NewJSObjectFromMap(map, NOT_TENURED, false);
result->InObjectPropertyAtPut(
JSGeneratorObject::kResultValuePropertyIndex, *value);
result->InObjectPropertyAtPut(
JSGeneratorObject::kResultDonePropertyIndex, *ToBoolean(done));
return result;
}


Handle<ScopeInfo> Factory::NewScopeInfo(int length) {
Handle<FixedArray> array = NewFixedArray(length, TENURED);
array->set_map_no_write_barrier(*scope_info_map());
Expand Down Expand Up @@ -2345,4 +2357,5 @@ Handle<Object> Factory::ToBoolean(bool value) {
return value ? true_value() : false_value();
}


} } // namespace v8::internal
2 changes: 2 additions & 0 deletions src/factory.h
Expand Up @@ -522,6 +522,8 @@ class Factory V8_FINAL {
Handle<Object> NewEvalError(const char* message,
Vector< Handle<Object> > args);

Handle<JSObject> NewIteratorResultObject(Handle<Object> value, bool done);

Handle<String> NumberToString(Handle<Object> number,
bool check_number_string_cache = true);

Expand Down
2 changes: 1 addition & 1 deletion src/ia32/full-codegen-ia32.cc
Expand Up @@ -2227,7 +2227,7 @@ void FullCodeGenerator::EmitCreateIteratorResult(bool done) {
Label gc_required;
Label allocated;

Handle<Map> map(isolate()->native_context()->generator_result_map());
Handle<Map> map(isolate()->native_context()->iterator_result_map());

__ Allocate(map->instance_size(), eax, ecx, edx, &gc_required, TAG_OBJECT);
__ jmp(&allocated);
Expand Down
5 changes: 5 additions & 0 deletions src/macros.py
Expand Up @@ -276,3 +276,8 @@
const PROPERTY_ATTRIBUTES_STRING = 8;
const PROPERTY_ATTRIBUTES_SYMBOLIC = 16;
const PROPERTY_ATTRIBUTES_PRIVATE_SYMBOL = 32;

# Use for keys, values and entries iterators.
const ITERATOR_KIND_KEYS = 1;
const ITERATOR_KIND_VALUES = 2;
const ITERATOR_KIND_ENTRIES = 3;
2 changes: 1 addition & 1 deletion src/mips/full-codegen-mips.cc
Expand Up @@ -2285,7 +2285,7 @@ void FullCodeGenerator::EmitCreateIteratorResult(bool done) {
Label gc_required;
Label allocated;

Handle<Map> map(isolate()->native_context()->generator_result_map());
Handle<Map> map(isolate()->native_context()->iterator_result_map());

__ Allocate(map->instance_size(), v0, a2, a3, &gc_required, TAG_OBJECT);
__ jmp(&allocated);
Expand Down
40 changes: 40 additions & 0 deletions src/objects-debug.cc
Expand Up @@ -170,6 +170,12 @@ void HeapObject::HeapObjectVerify() {
case JS_MAP_TYPE:
JSMap::cast(this)->JSMapVerify();
break;
case JS_SET_ITERATOR_TYPE:
JSSetIterator::cast(this)->JSSetIteratorVerify();
break;
case JS_MAP_ITERATOR_TYPE:
JSMapIterator::cast(this)->JSMapIteratorVerify();
break;
case JS_WEAK_MAP_TYPE:
JSWeakMap::cast(this)->JSWeakMapVerify();
break;
Expand Down Expand Up @@ -708,6 +714,7 @@ void JSSet::JSSetVerify() {
JSObjectVerify();
VerifyHeapPointer(table());
CHECK(table()->IsOrderedHashTable() || table()->IsUndefined());
// TODO(arv): Verify OrderedHashTable too.
}


Expand All @@ -716,6 +723,39 @@ void JSMap::JSMapVerify() {
JSObjectVerify();
VerifyHeapPointer(table());
CHECK(table()->IsOrderedHashTable() || table()->IsUndefined());
// TODO(arv): Verify OrderedHashTable too.
}


void JSSetIterator::JSSetIteratorVerify() {
CHECK(IsJSSetIterator());
JSObjectVerify();
VerifyHeapPointer(table());
CHECK(table()->IsOrderedHashTable() || table()->IsUndefined());
CHECK(index()->IsSmi());
CHECK(count()->IsSmi());
CHECK(kind()->IsSmi());
VerifyHeapPointer(next_iterator());
CHECK(next_iterator()->IsJSSetIterator() || next_iterator()->IsUndefined());
VerifyHeapPointer(table());
CHECK(previous_iterator()->IsJSSetIterator()
|| previous_iterator()->IsUndefined());
}


void JSMapIterator::JSMapIteratorVerify() {
CHECK(IsJSMapIterator());
JSObjectVerify();
VerifyHeapPointer(table());
CHECK(table()->IsOrderedHashTable() || table()->IsUndefined());
CHECK(index()->IsSmi());
CHECK(count()->IsSmi());
CHECK(kind()->IsSmi());
VerifyHeapPointer(next_iterator());
CHECK(next_iterator()->IsJSMapIterator() || next_iterator()->IsUndefined());
VerifyHeapPointer(table());
CHECK(previous_iterator()->IsJSMapIterator()
|| previous_iterator()->IsUndefined());
}


Expand Down

0 comments on commit cb07c56

Please sign in to comment.