Author: [email protected]
Date: Wed Jan  7 03:35:09 2009
New Revision: 1037

Added:
    branches/experimental/toiger/test/mjsunit/multiple-return.js
Modified:
    branches/experimental/toiger/src/codegen-ia32.cc
    branches/experimental/toiger/src/codegen-ia32.h
    branches/experimental/toiger/src/jump-target-ia32.cc
    branches/experimental/toiger/src/virtual-frame-ia32.cc
    branches/experimental/toiger/src/virtual-frame-ia32.h
    branches/experimental/toiger/test/mjsunit/debug-evaluate.js
    branches/experimental/toiger/test/mjsunit/debug-step.js

Log:
Enable register allocation for return statements. Make sure to
prepare the frame for the return even in the try-finally and
try-catch cases that shadows the function return label.
Review URL: http://codereview.chromium.org/16567

Modified: branches/experimental/toiger/src/codegen-ia32.cc
==============================================================================
--- branches/experimental/toiger/src/codegen-ia32.cc    (original)
+++ branches/experimental/toiger/src/codegen-ia32.cc    Wed Jan  7 03:35:09  
2009
@@ -83,7 +83,6 @@
        allocator_(NULL),
        cc_reg_(no_condition),
        state_(NULL),
-      is_inside_try_(false),
        break_stack_height_(0),
        loop_nesting_(0),
        function_return_is_shadowed_(false),
@@ -1627,41 +1626,58 @@

  void CodeGenerator::VisitReturnStatement(ReturnStatement* node) {
    ASSERT(!in_spilled_code());
-  VirtualFrame::SpilledScope spilled_scope(this);
    Comment cmnt(masm_, "[ ReturnStatement");
-  CodeForStatement(node);
-  LoadAndSpill(node->expression());
-
-  // Move the function result into eax
-  frame_->EmitPop(eax);

-  // If we're inside a try statement or the return instruction
-  // sequence has been generated, we just jump to that
-  // point. Otherwise, we generate the return instruction sequence and
-  // bind the function return label.
-  if (is_inside_try_ || function_return_.is_bound()) {
+  if (function_return_is_shadowed_) {
+    // If the function return is shadowed, we spill all information
+    // and just jump to the label.
+    VirtualFrame::SpilledScope spilled_scope(this);
+    CodeForStatement(node);
+    LoadAndSpill(node->expression());
+    frame_->EmitPop(eax);
      function_return_.Jump();
    } else {
-    function_return_.Bind();
-    if (FLAG_trace) {
-      frame_->EmitPush(eax);  // undo the pop(eax) from above
-      frame_->CallRuntime(Runtime::kTraceExit, 1);
+    // Load the returned value.
+    CodeForStatement(node);
+    Load(node->expression());
+
+    // Pop the result from the frame and prepare the frame for
+    // returning thus making it easier to merge.
+    Result result = frame_->Pop();
+    frame_->PrepareForReturn();
+
+    // Move the result into register eax where it belongs.
+    result.ToRegister(eax);
+    // TODO(): Instead of explictly calling Unuse on the result, it
+    // might be better to pass the result to Jump and Bind below.
+    result.Unuse();
+
+    // If the function return label is already bound, we reuse the
+    // code by jumping to the return site.
+    if (function_return_.is_bound()) {
+      function_return_.Jump();
+    } else {
+      function_return_.Bind();
+      if (FLAG_trace) {
+        frame_->Push(eax);  // Materialize result on the stack.
+        frame_->CallRuntime(Runtime::kTraceExit, 1);
+      }
+
+      // Add a label for checking the size of the code used for returning.
+      Label check_exit_codesize;
+      __ bind(&check_exit_codesize);
+
+      // Leave the frame and return popping the arguments and the
+      // receiver.
+      frame_->Exit();
+      __ ret((scope_->num_parameters() + 1) * kPointerSize);
+      DeleteFrame();
+
+      // Check that the size of the code used for returning matches what is
+      // expected by the debugger.
+      ASSERT_EQ(Debug::kIa32JSReturnSequenceLength,
+                __ SizeOfCodeGeneratedSince(&check_exit_codesize));
      }
-
-    // Add a label for checking the size of the code used for returning.
-    Label check_exit_codesize;
-    __ bind(&check_exit_codesize);
-
-    // Leave the frame and return popping the arguments and the
-    // receiver.
-    frame_->Exit();
-    __ ret((scope_->num_parameters() + 1) * kPointerSize);
-    DeleteFrame();
-
-    // Check that the size of the code used for returning matches what is
-    // expected by the debugger.
-    ASSERT_EQ(Debug::kIa32JSReturnSequenceLength,
-              __ SizeOfCodeGeneratedSince(&check_exit_codesize));
    }
  }

@@ -2310,17 +2326,21 @@
    // target.
    int nof_escapes = node->escaping_targets()->length();
    List<ShadowTarget*> shadows(1 + nof_escapes);
+
+  // Add the shadow target for the function return.
+  static const int kReturnShadowIndex = 0;
    shadows.Add(new ShadowTarget(&function_return_));
+  bool function_return_was_shadowed = function_return_is_shadowed_;
+  function_return_is_shadowed_ = true;
+  ASSERT(shadows[kReturnShadowIndex]->original_target() ==  
&function_return_);
+
+  // Add the remaining shadow targets.
    for (int i = 0; i < nof_escapes; i++) {
      shadows.Add(new ShadowTarget(node->escaping_targets()->at(i)));
    }
-  bool function_return_was_shadowed = function_return_is_shadowed_;
-  function_return_is_shadowed_ = true;

    // Generate code for the statements in the try block.
-  { TempAssign<bool> temp(&is_inside_try_, true);
-    VisitStatementsAndSpill(node->try_block()->statements());
-  }
+  VisitStatementsAndSpill(node->try_block()->statements());

    // Stop the introduced shadowing and count the number of required  
unlinks.
    // After shadowing stops, the original targets are unshadowed and the
@@ -2377,6 +2397,10 @@
        frame_->EmitPop(Operand::StaticVariable(handler_address));
        frame_->Drop(StackHandlerConstants::kSize / kPointerSize - 1);
        // next_sp popped.
+
+      if (!function_return_is_shadowed_ && i == kReturnShadowIndex) {
+        frame_->PrepareForReturn();
+      }
        shadows[i]->original_target()->Jump();
      }
    }
