From 3cd73ebc2f529645a120173c03a47500a4cbf002 Mon Sep 17 00:00:00 2001 From: "wingo@igalia.com" Date: Tue, 7 May 2013 08:46:42 +0000 Subject: [PATCH] Generators return boxed values Generators now box their return values in object literals of the form { value: VAL, done: DONE } where DONE is false for yield expressions, and true for return statements. BUG=v8:2355 TEST=mjsunit/harmony/generators-iteration R=mstarzinger@chromium.org Review URL: https://codereview.chromium.org/13870007 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@14563 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/arm/full-codegen-arm.cc | 69 +++++++++++++++++++++------ src/bootstrapper.cc | 34 ++++++++++++++ src/contexts.h | 2 + src/full-codegen.h | 5 ++ src/ia32/full-codegen-ia32.cc | 68 +++++++++++++++++++++------ src/objects.h | 11 +++++ src/x64/full-codegen-x64.cc | 70 ++++++++++++++++++++++------ test/mjsunit/harmony/generators-iteration.js | 25 ++++++++-- 8 files changed, 236 insertions(+), 48 deletions(-) diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index 0ef4be0..58c628b 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -1939,11 +1939,12 @@ void FullCodeGenerator::VisitYield(Yield* expr) { Label resume; __ CompareRoot(result_register(), Heap::kTheHoleValueRootIndex); __ b(ne, &resume); - __ pop(result_register()); if (expr->yield_kind() == Yield::SUSPEND) { - // TODO(wingo): Box into { value: VALUE, done: false }. + EmitReturnIteratorResult(false); + } else { + __ pop(result_register()); + EmitReturnSequence(); } - EmitReturnSequence(); __ bind(&resume); context()->Plug(result_register()); @@ -1955,18 +1956,7 @@ void FullCodeGenerator::VisitYield(Yield* expr) { __ mov(r1, Operand(Smi::FromInt(JSGeneratorObject::kGeneratorClosed))); __ str(r1, FieldMemOperand(result_register(), JSGeneratorObject::kContinuationOffset)); - __ pop(result_register()); - // TODO(wingo): Box into { value: VALUE, done: true }. - - // Exit all nested statements. - NestedStatement* current = nesting_stack_; - int stack_depth = 0; - int context_length = 0; - while (current != NULL) { - current = current->Exit(&stack_depth, &context_length); - } - __ Drop(stack_depth); - EmitReturnSequence(); + EmitReturnIteratorResult(true); break; } @@ -2074,6 +2064,55 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator, } +void FullCodeGenerator::EmitReturnIteratorResult(bool done) { + Label gc_required; + Label allocated; + + Handle map(isolate()->native_context()->generator_result_map()); + + __ Allocate(map->instance_size(), r0, r2, r3, &gc_required, TAG_OBJECT); + + __ bind(&allocated); + __ mov(r1, Operand(map)); + __ pop(r2); + __ mov(r3, Operand(isolate()->factory()->ToBoolean(done))); + __ mov(r4, Operand(isolate()->factory()->empty_fixed_array())); + ASSERT_EQ(map->instance_size(), 5 * kPointerSize); + __ str(r1, FieldMemOperand(r0, HeapObject::kMapOffset)); + __ str(r4, FieldMemOperand(r0, JSObject::kPropertiesOffset)); + __ str(r4, FieldMemOperand(r0, JSObject::kElementsOffset)); + __ str(r2, + FieldMemOperand(r0, JSGeneratorObject::kResultValuePropertyOffset)); + __ str(r3, + FieldMemOperand(r0, JSGeneratorObject::kResultDonePropertyOffset)); + + // Only the value field needs a write barrier, as the other values are in the + // root set. + __ RecordWriteField(r0, JSGeneratorObject::kResultValuePropertyOffset, + r2, r3, kLRHasBeenSaved, kDontSaveFPRegs); + + if (done) { + // Exit all nested statements. + NestedStatement* current = nesting_stack_; + int stack_depth = 0; + int context_length = 0; + while (current != NULL) { + current = current->Exit(&stack_depth, &context_length); + } + __ Drop(stack_depth); + } + + EmitReturnSequence(); + + __ bind(&gc_required); + __ Push(Smi::FromInt(map->instance_size())); + __ CallRuntime(Runtime::kAllocateInNewSpace, 1); + __ ldr(context_register(), + MemOperand(fp, StandardFrameConstants::kContextOffset)); + __ jmp(&allocated); +} + + void FullCodeGenerator::EmitNamedPropertyLoad(Property* prop) { SetSourcePosition(prop->position()); Literal* key = prop->key()->AsLiteral(); diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 08f9fce..27f9b7d 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1387,6 +1387,40 @@ void Genesis::InitializeExperimentalGlobal() { *generator_object_prototype); native_context()->set_generator_object_prototype_map( *generator_object_prototype_map); + + // Create a map for generator result objects. + ASSERT(object_map->inobject_properties() == 0); + STATIC_ASSERT(JSGeneratorObject::kResultPropertyCount == 2); + Handle generator_result_map = factory()->CopyMap(object_map, + JSGeneratorObject::kResultPropertyCount); + ASSERT(generator_result_map->inobject_properties() == + JSGeneratorObject::kResultPropertyCount); + + Handle descriptors = factory()->NewDescriptorArray(0, + JSGeneratorObject::kResultPropertyCount); + DescriptorArray::WhitenessWitness witness(*descriptors); + generator_result_map->set_instance_descriptors(*descriptors); + + Handle value_string = factory()->InternalizeOneByteString( + STATIC_ASCII_VECTOR("value")); + FieldDescriptor value_descr(*value_string, + JSGeneratorObject::kResultValuePropertyIndex, + NONE, + Representation::Tagged()); + generator_result_map->AppendDescriptor(&value_descr, witness); + + Handle done_string = factory()->InternalizeOneByteString( + STATIC_ASCII_VECTOR("done")); + FieldDescriptor done_descr(*done_string, + JSGeneratorObject::kResultDonePropertyIndex, + NONE, + Representation::Tagged()); + generator_result_map->AppendDescriptor(&done_descr, witness); + + generator_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); } } diff --git a/src/contexts.h b/src/contexts.h index 7a877f8..434b274 100644 --- a/src/contexts.h +++ b/src/contexts.h @@ -180,6 +180,7 @@ enum BindingFlags { strict_mode_generator_function_map) \ V(GENERATOR_OBJECT_PROTOTYPE_MAP_INDEX, Map, \ generator_object_prototype_map) \ + V(GENERATOR_RESULT_MAP_INDEX, Map, generator_result_map) \ V(RANDOM_SEED_INDEX, ByteArray, random_seed) // JSFunctions are pairs (context, function code), sometimes also called @@ -323,6 +324,7 @@ class Context: public FixedArray { GENERATOR_FUNCTION_MAP_INDEX, STRICT_MODE_GENERATOR_FUNCTION_MAP_INDEX, GENERATOR_OBJECT_PROTOTYPE_MAP_INDEX, + GENERATOR_RESULT_MAP_INDEX, RANDOM_SEED_INDEX, // Properties from here are treated as weak references by the full GC. diff --git a/src/full-codegen.h b/src/full-codegen.h index 3734ae5..32242b2 100644 --- a/src/full-codegen.h +++ b/src/full-codegen.h @@ -410,6 +410,11 @@ class FullCodeGenerator: public AstVisitor { // this has to be a separate pass _before_ populating or executing any module. void AllocateModules(ZoneList* declarations); + // Generator code to return a fresh iterator result object. The "value" + // property is set to a value popped from the stack, and "done" is set + // according to the argument. + void EmitReturnIteratorResult(bool done); + // Try to perform a comparison as a fast inlined literal compare if // the operands allow it. Returns true if the compare operations // has been matched and all code generated; false otherwise. diff --git a/src/ia32/full-codegen-ia32.cc b/src/ia32/full-codegen-ia32.cc index f71a76d..3b3908d 100644 --- a/src/ia32/full-codegen-ia32.cc +++ b/src/ia32/full-codegen-ia32.cc @@ -1900,11 +1900,12 @@ void FullCodeGenerator::VisitYield(Yield* expr) { Label resume; __ CompareRoot(result_register(), Heap::kTheHoleValueRootIndex); __ j(not_equal, &resume); - __ pop(result_register()); if (expr->yield_kind() == Yield::SUSPEND) { - // TODO(wingo): Box into { value: VALUE, done: false }. + EmitReturnIteratorResult(false); + } else { + __ pop(result_register()); + EmitReturnSequence(); } - EmitReturnSequence(); __ bind(&resume); context()->Plug(result_register()); @@ -1916,18 +1917,7 @@ void FullCodeGenerator::VisitYield(Yield* expr) { __ mov(FieldOperand(result_register(), JSGeneratorObject::kContinuationOffset), Immediate(Smi::FromInt(JSGeneratorObject::kGeneratorClosed))); - __ pop(result_register()); - // TODO(wingo): Box into { value: VALUE, done: true }. - - // Exit all nested statements. - NestedStatement* current = nesting_stack_; - int stack_depth = 0; - int context_length = 0; - while (current != NULL) { - current = current->Exit(&stack_depth, &context_length); - } - __ Drop(stack_depth); - EmitReturnSequence(); + EmitReturnIteratorResult(true); break; } @@ -2033,6 +2023,54 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator, } +void FullCodeGenerator::EmitReturnIteratorResult(bool done) { + Label gc_required; + Label allocated; + + Handle map(isolate()->native_context()->generator_result_map()); + + __ Allocate(map->instance_size(), eax, ecx, edx, &gc_required, TAG_OBJECT); + + __ bind(&allocated); + __ mov(ebx, map); + __ pop(ecx); + __ mov(edx, isolate()->factory()->ToBoolean(done)); + ASSERT_EQ(map->instance_size(), 5 * kPointerSize); + __ mov(FieldOperand(eax, HeapObject::kMapOffset), ebx); + __ mov(FieldOperand(eax, JSObject::kPropertiesOffset), + isolate()->factory()->empty_fixed_array()); + __ mov(FieldOperand(eax, JSObject::kElementsOffset), + isolate()->factory()->empty_fixed_array()); + __ mov(FieldOperand(eax, JSGeneratorObject::kResultValuePropertyOffset), ecx); + __ mov(FieldOperand(eax, JSGeneratorObject::kResultDonePropertyOffset), edx); + + // Only the value field needs a write barrier, as the other values are in the + // root set. + __ RecordWriteField(eax, JSGeneratorObject::kResultValuePropertyOffset, + ecx, edx, kDontSaveFPRegs); + + if (done) { + // Exit all nested statements. + NestedStatement* current = nesting_stack_; + int stack_depth = 0; + int context_length = 0; + while (current != NULL) { + current = current->Exit(&stack_depth, &context_length); + } + __ Drop(stack_depth); + } + + EmitReturnSequence(); + + __ bind(&gc_required); + __ Push(Smi::FromInt(map->instance_size())); + __ CallRuntime(Runtime::kAllocateInNewSpace, 1); + __ mov(context_register(), + Operand(ebp, StandardFrameConstants::kContextOffset)); + __ jmp(&allocated); +} + + void FullCodeGenerator::EmitNamedPropertyLoad(Property* prop) { SetSourcePosition(prop->position()); Literal* key = prop->key()->AsLiteral(); diff --git a/src/objects.h b/src/objects.h index 00f9927..d83d291 100644 --- a/src/objects.h +++ b/src/objects.h @@ -6435,6 +6435,17 @@ class JSGeneratorObject: public JSObject { // Resume mode, for use by runtime functions. enum ResumeMode { SEND, THROW }; + // Yielding from a generator returns an object with the following inobject + // properties. See Context::generator_result_map() for the map. + static const int kResultValuePropertyIndex = 0; + static const int kResultDonePropertyIndex = 1; + static const int kResultPropertyCount = 2; + + static const int kResultValuePropertyOffset = JSObject::kHeaderSize; + static const int kResultDonePropertyOffset = + kResultValuePropertyOffset + kPointerSize; + static const int kResultSize = kResultDonePropertyOffset + kPointerSize; + private: DISALLOW_IMPLICIT_CONSTRUCTORS(JSGeneratorObject); }; diff --git a/src/x64/full-codegen-x64.cc b/src/x64/full-codegen-x64.cc index a20d468..745de97 100644 --- a/src/x64/full-codegen-x64.cc +++ b/src/x64/full-codegen-x64.cc @@ -1924,11 +1924,12 @@ void FullCodeGenerator::VisitYield(Yield* expr) { Label resume; __ CompareRoot(result_register(), Heap::kTheHoleValueRootIndex); __ j(not_equal, &resume); - __ pop(result_register()); if (expr->yield_kind() == Yield::SUSPEND) { - // TODO(wingo): Box into { value: VALUE, done: false }. + EmitReturnIteratorResult(false); + } else { + __ pop(result_register()); + EmitReturnSequence(); } - EmitReturnSequence(); __ bind(&resume); context()->Plug(result_register()); @@ -1940,18 +1941,7 @@ void FullCodeGenerator::VisitYield(Yield* expr) { __ Move(FieldOperand(result_register(), JSGeneratorObject::kContinuationOffset), Smi::FromInt(JSGeneratorObject::kGeneratorClosed)); - __ pop(result_register()); - // TODO(wingo): Box into { value: VALUE, done: true }. - - // Exit all nested statements. - NestedStatement* current = nesting_stack_; - int stack_depth = 0; - int context_length = 0; - while (current != NULL) { - current = current->Exit(&stack_depth, &context_length); - } - __ Drop(stack_depth); - EmitReturnSequence(); + EmitReturnIteratorResult(true); break; } @@ -2058,6 +2048,56 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator, } +void FullCodeGenerator::EmitReturnIteratorResult(bool done) { + Label gc_required; + Label allocated; + + Handle map(isolate()->native_context()->generator_result_map()); + + __ Allocate(map->instance_size(), rax, rcx, rdx, &gc_required, TAG_OBJECT); + + __ bind(&allocated); + __ Move(rbx, map); + __ pop(rcx); + __ Move(rdx, isolate()->factory()->ToBoolean(done)); + ASSERT_EQ(map->instance_size(), 5 * kPointerSize); + __ movq(FieldOperand(rax, HeapObject::kMapOffset), rbx); + __ Move(FieldOperand(rax, JSObject::kPropertiesOffset), + isolate()->factory()->empty_fixed_array()); + __ Move(FieldOperand(rax, JSObject::kElementsOffset), + isolate()->factory()->empty_fixed_array()); + __ movq(FieldOperand(rax, JSGeneratorObject::kResultValuePropertyOffset), + rcx); + __ movq(FieldOperand(rax, JSGeneratorObject::kResultDonePropertyOffset), + rdx); + + // Only the value field needs a write barrier, as the other values are in the + // root set. + __ RecordWriteField(rax, JSGeneratorObject::kResultValuePropertyOffset, + rcx, rdx, kDontSaveFPRegs); + + if (done) { + // Exit all nested statements. + NestedStatement* current = nesting_stack_; + int stack_depth = 0; + int context_length = 0; + while (current != NULL) { + current = current->Exit(&stack_depth, &context_length); + } + __ Drop(stack_depth); + } + + EmitReturnSequence(); + + __ bind(&gc_required); + __ Push(Smi::FromInt(map->instance_size())); + __ CallRuntime(Runtime::kAllocateInNewSpace, 1); + __ movq(context_register(), + Operand(rbp, StandardFrameConstants::kContextOffset)); + __ jmp(&allocated); +} + + void FullCodeGenerator::EmitNamedPropertyLoad(Property* prop) { SetSourcePosition(prop->position()); Literal* key = prop->key()->AsLiteral(); diff --git a/test/mjsunit/harmony/generators-iteration.js b/test/mjsunit/harmony/generators-iteration.js index ba0ae10..d2b695b 100644 --- a/test/mjsunit/harmony/generators-iteration.js +++ b/test/mjsunit/harmony/generators-iteration.js @@ -31,19 +31,36 @@ var GeneratorFunction = (function*(){yield 1;}).__proto__.constructor; +function TestGeneratorResultPrototype() { + function* g() { yield 1; } + var iter = g(); + var result = iter.next(); + + assertSame(Object.prototype, Object.getPrototypeOf(result)); + property_names = Object.getOwnPropertyNames(result); + property_names.sort(); + assertEquals(["done", "value"], property_names); + assertEquals({ value: 1, done: false }, result); +} +TestGeneratorResultPrototype() + function TestGenerator(g, expected_values_for_next, send_val, expected_values_for_send) { function testNext(thunk) { var iter = thunk(); for (var i = 0; i < expected_values_for_next.length; i++) { - assertEquals(expected_values_for_next[i], iter.next()); + assertEquals({ value: expected_values_for_next[i], + done: i == expected_values_for_next.length - 1 }, + iter.next()); } assertThrows(function() { iter.next(); }, Error); } function testSend(thunk) { var iter = thunk(); for (var i = 0; i < expected_values_for_send.length; i++) { - assertEquals(expected_values_for_send[i], iter.send(send_val)); + assertEquals({ value: expected_values_for_send[i], + done: i == expected_values_for_send.length - 1 }, + iter.send(send_val)); } assertThrows(function() { iter.send(send_val); }, Error); } @@ -51,7 +68,9 @@ function TestGenerator(g, expected_values_for_next, for (var i = 0; i < expected_values_for_next.length; i++) { var iter = thunk(); for (var j = 0; j < i; j++) { - assertEquals(expected_values_for_next[j], iter.next()); + assertEquals({ value: expected_values_for_next[j], + done: j == expected_values_for_next.length - 1 }, + iter.next()); } function Sentinel() {} assertThrows(function () { iter.throw(new Sentinel); }, Sentinel); -- 2.7.4