Fast-codegen: Implementing try/finally on top of nesting context.
authorlrn@chromium.org <lrn@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 14 Dec 2009 11:09:25 +0000 (11:09 +0000)
committerlrn@chromium.org <lrn@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 14 Dec 2009 11:09:25 +0000 (11:09 +0000)
Review URL: http://codereview.chromium.org/492002

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@3461 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/arm/fast-codegen-arm.cc
src/compiler.cc
src/fast-codegen.cc
src/fast-codegen.h
src/ia32/fast-codegen-ia32.cc
src/ia32/macro-assembler-ia32.h
src/x64/fast-codegen-x64.cc
src/x64/macro-assembler-x64.cc
test/mjsunit/try.js

index 303f0a0..55fe9d0 100644 (file)
@@ -1681,6 +1681,7 @@ void FastCodeGenerator::VisitCompareOperation(CompareOperation* expr) {
   // Convert current context to test context: End post-test code.
 }
 
+
 void FastCodeGenerator::VisitThisFunction(ThisFunction* expr) {
   __ ldr(r0, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset));
   Move(expr->context(), r0);
@@ -1689,7 +1690,40 @@ void FastCodeGenerator::VisitThisFunction(ThisFunction* expr) {
 
 Register FastCodeGenerator::result_register() { return r0; }
 
-#undef __
+// ----------------------------------------------------------------------------
+// Non-local control flow support.
+
+void FastCodeGenerator::EnterFinallyBlock() {
+  ASSERT(!result_register().is(r1));
+  // Cook return address in link register to stack (smi encoded Code* delta)
+  __ sub(r1, lr, Operand(masm_->CodeObject()));
+  ASSERT_EQ(1, kSmiTagSize + kSmiShiftSize);
+  ASSERT_EQ(0, kSmiTag);
+  __ add(r1, r1, Operand(r1));  // Convert to smi.
+  __ push(r1);
+  // Store result register while executing finally block.
+  __ push(result_register());
+}
+
+
+void FastCodeGenerator::ExitFinallyBlock() {
+  ASSERT(!result_register().is(r1));
+  // Restore result register from stack.
+  __ pop(result_register());
+  // Uncook return address and return.
+  __ pop(r1);
+  ASSERT_EQ(1, kSmiTagSize + kSmiShiftSize);
+  __ mov(r1, Operand(r1, ASR, 1));  // Un-smi-tag value.
+  __ add(pc, r1, Operand(masm_->CodeObject()));
+}
+
+
+void FastCodeGenerator::ThrowException() {
+  __ push(result_register());
+  __ CallRuntime(Runtime::kThrow, 1);
+}
+
 
+#undef __
 
 } }  // namespace v8::internal
index 46b5aa0..ca0c989 100644 (file)
@@ -751,7 +751,9 @@ void CodeGenSelector::VisitTryCatchStatement(TryCatchStatement* stmt) {
 
 
 void CodeGenSelector::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
-  BAILOUT("TryFinallyStatement");
+  Visit(stmt->try_block());
+  CHECK_BAILOUT;
+  Visit(stmt->finally_block());
 }
 
 
index e53f43a..c635224 100644 (file)
@@ -443,7 +443,63 @@ void FastCodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
 
 
 void FastCodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