@@ -2423,17 +2447,21 @@
    // target.
    int nof_escapes = node->escaping_targets()->length();
    List<ShadowTarget*> shadows(1 + nof_escapes);
+
+  // Add the shadow target for the function return.
+  static const int kReturnShadowIndex = 0;
    shadows.Add(new ShadowTarget(&function_return_));
+  bool function_return_was_shadowed = function_return_is_shadowed_;
+  function_return_is_shadowed_ = true;
+  ASSERT(shadows[kReturnShadowIndex]->original_target() ==  
&function_return_);
+
+  // Add the remaining shadow targets.
    for (int i = 0; i < nof_escapes; i++) {
      shadows.Add(new ShadowTarget(node->escaping_targets()->at(i)));
    }
-  bool function_return_was_shadowed = function_return_is_shadowed_;
-  function_return_is_shadowed_ = true;

    // Generate code for the statements in the try block.
-  { TempAssign<bool> temp(&is_inside_try_, true);
-    VisitStatementsAndSpill(node->try_block()->statements());
-  }
+  VisitStatementsAndSpill(node->try_block()->statements());

    // Stop the introduced shadowing and count the number of required  
unlinks.
    // After shadowing stops, the original targets are unshadowed and the
@@ -2459,13 +2487,14 @@
    // have been jumped to.
    for (int i = 0; i <= nof_escapes; i++) {
      if (shadows[i]->is_linked()) {
-      // Because we can be jumping here (to spilled code) from unspilled
-      // code, we need to reestablish a spilled frame at this block.
+      // Because we can be jumping here (to spilled code) from
+      // unspilled code, we need to reestablish a spilled frame at
+      // this block.
        shadows[i]->Bind();
        frame_->SpillAll();
-      if (shadows[i]->original_target() == &function_return_) {
-        // If this target shadowed the function return, materialize the
-        // return value on the stack.
+      if (i == kReturnShadowIndex) {
+        // If this target shadowed the function return, materialize
+        // the return value on the stack.
          frame_->EmitPush(eax);
        } else {
          // Fake TOS for targets that shadowed breaks and continues.
@@ -2518,11 +2547,20 @@
      frame_->EmitPop(eax);

      // Generate code to jump to the right destination for all used
-    // (formerly) shadowing targets.
+    // formerly shadowing targets.
      for (int i = 0; i <= nof_escapes; i++) {
        if (shadows[i]->is_bound()) {
+        JumpTarget* original = shadows[i]->original_target();
          __ cmp(Operand(ecx), Immediate(Smi::FromInt(JUMPING + i)));
-        shadows[i]->original_target()->Branch(equal);
+        if (!function_return_is_shadowed_ && i == kReturnShadowIndex) {
+          JumpTarget skip(this);
+          skip.Branch(not_equal);
+          frame_->PrepareForReturn();
+          original->Jump();
+          skip.Bind();
+        } else {
+          original->Branch(equal);
+        }
        }
      }

@@ -4300,18 +4338,13 @@
  }


-bool CodeGenerator::IsActualFunctionReturn(JumpTarget* target) {
-  return (target == &function_return_ && !function_return_is_shadowed_);
-}
-
-
  #ifdef DEBUG
  bool CodeGenerator::HasValidEntryRegisters() {
-  return (allocator()->count(eax) - frame()->register_count(eax) == 0)
-      && (allocator()->count(ebx) - frame()->register_count(ebx) == 0)
-      && (allocator()->count(ecx) - frame()->register_count(ecx) == 0)
-      && (allocator()->count(edx) - frame()->register_count(edx) == 0)
-      && (allocator()->count(edi) - frame()->register_count(edi) == 0);
+  return (allocator()->count(eax) == frame()->register_count(eax))
+      && (allocator()->count(ebx) == frame()->register_count(ebx))
+      && (allocator()->count(ecx) == frame()->register_count(ecx))
+      && (allocator()->count(edx) == frame()->register_count(edx))
+      && (allocator()->count(edi) == frame()->register_count(edi));
  }
  #endif


Modified: branches/experimental/toiger/src/codegen-ia32.h
==============================================================================
--- branches/experimental/toiger/src/codegen-ia32.h     (original)
+++ branches/experimental/toiger/src/codegen-ia32.h     Wed Jan  7 03:35:09 2009
@@ -454,10 +454,6 @@
    void CodeForStatement(Node* node);
    void CodeForSourcePosition(int pos);

-  // Is the given jump target the actual (ie, non-shadowed) function return
-  // target?
-  bool IsActualFunctionReturn(JumpTarget* target);
-
    bool is_eval_;  // Tells whether code is generated for eval.

  #ifdef DEBUG
@@ -478,7 +474,6 @@
    RegisterAllocator* allocator_;
    Condition cc_reg_;
    CodeGenState* state_;
-  bool is_inside_try_;
    int break_stack_height_;
    int loop_nesting_;


Modified: branches/experimental/toiger/src/jump-target-ia32.cc
==============================================================================
--- branches/experimental/toiger/src/jump-target-ia32.cc        (original)
+++ branches/experimental/toiger/src/jump-target-ia32.cc        Wed Jan  7  
03:35:09 2009
@@ -69,20 +69,12 @@
    ASSERT(cgen_->HasValidEntryRegisters());

    if (expected_frame_ == NULL) {
-    // The frame at the actual function return will always have height  
zero.
-    if (cgen_->IsActualFunctionReturn(this)) {
-      current_frame->Forget(current_frame->height());
-    }
      current_frame->MakeMergable();
      expected_frame_ = current_frame;
      ASSERT(cgen_->HasValidEntryRegisters());
      cgen_->SetFrame(NULL);
    } else {
-    // No code needs to be emitted to merge to the expected frame at the
-    // actual function return.
-    if (!cgen_->IsActualFunctionReturn(this)) {
-      current_frame->MergeTo(expected_frame_);
-    }
+    current_frame->MergeTo(expected_frame_);
      ASSERT(cgen_->HasValidEntryRegisters());
      cgen_->DeleteFrame();
    }
@@ -114,10 +106,6 @@

    if (expected_frame_ == NULL) {
      expected_frame_ = new VirtualFrame(current_frame);
-    // The frame at the actual function return will always have height  
zero.
-    if (cgen_->IsActualFunctionReturn(this)) {
-      expected_frame_->Forget(expected_frame_->height());
-    }
      // For a branch, the frame at the fall-through basic block (not  
labeled)
      // does not need to be mergable, but only the other (labeled) one.   
That
      // is achieved by reversing the condition and emitting the make  
mergable
@@ -135,29 +123,22 @@
        __ j(cc, &label_, hint);
      }
    } else {
-    // No code needs to be emitted to merge to the expected frame at the
-    // actual function return.
-    if (cgen_->IsActualFunctionReturn(this)) {
-      ASSERT(cgen_->HasValidEntryRegisters());
-      __ j(cc, &label_, hint);
-    } else {
-      // We negate the condition and emit the code to merge to the expected
-      // frame immediately.
-      //
-      // TODO(): This should be replaced with a solution that emits the
-      // merge code for forward CFG edges at the appropriate entry to the
-      // target block.
-      Label original_fall_through;
-      __ j(NegateCondition(cc), &original_fall_through, NegateHint(hint));
-      VirtualFrame* working_frame = new VirtualFrame(current_frame);
-      cgen_->SetFrame(working_frame);
-      working_frame->MergeTo(expected_frame_);
-      ASSERT(cgen_->HasValidEntryRegisters());
-      __ jmp(&label_);
-      cgen_->SetFrame(current_frame);
-      delete working_frame;
-      __ bind(&original_fall_through);
-    }
+    // We negate the condition and emit the code to merge to the expected
+    // frame immediately.
+    //
+    // TODO(): This should be replaced with a solution that emits the
+    // merge code for forward CFG edges at the appropriate entry to the
+    // target block.
+    Label original_fall_through;
+    __ j(NegateCondition(cc), &original_fall_through, NegateHint(hint));
+    VirtualFrame* working_frame = new VirtualFrame(current_frame);
+    cgen_->SetFrame(working_frame);
+    working_frame->MergeTo(expected_frame_);
+    ASSERT(cgen_->HasValidEntryRegisters());
+    __ jmp(&label_);
+    cgen_->SetFrame(current_frame);
+    delete working_frame;
+    __ bind(&original_fall_through);
    }
    // Postcondition: there is both a current frame and an expected frame at
    // the label and they match.
@@ -215,7 +196,6 @@
    // at the label.
    ASSERT(cgen_ != NULL);
    ASSERT(masm_ != NULL);
-  ASSERT(!cgen_->IsActualFunctionReturn(this));

    VirtualFrame* current_frame = cgen_->frame();
    ASSERT(current_frame != NULL);
@@ -250,10 +230,6 @@
      ASSERT(cgen_->HasValidEntryRegisters());
      // When a label is bound the current frame becomes the expected frame  
at
      // the label.  This requires the current frame to be mergable.
-    // The frame at the actual function return will always have height  
zero.
-    if (cgen_->IsActualFunctionReturn(this)) {
-      current_frame->Forget(current_frame->height());
-    }
      current_frame->MakeMergable();
      ASSERT(cgen_->HasValidEntryRegisters());
      expected_frame_ = new VirtualFrame(current_frame);
@@ -262,11 +238,7 @@
      ASSERT(cgen_->HasValidEntryRegisters());
    } else {
      ASSERT(cgen_->HasValidEntryRegisters());
-    // No code needs to be emitted to merge to the expected frame at the
-    // actual function return.
-    if (!cgen_->IsActualFunctionReturn(this)) {
-      current_frame->MergeTo(expected_frame_);
-    }
+    current_frame->MergeTo(expected_frame_);
      ASSERT(cgen_->HasValidEntryRegisters());
    }


