From: keuchel@chromium.org Date: Tue, 30 Aug 2011 11:23:57 +0000 (+0000) Subject: Temporal dead zone behaviour for let bindings. X-Git-Tag: upstream/4.7.83~18615 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=42388ad5c7144502525a08dccfc32370a2432714;p=platform%2Fupstream%2Fv8.git Temporal dead zone behaviour for let bindings. BUG= TEST=mjsunit/harmony/block-let-semantics.js Review URL: http://codereview.chromium.org/7671042 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@9070 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index dd3b189..408946e 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -697,12 +697,12 @@ void FullCodeGenerator::EmitDeclaration(Variable* variable, switch (slot->type()) { case Slot::PARAMETER: case Slot::LOCAL: - if (mode == Variable::CONST) { - __ LoadRoot(ip, Heap::kTheHoleValueRootIndex); - __ str(ip, MemOperand(fp, SlotOffset(slot))); - } else if (function != NULL) { + if (function != NULL) { VisitForAccumulatorValue(function); __ str(result_register(), MemOperand(fp, SlotOffset(slot))); + } else if (mode == Variable::CONST || mode == Variable::LET) { + __ LoadRoot(ip, Heap::kTheHoleValueRootIndex); + __ str(ip, MemOperand(fp, SlotOffset(slot))); } break; @@ -721,17 +721,17 @@ void FullCodeGenerator::EmitDeclaration(Variable* variable, __ CompareRoot(r1, Heap::kCatchContextMapRootIndex); __ Check(ne, "Declaration in catch context."); } - if (mode == Variable::CONST) { - __ LoadRoot(ip, Heap::kTheHoleValueRootIndex); - __ str(ip, ContextOperand(cp, slot->index())); - // No write barrier since the_hole_value is in old space. - } else if (function != NULL) { + if (function != NULL) { VisitForAccumulatorValue(function); __ str(result_register(), ContextOperand(cp, slot->index())); int offset = Context::SlotOffset(slot->index()); // We know that we have written a function, which is not a smi. __ mov(r1, Operand(cp)); __ RecordWrite(r1, Operand(offset), r2, result_register()); + } else if (mode == Variable::CONST || mode == Variable::LET) { + __ LoadRoot(ip, Heap::kTheHoleValueRootIndex); + __ str(ip, ContextOperand(cp, slot->index())); + // No write barrier since the_hole_value is in old space. } break; @@ -747,13 +747,13 @@ void FullCodeGenerator::EmitDeclaration(Variable* variable, // Note: For variables we must not push an initial value (such as // 'undefined') because we may have a (legal) redeclaration and we // must not destroy the current value. - if (mode == Variable::CONST) { - __ LoadRoot(r0, Heap::kTheHoleValueRootIndex); - __ Push(cp, r2, r1, r0); - } else if (function != NULL) { + if (function != NULL) { __ Push(cp, r2, r1); // Push initial value for function declaration. VisitForStackValue(function); + } else if (mode == Variable::CONST || mode == Variable::LET) { + __ LoadRoot(r0, Heap::kTheHoleValueRootIndex); + __ Push(cp, r2, r1, r0); } else { __ mov(r0, Operand(Smi::FromInt(0))); // No initial value! __ Push(cp, r2, r1, r0); @@ -1279,6 +1279,20 @@ void FullCodeGenerator::EmitVariableLoad(VariableProxy* proxy) { __ cmp(r0, ip); __ LoadRoot(r0, Heap::kUndefinedValueRootIndex, eq); context()->Plug(r0); + } else if (var->mode() == Variable::LET) { + // Let bindings may be the hole value if they have not been initialized. + // Throw a type error in this case. + Label done; + MemOperand slot_operand = EmitSlotSearch(slot, r0); + __ ldr(r0, slot_operand); + __ LoadRoot(ip, Heap::kTheHoleValueRootIndex); + __ cmp(r0, ip); + __ b(ne, &done); + __ mov(r0, Operand(var->name())); + __ push(r0); + __ CallRuntime(Runtime::kThrowReferenceError, 1); + __ bind(&done); + context()->Plug(r0); } else { context()->Plug(slot); } @@ -1859,6 +1873,59 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var, } __ bind(&skip); + } else if (var->mode() == Variable::LET && op != Token::INIT_LET) { + // Perform the assignment for non-const variables. Const assignments + // are simply skipped. + Slot* slot = var->AsSlot(); + switch (slot->type()) { + case Slot::PARAMETER: + case Slot::LOCAL: { + Label assign; + // Check for an initialized let binding. + __ ldr(r1, MemOperand(fp, SlotOffset(slot))); + __ LoadRoot(ip, Heap::kTheHoleValueRootIndex); + __ cmp(r1, ip); + __ b(ne, &assign); + __ mov(r1, Operand(var->name())); + __ push(r1); + __ CallRuntime(Runtime::kThrowReferenceError, 1); + // Perform the assignment. + __ bind(&assign); + __ str(result_register(), MemOperand(fp, SlotOffset(slot))); + break; + } + case Slot::CONTEXT: { + // Let variables may be the hole value if they have not been + // initialized. Throw a type error in this case. + Label assign; + MemOperand target = EmitSlotSearch(slot, r1); + // Check for an initialized let binding. + __ ldr(r3, target); + __ LoadRoot(ip, Heap::kTheHoleValueRootIndex); + __ cmp(r3, ip); + __ b(ne, &assign); + __ mov(r3, Operand(var->name())); + __ push(r3); + __ CallRuntime(Runtime::kThrowReferenceError, 1); + // Perform the assignment. + __ bind(&assign); + __ str(result_register(), target); + // RecordWrite may destroy all its register arguments. + __ mov(r3, result_register()); + int offset = Context::SlotOffset(slot->index()); + __ RecordWrite(r1, Operand(offset), r2, r3); + break; + } + case Slot::LOOKUP: + // Call the runtime for the assignment. + __ push(r0); // Value. + __ mov(r1, Operand(slot->var()->name())); + __ mov(r0, Operand(Smi::FromInt(strict_mode_flag()))); + __ Push(cp, r1, r0); // Context, name, strict mode. + __ CallRuntime(Runtime::kStoreContextSlot, 4); + break; + } + } else if (var->mode() != Variable::CONST) { // Perform the assignment for non-const variables. Const assignments // are simply skipped. diff --git a/src/contexts.cc b/src/contexts.cc index c0e7242..4f93abd 100644 --- a/src/contexts.cc +++ b/src/contexts.cc @@ -87,13 +87,15 @@ void Context::set_global_proxy(JSObject* object) { Handle Context::Lookup(Handle name, ContextLookupFlags flags, int* index_, - PropertyAttributes* attributes) { + PropertyAttributes* attributes, + BindingFlags* binding_flags) { Isolate* isolate = GetIsolate(); Handle context(this, isolate); bool follow_context_chain = (flags & FOLLOW_CONTEXT_CHAIN) != 0; *index_ = -1; *attributes = ABSENT; + *binding_flags = MISSING_BINDING; if (FLAG_trace_contexts) { PrintF("Context::Lookup("); @@ -118,6 +120,7 @@ Handle Context::Lookup(Handle name, } *index_ = Context::THROWN_OBJECT_INDEX; *attributes = NONE; + *binding_flags = MUTABLE_IS_INITIALIZED; return context; } } else { @@ -180,11 +183,16 @@ Handle Context::Lookup(Handle name, switch (mode) { case Variable::INTERNAL: // Fall through. case Variable::VAR: + *attributes = NONE; + *binding_flags = MUTABLE_IS_INITIALIZED; + break; case Variable::LET: *attributes = NONE; + *binding_flags = MUTABLE_CHECK_INITIALIZED; break; case Variable::CONST: *attributes = READ_ONLY; + *binding_flags = IMMUTABLE_CHECK_INITIALIZED; break; case Variable::DYNAMIC: case Variable::DYNAMIC_GLOBAL: @@ -207,6 +215,7 @@ Handle Context::Lookup(Handle name, } *index_ = index; *attributes = READ_ONLY; + *binding_flags = IMMUTABLE_IS_INITIALIZED; return context; } } diff --git a/src/contexts.h b/src/contexts.h index 3d9e7f4..505f86c 100644 --- a/src/contexts.h +++ b/src/contexts.h @@ -44,6 +44,30 @@ enum ContextLookupFlags { }; +// ES5 10.2 defines lexical environments with mutable and immutable bindings. +// Immutable bindings have two states, initialized and uninitialized, and +// their state is changed by the InitializeImmutableBinding method. +// +// The harmony proposal for block scoped bindings also introduces the +// uninitialized state for mutable bindings. A 'let' declared variable +// is a mutable binding that is created uninitalized upon activation of its +// lexical environment and it is initialized when evaluating its declaration +// statement. Var declared variables are mutable bindings that are +// immediately initialized upon creation. The BindingFlags enum represents +// information if a binding has definitely been initialized. 'const' declared +// variables are created as uninitialized immutable bindings. + +// In harmony mode accessing an uninitialized binding produces a reference +// error. +enum BindingFlags { + MUTABLE_IS_INITIALIZED, + MUTABLE_CHECK_INITIALIZED, + IMMUTABLE_IS_INITIALIZED, + IMMUTABLE_CHECK_INITIALIZED, + MISSING_BINDING +}; + + // Heap-allocated activation contexts. // // Contexts are implemented as FixedArray objects; the Context @@ -351,8 +375,11 @@ class Context: public FixedArray { // 4) index_ < 0 && result.is_null(): // there was no context found with the corresponding property. // attributes == ABSENT. - Handle Lookup(Handle name, ContextLookupFlags flags, - int* index_, PropertyAttributes* attributes); + Handle Lookup(Handle name, + ContextLookupFlags flags, + int* index_, + PropertyAttributes* attributes, + BindingFlags* binding_flags); // Determine if a local variable with the given name exists in a // context. Do not consider context extension objects. This is diff --git a/src/heap.cc b/src/heap.cc index 90d0e11..84909d5 100644 --- a/src/heap.cc +++ b/src/heap.cc @@ -4085,10 +4085,9 @@ MaybeObject* Heap::AllocateBlockContext(JSFunction* function, SerializedScopeInfo* scope_info) { Object* result; { MaybeObject* maybe_result = - AllocateFixedArray(scope_info->NumberOfContextSlots()); + AllocateFixedArrayWithHoles(scope_info->NumberOfContextSlots()); if (!maybe_result->ToObject(&result)) return maybe_result; } - // TODO(keuchel): properly initialize context slots. Context* context = reinterpret_cast(result); context->set_map(block_context_map()); context->set_closure(function); diff --git a/src/hydrogen.cc b/src/hydrogen.cc index dd3a591..55bf2dc 100644 --- a/src/hydrogen.cc +++ b/src/hydrogen.cc @@ -3122,6 +3122,8 @@ void HGraphBuilder::VisitVariableProxy(VariableProxy* expr) { Variable* variable = expr->AsVariable(); if (variable == NULL) { return Bailout("reference to rewritten variable"); + } else if (variable->mode() == Variable::LET) { + return Bailout("reference to let variable"); } else if (variable->IsStackAllocated()) { HValue* value = environment()->Lookup(variable); if (variable->mode() == Variable::CONST && @@ -3587,8 +3589,9 @@ void HGraphBuilder::HandleCompoundAssignment(Assignment* expr) { BinaryOperation* operation = expr->binary_operation(); if (var != NULL) { - if (var->mode() == Variable::CONST) { - return Bailout("unsupported const compound assignment"); + if (var->mode() == Variable::CONST || + var->mode() == Variable::LET) { + return Bailout("unsupported let or const compound assignment"); } CHECK_ALIVE(VisitForValue(operation)); @@ -3731,6 +3734,8 @@ void HGraphBuilder::VisitAssignment(Assignment* expr) { // variables (e.g. initialization inside a loop). HValue* old_value = environment()->Lookup(var); AddInstruction(new HUseConst(old_value)); + } else if (var->mode() == Variable::LET) { + return Bailout("unsupported assignment to let"); } if (proxy->IsArguments()) return Bailout("assignment to arguments"); @@ -5804,7 +5809,9 @@ void HGraphBuilder::VisitThisFunction(ThisFunction* expr) { void HGraphBuilder::VisitDeclaration(Declaration* decl) { // We support only declarations that do not require code generation. Variable* var = decl->proxy()->var(); - if (!var->IsStackAllocated() || decl->fun() != NULL) { + if (!var->IsStackAllocated() || + decl->fun() != NULL || + decl->mode() == Variable::LET) { return Bailout("unsupported declaration"); } diff --git a/src/ia32/full-codegen-ia32.cc b/src/ia32/full-codegen-ia32.cc index 43f733f..52e33d6 100644 --- a/src/ia32/full-codegen-ia32.cc +++ b/src/ia32/full-codegen-ia32.cc @@ -693,12 +693,12 @@ void FullCodeGenerator::EmitDeclaration(Variable* variable, switch (slot->type()) { case Slot::PARAMETER: case Slot::LOCAL: - if (mode == Variable::CONST) { - __ mov(Operand(ebp, SlotOffset(slot)), - Immediate(isolate()->factory()->the_hole_value())); - } else if (function != NULL) { + if (function != NULL) { VisitForAccumulatorValue(function); __ mov(Operand(ebp, SlotOffset(slot)), result_register()); + } else if (mode == Variable::CONST || mode == Variable::LET) { + __ mov(Operand(ebp, SlotOffset(slot)), + Immediate(isolate()->factory()->the_hole_value())); } break; @@ -717,16 +717,16 @@ void FullCodeGenerator::EmitDeclaration(Variable* variable, __ cmp(ebx, isolate()->factory()->catch_context_map()); __ Check(not_equal, "Declaration in catch context."); } - if (mode == Variable::CONST) { - __ mov(ContextOperand(esi, slot->index()), - Immediate(isolate()->factory()->the_hole_value())); - // No write barrier since the hole value is in old space. - } else if (function != NULL) { + if (function != NULL) { VisitForAccumulatorValue(function); __ mov(ContextOperand(esi, slot->index()), result_register()); int offset = Context::SlotOffset(slot->index()); __ mov(ebx, esi); __ RecordWrite(ebx, offset, result_register(), ecx); + } else if (mode == Variable::CONST || mode == Variable::LET) { + __ mov(ContextOperand(esi, slot->index()), + Immediate(isolate()->factory()->the_hole_value())); + // No write barrier since the hole value is in old space. } break; @@ -744,11 +744,11 @@ void FullCodeGenerator::EmitDeclaration(Variable* variable, // 'undefined') because we may have a (legal) redeclaration and we // must not destroy the current value. increment_stack_height(3); - if (mode == Variable::CONST) { + if (function != NULL) { + VisitForStackValue(function); + } else if (mode == Variable::CONST || mode == Variable::LET) { __ push(Immediate(isolate()->factory()->the_hole_value())); increment_stack_height(); - } else if (function != NULL) { - VisitForStackValue(function); } else { __ push(Immediate(Smi::FromInt(0))); // No initial value! increment_stack_height(); @@ -1268,6 +1268,18 @@ void FullCodeGenerator::EmitVariableLoad(VariableProxy* proxy) { __ mov(eax, isolate()->factory()->undefined_value()); __ bind(&done); context()->Plug(eax); + } else if (var->mode() == Variable::LET) { + // Let bindings may be the hole value if they have not been initialized. + // Throw a type error in this case. + Label done; + MemOperand slot_operand = EmitSlotSearch(slot, eax); + __ mov(eax, slot_operand); + __ cmp(eax, isolate()->factory()->the_hole_value()); + __ j(not_equal, &done, Label::kNear); + __ push(Immediate(var->name())); + __ CallRuntime(Runtime::kThrowReferenceError, 1); + __ bind(&done); + context()->Plug(eax); } else { context()->Plug(slot); } @@ -1855,6 +1867,57 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var, } __ bind(&skip); + } else if (var->mode() == Variable::LET && op != Token::INIT_LET) { + // Perform the assignment for non-const variables. Const assignments + // are simply skipped. + Slot* slot = var->AsSlot(); + switch (slot->type()) { + case Slot::PARAMETER: + case Slot::LOCAL: { + Label assign; + // Check for an initialized let binding. + __ mov(edx, Operand(ebp, SlotOffset(slot))); + __ cmp(edx, isolate()->factory()->the_hole_value()); + __ j(not_equal, &assign); + __ push(Immediate(var->name())); + __ CallRuntime(Runtime::kThrowReferenceError, 1); + // Perform the assignment. + __ bind(&assign); + __ mov(Operand(ebp, SlotOffset(slot)), eax); + break; + } + + case Slot::CONTEXT: { + // Let variables may be the hole value if they have not been + // initialized. Throw a type error in this case. + Label assign; + MemOperand target = EmitSlotSearch(slot, ecx); + // Check for an initialized let binding. + __ mov(edx, target); + __ cmp(edx, isolate()->factory()->the_hole_value()); + __ j(not_equal, &assign, Label::kNear); + __ push(Immediate(var->name())); + __ CallRuntime(Runtime::kThrowReferenceError, 1); + // Perform the assignment. + __ bind(&assign); + __ mov(target, eax); + // The value of the assignment is in eax. RecordWrite clobbers its + // register arguments. + __ mov(edx, eax); + int offset = Context::SlotOffset(slot->index()); + __ RecordWrite(ecx, offset, edx, ebx); + break; + } + + case Slot::LOOKUP: + // Call the runtime for the assignment. + __ push(eax); // Value. + __ push(esi); // Context. + __ push(Immediate(var->name())); + __ push(Immediate(Smi::FromInt(strict_mode_flag()))); + __ CallRuntime(Runtime::kStoreContextSlot, 4); + break; + } } else if (var->mode() != Variable::CONST) { // Perform the assignment for non-const variables. Const assignments // are simply skipped. diff --git a/src/parser.cc b/src/parser.cc index 844dd70..615c6ea 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -1609,7 +1609,13 @@ Block* Parser::ParseVariableDeclarations(VariableDeclarationContext var_context, // ('var' | 'const') (Identifier ('=' AssignmentExpression)?)+[','] Variable::Mode mode = Variable::VAR; + // True if the binding needs initialization. 'let' and 'const' declared + // bindings are created uninitialized by their declaration nodes and + // need initialization. 'var' declared bindings are always initialized + // immediately by their declaration nodes. + bool needs_init = false; bool is_const = false; + Token::Value init_op = Token::INIT_VAR; if (peek() == Token::VAR) { Consume(Token::VAR); } else if (peek() == Token::CONST) { @@ -1621,6 +1627,8 @@ Block* Parser::ParseVariableDeclarations(VariableDeclarationContext var_context, } mode = Variable::CONST; is_const = true; + needs_init = true; + init_op = Token::INIT_CONST; } else if (peek() == Token::LET) { Consume(Token::LET); if (var_context != kSourceElement && @@ -1631,6 +1639,8 @@ Block* Parser::ParseVariableDeclarations(VariableDeclarationContext var_context, return NULL; } mode = Variable::LET; + needs_init = true; + init_op = Token::INIT_LET; } else { UNREACHABLE(); // by current callers } @@ -1732,9 +1742,8 @@ Block* Parser::ParseVariableDeclarations(VariableDeclarationContext var_context, } } - // Make sure that 'const c' actually initializes 'c' to undefined - // even though it seems like a stupid thing to do. - if (value == NULL && is_const) { + // Make sure that 'const x' and 'let x' initialize 'x' to undefined. + if (value == NULL && needs_init) { value = GetLiteralUndefined(); } @@ -1822,12 +1831,11 @@ Block* Parser::ParseVariableDeclarations(VariableDeclarationContext var_context, // for constant lookups is always the function context, while it is // the top context for variables). Sigh... if (value != NULL) { - Token::Value op = (is_const ? Token::INIT_CONST : Token::INIT_VAR); bool in_with = is_const ? false : inside_with(); VariableProxy* proxy = initialization_scope->NewUnresolved(name, in_with); Assignment* assignment = - new(zone()) Assignment(isolate(), op, proxy, value, position); + new(zone()) Assignment(isolate(), init_op, proxy, value, position); if (block) { block->AddStatement(new(zone()) ExpressionStatement(assignment)); } diff --git a/src/runtime.cc b/src/runtime.cc index 802fd68..9d3bb1d 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -1306,8 +1306,9 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_DeclareContextSlot) { int index; PropertyAttributes attributes; ContextLookupFlags flags = DONT_FOLLOW_CHAINS; + BindingFlags binding_flags; Handle holder = - context->Lookup(name, flags, &index, &attributes); + context->Lookup(name, flags, &index, &attributes, &binding_flags); if (attributes != ABSENT) { // The name was declared before; check for conflicting @@ -1594,8 +1595,9 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_InitializeConstContextSlot) { int index; PropertyAttributes attributes; ContextLookupFlags flags = FOLLOW_CHAINS; + BindingFlags binding_flags; Handle holder = - context->Lookup(name, flags, &index, &attributes); + context->Lookup(name, flags, &index, &attributes, &binding_flags); // In most situations, the property introduced by the const // declaration should be present in the context extension object. @@ -8386,7 +8388,12 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_DeleteContextSlot) { int index; PropertyAttributes attributes; ContextLookupFlags flags = FOLLOW_CHAINS; - Handle holder = context->Lookup(name, flags, &index, &attributes); + BindingFlags binding_flags; + Handle holder = context->Lookup(name, + flags, + &index, + &attributes, + &binding_flags); // If the slot was not found the result is true. if (holder.is_null()) { @@ -8488,7 +8495,12 @@ static ObjectPair LoadContextSlotHelper(Arguments args, int index; PropertyAttributes attributes; ContextLookupFlags flags = FOLLOW_CHAINS; - Handle holder = context->Lookup(name, flags, &index, &attributes); + BindingFlags binding_flags; + Handle holder = context->Lookup(name, + flags, + &index, + &attributes, + &binding_flags); // If the index is non-negative, the slot has been found in a local // variable or a parameter. Read it from the context object or the @@ -8504,7 +8516,17 @@ static ObjectPair LoadContextSlotHelper(Arguments args, MaybeObject* value = (holder->IsContext()) ? Context::cast(*holder)->get(index) : JSObject::cast(*holder)->GetElement(index); - return MakePair(Unhole(isolate->heap(), value, attributes), *receiver); + // Check for uninitialized bindings. + if (holder->IsContext() && + binding_flags == MUTABLE_CHECK_INITIALIZED && + value->IsTheHole()) { + Handle reference_error = + isolate->factory()->NewReferenceError("not_defined", + HandleVector(&name, 1)); + return MakePair(isolate->Throw(*reference_error), NULL); + } else { + return MakePair(Unhole(isolate->heap(), value, attributes), *receiver); + } } // If the holder is found, we read the property from it. @@ -8570,14 +8592,27 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_StoreContextSlot) { int index; PropertyAttributes attributes; ContextLookupFlags flags = FOLLOW_CHAINS; - Handle holder = context->Lookup(name, flags, &index, &attributes); + BindingFlags binding_flags; + Handle holder = context->Lookup(name, + flags, + &index, + &attributes, + &binding_flags); if (index >= 0) { if (holder->IsContext()) { + Handle context = Handle::cast(holder); + if (binding_flags == MUTABLE_CHECK_INITIALIZED && + context->get(index)->IsTheHole()) { + Handle error = + isolate->factory()->NewReferenceError("not_defined", + HandleVector(&name, 1)); + return isolate->Throw(*error); + } // Ignore if read_only variable. if ((attributes & READ_ONLY) == 0) { // Context is a fixed array and set cannot fail. - Context::cast(*holder)->set(index, *value); + context->set(index, *value); } else if (strict_mode == kStrictMode) { // Setting read only property in strict mode. Handle error = @@ -9029,10 +9064,13 @@ RUNTIME_FUNCTION(ObjectPair, Runtime_ResolvePossiblyDirectEval) { // it is bound in the global context. int index = -1; PropertyAttributes attributes = ABSENT; + BindingFlags binding_flags; while (true) { receiver = context->Lookup(isolate->factory()->eval_symbol(), FOLLOW_PROTOTYPE_CHAIN, - &index, &attributes); + &index, + &attributes, + &binding_flags); // Stop search when eval is found or when the global context is // reached. if (attributes != ABSENT || context->IsGlobalContext()) break; diff --git a/src/token.h b/src/token.h index 33af7fe..eb825c1 100644 --- a/src/token.h +++ b/src/token.h @@ -71,6 +71,7 @@ namespace internal { /* this block of enum values being contiguous and sorted in the */ \ /* same order! */ \ T(INIT_VAR, "=init_var", 2) /* AST-use only. */ \ + T(INIT_LET, "=init_let", 2) /* AST-use only. */ \ T(INIT_CONST, "=init_const", 2) /* AST-use only. */ \ T(ASSIGN, "=", 2) \ T(ASSIGN_BIT_OR, "|=", 2) \ diff --git a/src/x64/full-codegen-x64.cc b/src/x64/full-codegen-x64.cc index 37e24e9..e77ec64 100644 --- a/src/x64/full-codegen-x64.cc +++ b/src/x64/full-codegen-x64.cc @@ -668,12 +668,12 @@ void FullCodeGenerator::EmitDeclaration(Variable* variable, switch (slot->type()) { case Slot::PARAMETER: case Slot::LOCAL: - if (mode == Variable::CONST) { - __ LoadRoot(kScratchRegister, Heap::kTheHoleValueRootIndex); - __ movq(Operand(rbp, SlotOffset(slot)), kScratchRegister); - } else if (function != NULL) { + if (function != NULL) { VisitForAccumulatorValue(function); __ movq(Operand(rbp, SlotOffset(slot)), result_register()); + } else if (mode == Variable::CONST || mode == Variable::LET) { + __ LoadRoot(kScratchRegister, Heap::kTheHoleValueRootIndex); + __ movq(Operand(rbp, SlotOffset(slot)), kScratchRegister); } break; @@ -692,16 +692,16 @@ void FullCodeGenerator::EmitDeclaration(Variable* variable, __ CompareRoot(rbx, Heap::kCatchContextMapRootIndex); __ Check(not_equal, "Declaration in catch context."); } - if (mode == Variable::CONST) { - __ LoadRoot(kScratchRegister, Heap::kTheHoleValueRootIndex); - __ movq(ContextOperand(rsi, slot->index()), kScratchRegister); - // No write barrier since the hole value is in old space. - } else if (function != NULL) { + if (function != NULL) { VisitForAccumulatorValue(function); __ movq(ContextOperand(rsi, slot->index()), result_register()); int offset = Context::SlotOffset(slot->index()); __ movq(rbx, rsi); __ RecordWrite(rbx, offset, result_register(), rcx); + } else if (mode == Variable::CONST || mode == Variable::LET) { + __ LoadRoot(kScratchRegister, Heap::kTheHoleValueRootIndex); + __ movq(ContextOperand(rsi, slot->index()), kScratchRegister); + // No write barrier since the hole value is in old space. } break; @@ -718,10 +718,10 @@ void FullCodeGenerator::EmitDeclaration(Variable* variable, // Note: For variables we must not push an initial value (such as // 'undefined') because we may have a (legal) redeclaration and we // must not destroy the current value. - if (mode == Variable::CONST) { - __ PushRoot(Heap::kTheHoleValueRootIndex); - } else if (function != NULL) { + if (function != NULL) { VisitForStackValue(function); + } else if (mode == Variable::CONST || mode == Variable::LET) { + __ PushRoot(Heap::kTheHoleValueRootIndex); } else { __ Push(Smi::FromInt(0)); // no initial value! } @@ -1246,6 +1246,18 @@ void FullCodeGenerator::EmitVariableLoad(VariableProxy* proxy) { __ LoadRoot(rax, Heap::kUndefinedValueRootIndex); __ bind(&done); context()->Plug(rax); + } else if (var->mode() == Variable::LET) { + // Let bindings may be the hole value if they have not been initialized. + // Throw a type error in this case. + Label done; + MemOperand slot_operand = EmitSlotSearch(slot, rax); + __ movq(rax, slot_operand); + __ CompareRoot(rax, Heap::kTheHoleValueRootIndex); + __ j(not_equal, &done, Label::kNear); + __ Push(var->name()); + __ CallRuntime(Runtime::kThrowReferenceError, 1); + __ bind(&done); + context()->Plug(rax); } else { context()->Plug(slot); } @@ -1775,6 +1787,57 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var, } __ bind(&skip); + } else if (var->mode() == Variable::LET && op != Token::INIT_LET) { + // Perform the assignment for non-const variables. Const assignments + // are simply skipped. + Slot* slot = var->AsSlot(); + switch (slot->type()) { + case Slot::PARAMETER: + case Slot::LOCAL: { + Label assign; + // Check for an initialized let binding. + __ movq(rdx, Operand(rbp, SlotOffset(slot))); + __ CompareRoot(rdx, Heap::kTheHoleValueRootIndex); + __ j(not_equal, &assign); + __ Push(var->name()); + __ CallRuntime(Runtime::kThrowReferenceError, 1); + // Perform the assignment. + __ bind(&assign); + __ movq(Operand(rbp, SlotOffset(slot)), rax); + break; + } + + case Slot::CONTEXT: { + // Let variables may be the hole value if they have not been + // initialized. Throw a type error in this case. + Label assign; + MemOperand target = EmitSlotSearch(slot, rcx); + // Check for an initialized let binding. + __ movq(rdx, target); + __ CompareRoot(rdx, Heap::kTheHoleValueRootIndex); + __ j(not_equal, &assign, Label::kNear); + __ Push(var->name()); + __ CallRuntime(Runtime::kThrowReferenceError, 1); + // Perform the assignment. + __ bind(&assign); + __ movq(target, rax); + // The value of the assignment is in eax. RecordWrite clobbers its + // register arguments. + __ movq(rdx, rax); + int offset = Context::SlotOffset(slot->index()); + __ RecordWrite(rcx, offset, rdx, rbx); + break; + } + + case Slot::LOOKUP: + // Call the runtime for the assignment. + __ push(rax); // Value. + __ push(rsi); // Context. + __ Push(var->name()); + __ Push(Smi::FromInt(strict_mode_flag())); + __ CallRuntime(Runtime::kStoreContextSlot, 4); + break; + } } else if (var->mode() != Variable::CONST) { // Perform the assignment for non-const variables. Const assignments // are simply skipped. diff --git a/test/mjsunit/harmony/block-let-crankshaft.js b/test/mjsunit/harmony/block-let-crankshaft.js new file mode 100644 index 0000000..c2fb96b --- /dev/null +++ b/test/mjsunit/harmony/block-let-crankshaft.js @@ -0,0 +1,63 @@ +// Copyright 2011 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Flags: --harmony-block-scoping --allow-natives-syntax + +// Test that temporal dead zone semantics for function and block scoped +// ket bindings are handled by the optimizing compiler. + +function f(x, b) { + let y = (b ? y : x) + 42; + return y; +} + +function g(x, b) { + { + let y = (b ? y : x) + 42; + return y; + } +} + +for (var i=0; i<10; i++) { + f(i, false); + g(i, false); +} + +%OptimizeFunctionOnNextCall(f); +%OptimizeFunctionOnNextCall(g); + +try { + f(42, true); +} catch (e) { + assertInstanceof(e, ReferenceError); +} + +try { + g(42, true); +} catch (e) { + assertInstanceof(e, ReferenceError); +} diff --git a/test/mjsunit/harmony/block-let-semantics.js b/test/mjsunit/harmony/block-let-semantics.js new file mode 100644 index 0000000..198c3b4 --- /dev/null +++ b/test/mjsunit/harmony/block-let-semantics.js @@ -0,0 +1,138 @@ +// Copyright 2011 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Flags: --harmony-block-scoping + +// Test temporal dead zone semantics of let bound variables in +// function and block scopes. + +function TestFunctionLocal(s) { + try { + eval("(function(){" + s + "; })")(); + } catch (e) { + assertInstanceof(e, ReferenceError); + return; + } + assertUnreachable(); +} + +function TestBlockLocal(s,e) { + try { + eval("(function(){ {" + s + ";} })")(); + } catch (e) { + assertInstanceof(e, ReferenceError); + return; + } + assertUnreachable(); +} + + +function TestAll(s) { + TestBlockLocal(s); + TestFunctionLocal(s); +} + +// Use before initialization in declaration statement. +TestAll('let x = x + 1'); +TestAll('let x = x += 1'); +TestAll('let x = x++'); +TestAll('let x = ++x'); + +// Use before initialization in prior statement. +TestAll('x + 1; let x;'); +TestAll('x = 1; let x;'); +TestAll('x += 1; let x;'); +TestAll('++x; let x;'); +TestAll('x++; let x;'); + +TestAll('f(); let x; function f() { return x + 1; }'); +TestAll('f(); let x; function f() { x = 1; }'); +TestAll('f(); let x; function f() { x += 1; }'); +TestAll('f(); let x; function f() { ++x; }'); +TestAll('f(); let x; function f() { x++; }'); + +TestAll('f()(); let x; function f() { return function() { return x + 1; } }'); +TestAll('f()(); let x; function f() { return function() { x = 1; } }'); +TestAll('f()(); let x; function f() { return function() { x += 1; } }'); +TestAll('f()(); let x; function f() { return function() { ++x; } }'); +TestAll('f()(); let x; function f() { return function() { x++; } }'); + +// Use in before initialization with a dynamic lookup. +TestAll('eval("x + 1;"); let x;'); +TestAll('eval("x = 1;"); let x;'); +TestAll('eval("x += 1;"); let x;'); +TestAll('eval("++x;"); let x;'); +TestAll('eval("x++;"); let x;'); + +// Test that variables introduced by function declarations are created and +// initialized upon entering a function / block scope. +function f() { + { + assertEquals(2, g1()); + assertEquals(2, eval("g1()")); + + // block scoped function declaration + function g1() { + return 2; + } + } + + assertEquals(3, g2()); + assertEquals(3, eval("g2()")); + // function scoped function declaration + function g2() { + return 3; + } +} +f(); + +// Test that a function declaration introduces a block scoped variable. +TestAll('{ function k() { return 0; } }; k(); '); + +// Test that a function declaration sees the scope it resides in. +function f2() { + let m, n; + { + m = g; + function g() { + return a; + } + let a = 1; + } + assertEquals(1, m()); + + try { + throw 2; + } catch(b) { + n = h; + function h() { + return b + c; + } + let b = 3; + } + assertEquals(5, n()); +}