-  UNREACHABLE();
+  // Try finally is compiled by setting up a try-handler on the stack while
+  // executing the try body, and removing it again afterwards.
+  //
+  // The try-finally construct can enter the finally block in three ways:
+  // 1. By exiting the try-block normally. This removes the try-handler and
+  //      calls the finally block code before continuing.
+  // 2. By exiting the try-block with a function-local control flow transfer
+  //    (break/continue/return). The site of the, e.g., break removes the
+  //    try handler and calls the finally block code before continuing
+  //    its outward control transfer.
+  // 3. by exiting the try-block with a thrown exception.
+  //    This can happen in nested function calls. It traverses the try-handler
+  //    chaing and consumes the try-handler entry before jumping to the
+  //    handler code. The handler code then calls the finally-block before
+  //    rethrowing the exception.
+  //
+  // The finally block must assume a return address on top of the stack
+  // (or in the link register on ARM chips) and a value (return value or
+  // exception) in the result register (rax/eax/r0), both of which must
+  // be preserved. The return address isn't GC-safe, so it should be
+  // cooked before GC.
+  Label finally_entry;
+  Label try_handler_setup;
+
+  // Setup the try-handler chain. Use a call to
+  // Jump to try-handler setup and try-block code. Use call to put try-handler
+  // address on stack.
+  __ Call(&try_handler_setup);
+  // Try handler code. Return address of call is pushed on handler stack.
+  {
+    // This code is only executed during stack-handler traversal when an
+    // exception is thrown. The execption is in the result register, which
+    // is retained by the finally block.
+    // Call the finally block and then rethrow the exception.
+    __ Call(&finally_entry);
+    ThrowException();
+  }
+
+  __ bind(&finally_entry);
+  {
+    // Finally block implementation.
+    EnterFinallyBlock();
+    Finally finally_block(this);
+    Visit(stmt->finally_block());
+    ExitFinallyBlock();  // Return to the calling code.
+  }
+
+  __ bind(&try_handler_setup);
+  {
+    // Setup try handler (stack pointer registers).
+    __ PushTryHandler(IN_JAVASCRIPT, TRY_FINALLY_HANDLER);
+    TryFinally try_block(this, &finally_entry);
+    VisitStatements(stmt->try_block()->statements());
+    __ PopTryHandler();
+  }
+  // Execute the finally block on the way out.
+  __ Call(&finally_entry);
 }
 
 
index 53d3f24..38588d1 100644 (file)
@@ -276,6 +276,12 @@ class FastCodeGenerator: public AstVisitor {
   void SetStatementPosition(Statement* stmt);
   void SetSourcePosition(int pos);
 
+  // Non-local control flow support.
+  void EnterFinallyBlock();
+  void ExitFinallyBlock();
+  void ThrowException();
+
+  // Loop nesting counter.
   int loop_depth() { return loop_depth_; }
   void increment_loop_depth() { loop_depth_++; }
   void decrement_loop_depth() {
index b81c836..1e4473c 100644 (file)
@@ -1656,14 +1656,51 @@ void FastCodeGenerator::VisitCompareOperation(CompareOperation* expr) {
   // Convert current context to test context: End post-test code.
 }
 
-Register FastCodeGenerator::result_register() { return eax; }
 
 void FastCodeGenerator::VisitThisFunction(ThisFunction* expr) {
   __ mov(eax, Operand(ebp, JavaScriptFrameConstants::kFunctionOffset));
   Move(expr->context(), eax);
 }
 
-#undef __
 
+Register FastCodeGenerator::result_register() { return eax; }
+
+// ----------------------------------------------------------------------------
+// Non-local control flow support.
+
+void FastCodeGenerator::EnterFinallyBlock() {
+  // Cook return address on top of stack (smi encoded Code* delta)
+  ASSERT(!result_register().is(edx));
+  __ mov(edx, Operand(esp, 0));
+  __ sub(Operand(edx), Immediate(masm_->CodeObject()));
+  ASSERT_EQ(1, kSmiTagSize + kSmiShiftSize);
+  ASSERT_EQ(0, kSmiTag);
+  __ add(edx, Operand(edx));  // Convert to smi.
+  __ mov(Operand(esp, 0), edx);
+  // Store result register while executing finally block.
+  __ push(result_register());
+}
+
+
+void FastCodeGenerator::ExitFinallyBlock() {
+  ASSERT(!result_register().is(edx));
+  // Restore result register from stack.
+  __ pop(result_register());
+  // Uncook return address.
+  __ mov(edx, Operand(esp, 0));
+  __ sar(edx, 1);  // Convert smi to int.
+  __ add(Operand(edx), Immediate(masm_->CodeObject()));
+  __ mov(Operand(esp, 0), edx);
+  // And return.
+  __ ret(0);
+}
+
+
+void FastCodeGenerator::ThrowException() {
+  __ push(result_register());
+  __ CallRuntime(Runtime::kThrow, 1);
+}
+
+#undef __
 
 } }  // namespace v8::internal