Modified: branches/experimental/toiger/src/virtual-frame-ia32.cc
==============================================================================
--- branches/experimental/toiger/src/virtual-frame-ia32.cc      (original)
+++ branches/experimental/toiger/src/virtual-frame-ia32.cc      Wed Jan  7  
03:35:09 2009
@@ -594,11 +594,13 @@
    // copy is stored in the frame.  The external reference to esi
    // remains in addition to the cached copy in the frame.
    Push(esi);
+  SyncElementAt(elements_.length() - 1);

-  // Store the function in the frame.  The frame owns the register  
reference
-  // now (ie, it can keep it in edi or spill it later).
+  // Store the function in the frame.  The frame owns the register
+  // reference now (ie, it can keep it in edi or spill it later).
    Push(edi);
    cgen_->allocator()->Unuse(edi);
+  SpillElementAt(elements_.length() - 1);
  }


@@ -623,6 +625,21 @@

    frame_pointer_ = kIllegalIndex;
    EmitPop(ebp);
+}
+
+
+void VirtualFrame::PrepareForReturn() {
+  // Spill all locals. This is necessary to make sure all locals have
+  // the right value when breaking at the return site in the debugger.
+  for (int i = 0; i < expression_base_index(); i++) SpillElementAt(i);
+
+  // Drop all non-local stack elements.
+  Drop(height());
+
+  // Validate state: The expression stack should be empty and the
+  // stack pointer should have been updated to reflect this.
+  ASSERT(height() == 0);
+  ASSERT(stack_pointer_ == expression_base_index() - 1);
  }



Modified: branches/experimental/toiger/src/virtual-frame-ia32.h
==============================================================================
--- branches/experimental/toiger/src/virtual-frame-ia32.h       (original)
+++ branches/experimental/toiger/src/virtual-frame-ia32.h       Wed Jan  7  
03:35:09 2009
@@ -237,6 +237,12 @@
    void Enter();
    void Exit();

+  // Prepare for returning from the frame by spilling locals and
+  // dropping all non-locals elements in the virtual frame.  This
+  // avoids generating unnecessary merge code when jumping to the
+  // shared return site.  Emits code for spills.
+  void PrepareForReturn();
+
    // Allocate and initialize the frame-allocated locals.  The eax register
    // us clobbered.
    void AllocateStackSlots(int count);
@@ -495,7 +501,7 @@

    // Move frame elements currently in registers or constants, that
    // should be in memory in the expected frame, to memory.
-  void MergeMoveRegistersToMemory(VirtualFrame *expected);
+  void MergeMoveRegistersToMemory(VirtualFrame* expected);

    // Make the register-to-register moves necessary to
    // merge this frame with the expected frame.
@@ -504,14 +510,14 @@
    // This is because some new memory-to-register moves are
    // created in order to break cycles of register moves.
    // Used in the implementation of MergeTo().