index 1d4bfa1..160dbcb 100644 (file)
@@ -152,7 +152,6 @@ class MacroAssembler: public Assembler {
   // Unlink the stack handler on top of the stack from the try handler chain.
   void PopTryHandler();
 
-
   // ---------------------------------------------------------------------------
   // Inline caching support
 
index 192c7b0..cf9b6b0 100644 (file)
@@ -1671,6 +1671,45 @@ void FastCodeGenerator::VisitThisFunction(ThisFunction* expr) {
 
 Register FastCodeGenerator::result_register() { return rax; }
 
+// ----------------------------------------------------------------------------
+// Non-local control flow support.
+
+
+void FastCodeGenerator::EnterFinallyBlock() {
+  ASSERT(!result_register().is(rdx));
+  ASSERT(!result_register().is(rcx));
+  // Cook return address on top of stack (smi encoded Code* delta)
+  __ movq(rdx, Operand(rsp, 0));
+  __ Move(rcx, masm_->CodeObject());
+  __ subq(rdx, rcx);
+  __ Integer32ToSmi(rdx, rdx);
+  __ movq(Operand(rsp, 0), rdx);
+  // Store result register while executing finally block.
+  __ push(result_register());
+}
+
+
+void FastCodeGenerator::ExitFinallyBlock() {
+  ASSERT(!result_register().is(rdx));
+  ASSERT(!result_register().is(rcx));
+  // Restore result register from stack.
+  __ pop(result_register());
+  // Uncook return address.
+  __ movq(rdx, Operand(rsp, 0));
+  __ SmiToInteger32(rdx, rdx);
+  __ Move(rcx, masm_->CodeObject());
+  __ addq(rdx, rcx);
+  __ movq(Operand(rsp, 0), rdx);
+  // And return.
+  __ ret(0);
+}
+
+
+void FastCodeGenerator::ThrowException() {
+  __ push(result_register());
+  __ CallRuntime(Runtime::kThrow, 1);
+}
+
 #undef __
 
 
index c3ea925..5cf09f2 100644 (file)
@@ -1440,8 +1440,10 @@ void MacroAssembler::PushTryHandler(CodeLocation try_location,
 
 void MacroAssembler::PopTryHandler() {
   ASSERT_EQ(0, StackHandlerConstants::kNextOffset);
+  // Unlink this handler.
   movq(kScratchRegister, ExternalReference(Top::k_handler_address));
   pop(Operand(kScratchRegister, 0));
+  // Remove the remaining fields.
   addq(rsp, Immediate(StackHandlerConstants::kSize - kPointerSize));
 }
 
index 0bd78b4..794860a 100644 (file)
@@ -347,3 +347,48 @@ assertTrue(broke);
 assertFalse(caught);
 assertTrue(finalized);
 
+function return_from_nested_finally_in_finally() {
+  try {
+    return 1;
+  } finally {
+    try {
+      return 2;
+    } finally {
+      return 42;
+    }
+  }
+}
+
+assertEquals(42, return_from_nested_finally_in_finally());
+
+function break_from_nested_finally_in_finally() {
+  L: try {
+    return 1;
+  } finally {
+    try {
+      return 2;
+    } finally {
+      break L;
+    }
+  }
+  return 42;
+}
+
+assertEquals(42, break_from_nested_finally_in_finally());
+
+function continue_from_nested_finally_in_finally() {
+  do {
+    try {
+      return 1;
+    } finally {
+      try {
+        return 2;
+      } finally {
+        continue;
+      }
+    }
+  } while (false);
+  return 42;
+}
+
+assertEquals(42, continue_from_nested_finally_in_finally());