-  void MergeMoveRegistersToRegisters(VirtualFrame *expected);
+  void MergeMoveRegistersToRegisters(VirtualFrame* expected);

    // Make the memory-to-register and constant-to-register moves
    // needed to make this frame equal the expected frame.
    // Called after all register-to-memory and register-to-register
    // moves have been made.  After this function returns, the frames
    // should be equal.
-  void MergeMoveMemoryToRegisters(VirtualFrame *expected);
+  void MergeMoveMemoryToRegisters(VirtualFrame* expected);
  };



Modified: branches/experimental/toiger/test/mjsunit/debug-evaluate.js
==============================================================================
--- branches/experimental/toiger/test/mjsunit/debug-evaluate.js (original)
+++ branches/experimental/toiger/test/mjsunit/debug-evaluate.js Wed Jan  7  
03:35:09 2009
@@ -64,33 +64,33 @@

  function listener(event, exec_state, event_data, data) {
    try {
-  if (event == Debug.DebugEvent.Break) {
-    // Get the debug command processor.
-    var dcp = exec_state.debugCommandProcessor();
+    if (event == Debug.DebugEvent.Break) {
+      // Get the debug command processor.
+      var dcp = exec_state.debugCommandProcessor();

-    // Test some illegal evaluate requests.
-    testRequest(dcp, void 0, false);
-    testRequest(dcp, '{"expression":"1","global"=true}', false);
-    testRequest(dcp, '{"expression":"a","frame":4}', false);
+      // Test some illegal evaluate requests.
+      testRequest(dcp, void 0, false);
+      testRequest(dcp, '{"expression":"1","global"=true}', false);
+      testRequest(dcp, '{"expression":"a","frame":4}', false);

-    // Test some legal evaluate requests.
-    testRequest(dcp, '{"expression":"1+2"}', true, 3);
-    testRequest(dcp, '{"expression":"a+2"}', true, 5);
-    testRequest(dcp, '{"expression":"({\\"a\\":1,\\"b\\":2}).b+2"}', true,  
4);
+      // Test some legal evaluate requests.
+      testRequest(dcp, '{"expression":"1+2"}', true, 3);
+      testRequest(dcp, '{"expression":"a+2"}', true, 5);
+      testRequest(dcp, '{"expression":"({\\"a\\":1,\\"b\\":2}).b+2"}',  
true, 4);

-    // Test evaluation of a in the stack frames and the global context.
-    testRequest(dcp, '{"expression":"a"}', true, 3);
-    testRequest(dcp, '{"expression":"a","frame":0}', true, 3);
-    testRequest(dcp, '{"expression":"a","frame":1}', true, 2);
-    testRequest(dcp, '{"expression":"a","frame":2}', true, 1);
-    testRequest(dcp, '{"expression":"a","global":true}', true, 1);
-    testRequest(dcp, '{"expression":"this.a","global":true}', true, 1);
+      // Test evaluation of a in the stack frames and the global context.
+      testRequest(dcp, '{"expression":"a"}', true, 3);
+      testRequest(dcp, '{"expression":"a","frame":0}', true, 3);
+      testRequest(dcp, '{"expression":"a","frame":1}', true, 2);
+      testRequest(dcp, '{"expression":"a","frame":2}', true, 1);
+      testRequest(dcp, '{"expression":"a","global":true}', true, 1);
+      testRequest(dcp, '{"expression":"this.a","global":true}', true, 1);

-    // Indicate that all was processed.
-    listenerComplete = true;
-  }
+      // Indicate that all was processed.
+      listenerComplete = true;
+    }
    } catch (e) {
-    exception = e
+   exception = e
    };
  };


Modified: branches/experimental/toiger/test/mjsunit/debug-step.js
==============================================================================
--- branches/experimental/toiger/test/mjsunit/debug-step.js     (original)
+++ branches/experimental/toiger/test/mjsunit/debug-step.js     Wed Jan  7  
03:35:09 2009
@@ -36,8 +36,7 @@
  var bp1, bp2;

  function listener(event, exec_state, event_data, data) {
-  if (event == Debug.DebugEvent.Break)
-  {
+  if (event == Debug.DebugEvent.Break) {
      if (state == 0) {
        exec_state.prepareStep(Debug.StepAction.StepIn, 1000);
        state = 1;
@@ -68,7 +67,6 @@
  state = 0;
  result = -1;
  f();
-print(state);
  assertEquals(499, result);

  // Check that performing 1000 steps with a break point on the statement in  
the

Added: branches/experimental/toiger/test/mjsunit/multiple-return.js
==============================================================================
--- (empty file)
+++ branches/experimental/toiger/test/mjsunit/multiple-return.js        Wed Jan 
 7  
03:35:09 2009
@@ -0,0 +1,62 @@
+// Copyright 2009 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.
+
+function F() {
+  for (var x in [1,2,3]) {
+    return 42;
+  }
+  return 87;
+}
+
+
+function G() {
+  for (var x in [1,2,3]) {
+    try {
+      return 42;
+    } finally {
+      // Do nothing.
+    }
+  }
+  return 87;
+}
+
+
+function H() {
+  for (var x in [1,2,3]) {
+    try {
+      return 42;
+    } catch (e) {
+      // Do nothing.
+    }
+  }
+  return 87;
+}
+
+
+assertEquals(42, F());
+assertEquals(42, G());
+assertEquals(42, H());

--~--~---------~--~----~------------~-------~--~----~
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
-~----------~----~----~----~------~----~------~--~---

Reply via email to