Patchwork [gccgo] Compiler support for panic/recover

login
register
mail settings
Submitter Ian Taylor
Date June 14, 2010, 6:14 a.m.
Message ID <mcr7hm2f8dq.fsf@dhcp-172-17-9-151.mtv.corp.google.com>
Download mbox | patch
Permalink /patch/55475/
State New
Headers show

Comments

Ian Taylor - June 14, 2010, 6:14 a.m.
This patch to the Go frontend implements support for the new
panic/recover functionality.  It also permits taking the address of a
result variable, as is required by recover.  Committed to gccgo
branch.

Ian

Patch

diff -r da79ac110978 go/Make-lang.in
--- a/go/Make-lang.in	Sun Jun 13 23:06:30 2010 -0700
+++ b/go/Make-lang.in	Sun Jun 13 23:07:43 2010 -0700
@@ -155,8 +155,8 @@ 
 go/go-dump.o: go/go-dump.cc $(GO_SYSTEM_H) $(GO_C_H) go/go-dump.h
 go/go-lang.o: go/go-lang.c $(GO_SYSTEM_H) coretypes.h opts.h $(TREE_H) \
 	$(GIMPLE_H) $(GGC_H) toplev.h debug.h options.h $(FLAGS_H) convert.h \
-	langhooks.h langhooks-def.h $(TARGET_H) $(DIAGNOSTIC_H) $(GO_C_H) \
-	gt-go-go-lang.h gtype-go.h
+	langhooks.h langhooks-def.h $(EXCEPT_H) $(TARGET_H) $(DIAGNOSTIC_H) \
+	$(GO_C_H) gt-go-go-lang.h gtype-go.h
 go/gogo-tree.o: go/gogo-tree.cc $(GO_SYSTEM_H) $(TREE_H) $(GIMPLE_H) \
 	tree-iterator.h $(CGRAPH_H) langhooks.h convert.h output.h \
 	$(TM_P_H) $(DIAGNOSTIC_H) $(GO_TYPES_H) $(GO_EXPRESSIONS_H) \
diff -r da79ac110978 go/export.cc
--- a/go/export.cc	Sun Jun 13 23:06:30 2010 -0700
+++ b/go/export.cc	Sun Jun 13 23:07:43 2010 -0700
@@ -133,14 +133,6 @@ 
 	exports.push_back(p->second);
     }
 
-  if (exports.empty()
-      && import_init_fn.empty()
-      && imported_init_fns.empty())
-    {
-      // Nothing to export.
-      return;
-    }
-
   std::sort(exports.begin(), exports.end(), Sort_bindings());
 
   // Although the export data is readable, at least this version is,
diff -r da79ac110978 go/expressions.cc
--- a/go/expressions.cc	Sun Jun 13 23:06:30 2010 -0700
+++ b/go/expressions.cc	Sun Jun 13 23:07:43 2010 -0700
@@ -417,6 +417,8 @@ 
 					 fold_convert(ptr_type_node, rhs_tree),
 					 build_pointer_type(boolean_type_node),
 					 null_pointer_node);
+	  // This will panic if the interface conversion fails.
+	  TREE_NOTHROW(convert_interface_decl) = 0;
 	  return fold_convert(lhs_type_tree, call);
 	}
     }
@@ -442,6 +444,8 @@ 
 					 const_ptr_type_node,
 					 fold_convert(const_ptr_type_node,
 						      rhs_tree));
+	  // This call will panic if the conversion fails.
+	  TREE_NOTHROW(interface_to_pointer_decl) = 0;
 	  gcc_assert(POINTER_TYPE_P(lhs_type_tree));
 	  return fold_convert(lhs_type_tree, call);
 	}
@@ -471,6 +475,8 @@ 
 					 ptr_type_node,
 					 fold_convert(ptr_type_node,
 						      rhs_tree));
+	  // This call will panic if the conversion fails.
+	  TREE_NOTHROW(interface_to_object_decl) = 0;
 	  return build2(COMPOUND_EXPR, lhs_type_tree, make_tmp,
 			build2(COMPOUND_EXPR, lhs_type_tree, call, tmp));
 	}
@@ -568,6 +574,7 @@ 
 					  "__go_bad_index",
 					  0,
 					  void_type_node);
+	  TREE_NOTHROW(bad_index_fndecl) = 0;
 	  TREE_THIS_VOLATILE(bad_index_fndecl) = 1;
 	  val = build2(COMPOUND_EXPR, TREE_TYPE(val),
 		       build3(COND_EXPR, void_type_node,
@@ -924,7 +931,7 @@ 
 // may want to move the variable onto the heap.
 
 bool
-Var_expression::do_address_taken(source_location location, bool escapes)
+Var_expression::do_address_taken(source_location, bool escapes)
 {
   if (!escapes)
     return true;
@@ -935,10 +942,8 @@ 
     }
   else if (this->variable_->is_result_variable())
     {
-      // There is no way to make a result variable permanent; it has
-      // to disappear after the function returns.
-      error_at(location, "may not take address of out parameter");
-      return false;
+      this->variable_->result_var_value()->set_address_taken();
+      return true;
     }
   else
     gcc_unreachable();
@@ -6112,6 +6117,8 @@ 
 				     const_ptr_type_node,
 				     fold_convert(const_ptr_type_node,
 						  right_tree));
+      // This can panic if the type is uncomparable.
+      TREE_NOTHROW(interface_compare_decl) = 0;
       right_tree = build_int_cst_type(integer_type_node, 0);
     }
 
@@ -6275,6 +6282,12 @@ 
   void
   do_export(Export*) const;
 
+  virtual bool
+  do_is_recover_call() const;
+
+  virtual void
+  do_set_recover_arg(Expression*);
+
  private:
   // The builtin functions.
   enum Builtin_function_code
@@ -6292,10 +6305,10 @@ 
       BUILTIN_MAKE,
       BUILTIN_NEW,
       BUILTIN_PANIC,
-      BUILTIN_PANICLN,
       BUILTIN_PRINT,
       BUILTIN_PRINTLN,
       BUILTIN_REAL,
+      BUILTIN_RECOVER,
 
       // Builtin functions from the unsafe package.
       BUILTIN_ALIGNOF,
@@ -6352,14 +6365,14 @@ 
     this->code_ = BUILTIN_NEW;
   else if (name == "panic")
     this->code_ = BUILTIN_PANIC;
-  else if (name == "panicln")
-    this->code_ = BUILTIN_PANICLN;
   else if (name == "print")
     this->code_ = BUILTIN_PRINT;
   else if (name == "println")
     this->code_ = BUILTIN_PRINTLN;
   else if (name == "real")
     this->code_ = BUILTIN_REAL;
+  else if (name == "recover")
+    this->code_ = BUILTIN_RECOVER;
   else if (name == "Alignof")
     this->code_ = BUILTIN_ALIGNOF;
   else if (name == "Offsetof")
@@ -6370,11 +6383,34 @@ 
     gcc_unreachable();
 }
 
+// Return whether this is a call to recover.  This is a virtual
+// function called from the parent class.
+
+bool
+Builtin_call_expression::do_is_recover_call() const
+{
+  if (this->classification() == EXPRESSION_ERROR)
+    return false;
+  return this->code_ == BUILTIN_RECOVER;
+}
+
+// Set the argument for a call to recover.
+
+void
+Builtin_call_expression::do_set_recover_arg(Expression* arg)
+{
+  const Expression_list* args = this->args();
+  gcc_assert(args == NULL || args->empty());
+  Expression_list* new_args = new Expression_list();
+  new_args->push_back(arg);
+  this->set_args(new_args);
+}
+
 // Lower a builtin call expression.  This turns new and make into
 // specific expressions.  We also convert to a constant if we can.
 
 Expression*
-Builtin_call_expression::do_lower(Gogo*, Named_object*, int)
+Builtin_call_expression::do_lower(Gogo*, Named_object* function, int)
 {
   if (this->code_ == BUILTIN_NEW)
     {
@@ -6463,6 +6499,20 @@ 
       mpfr_clear(rval);
       mpfr_clear(imag);
     }
+  else if (this->code_ == BUILTIN_RECOVER)
+    {
+      if (function != NULL)
+	function->func_value()->set_calls_recover();
+      else
+	{
+	  // Calling recover outside of a function always returns the
+	  // nil empty interface.
+	  Type* eface = Type::make_interface_type(NULL, this->location());
+	  return Expression::make_cast(eface,
+				       Expression::make_nil(this->location()),
+				       this->location());
+	}
+    }
 
   return this;
 }
@@ -6827,7 +6877,6 @@ 
 
     case BUILTIN_CLOSE:
     case BUILTIN_PANIC:
-    case BUILTIN_PANICLN:
     case BUILTIN_PRINT:
     case BUILTIN_PRINTLN:
       return Type::make_void_type();
@@ -6835,6 +6884,9 @@ 
     case BUILTIN_CLOSED:
       return Type::lookup_bool_type();
 
+    case BUILTIN_RECOVER:
+      return Type::make_interface_type(NULL, BUILTINS_LOCATION);
+
     case BUILTIN_REAL:
     case BUILTIN_IMAG:
       {
@@ -6881,8 +6933,6 @@ 
   Type* arg_type = NULL;
   switch (this->code_)
     {
-    case BUILTIN_PANIC:
-    case BUILTIN_PANICLN:
     case BUILTIN_PRINT:
     case BUILTIN_PRINTLN:
       // Do not force a large integer constant to "int".
@@ -7025,8 +7075,6 @@ 
       }
       break;
 
-    case BUILTIN_PANIC:
-    case BUILTIN_PANICLN:
     case BUILTIN_PRINT:
     case BUILTIN_PRINTLN:
       {
@@ -7077,11 +7125,17 @@ 
 	}
       break;
 
+    case BUILTIN_PANIC:
     case BUILTIN_SIZEOF:
     case BUILTIN_ALIGNOF:
       this->check_one_arg();
       break;
 
+    case BUILTIN_RECOVER:
+      if (this->args() != NULL && !this->args()->empty())
+	this->report_error(_("too many arguments"));
+      break;
+
     case BUILTIN_OFFSETOF:
       if (this->check_one_arg())
 	{
@@ -7266,19 +7320,12 @@ 
 	  return fold(convert_to_integer(type_tree, val_tree));
       }
 
-    case BUILTIN_PANIC:
-    case BUILTIN_PANICLN:
     case BUILTIN_PRINT:
     case BUILTIN_PRINTLN:
       {
-	const bool is_panic = (this->code_ == BUILTIN_PANIC
-			       || this->code_ == BUILTIN_PANICLN);
-	const bool is_ln = (this->code_ == BUILTIN_PANICLN
-			    || this->code_ == BUILTIN_PRINTLN);
+	const bool is_ln = this->code_ == BUILTIN_PRINTLN;
 	tree stmt_list = NULL_TREE;
 
-	tree panic_arg = is_panic ? boolean_true_node : boolean_false_node;
-
 	const Expression_list* call_args = this->args();
 	if (call_args != NULL)
 	  {
@@ -7292,10 +7339,8 @@ 
 		    tree call = Gogo::call_builtin(&print_space_fndecl,
 						   location,
 						   "__go_print_space",
-						   1,
-						   void_type_node,
-						   boolean_type_node,
-						   panic_arg);
+						   0,
+						   void_type_node);
 		    append_to_statement_list(call, &stmt_list);
 		  }
 
@@ -7376,10 +7421,8 @@ 
 		tree call = Gogo::call_builtin(pfndecl,
 					       location,
 					       fnname,
-					       2,
+					       1,
 					       void_type_node,
-					       boolean_type_node,
-					       panic_arg,
 					       TREE_TYPE(arg),
 					       arg);
 		append_to_statement_list(call, &stmt_list);
@@ -7392,29 +7435,90 @@ 
 	    tree call = Gogo::call_builtin(&print_nl_fndecl,
 					   location,
 					   "__go_print_nl",
-					   1,
-					   void_type_node,
-					   boolean_type_node,
-					   panic_arg);
-	    append_to_statement_list(call, &stmt_list);
-	  }
-
-	if (is_panic)
-	  {
-	    static tree panic_fndecl;
-	    tree call = Gogo::call_builtin(&panic_fndecl,
-					   location,
-					   "__go_panic",
 					   0,
 					   void_type_node);
-	    // Mark the function as not returning.
-	    TREE_THIS_VOLATILE(panic_fndecl) = 1;
 	    append_to_statement_list(call, &stmt_list);
 	  }
-	  
+
 	return stmt_list;
       }
 
+    case BUILTIN_PANIC:
+      {
+	const Expression_list* args = this->args();
+	gcc_assert(args != NULL && args->size() == 1);
+	Expression* arg = args->front();
+	tree arg_tree = arg->get_tree(context);
+	if (arg_tree == error_mark_node)
+	  return error_mark_node;
+	Type *empty = Type::make_interface_type(NULL, BUILTINS_LOCATION);
+	arg_tree = Expression::convert_for_assignment(context, empty,
+						      arg->type(),
+						      arg_tree, location);
+	static tree panic_fndecl;
+	tree call = Gogo::call_builtin(&panic_fndecl,
+				       location,
+				       "__go_panic",
+				       1,
+				       void_type_node,
+				       TREE_TYPE(arg_tree),
+				       arg_tree);
+	// This function will throw an exception.
+	TREE_NOTHROW(panic_fndecl) = 0;
+	// This function will not return.
+	TREE_THIS_VOLATILE(panic_fndecl) = 1;
+	return call;
+      }
+
+    case BUILTIN_RECOVER:
+      {
+	// The argument is set when building recover thunks.  It's a
+	// boolean value which is true if we can recover a value now.
+	const Expression_list* args = this->args();
+	gcc_assert(args != NULL && args->size() == 1);
+	Expression* arg = args->front();
+	tree arg_tree = arg->get_tree(context);
+	if (arg_tree == error_mark_node)
+	  return error_mark_node;
+
+	Type *empty = Type::make_interface_type(NULL, BUILTINS_LOCATION);
+	tree empty_tree = empty->get_tree(context->gogo());
+
+	Type* nil_type = Type::make_nil_type();
+	Expression* nil = Expression::make_nil(location);
+	tree nil_tree = nil->get_tree(context);
+	tree empty_nil_tree = Expression::convert_for_assignment(context,
+								 empty,
+								 nil_type,
+								 nil_tree,
+								 location);
+
+	// We need to handle a deferred call to recover specially,
+	// because it changes whether it can recover a panic or not.
+	// See test7 in test/recover1.go.
+	tree call;
+	if (this->is_deferred())
+	  {
+	    static tree deferred_recover_fndecl;
+	    call = Gogo::call_builtin(&deferred_recover_fndecl,
+				      location,
+				      "__go_deferred_recover",
+				      0,
+				      empty_tree);
+	  }
+	else
+	  {
+	    static tree recover_fndecl;
+	    call = Gogo::call_builtin(&recover_fndecl,
+				      location,
+				      "__go_recover",
+				      0,
+				      empty_tree);
+	  }
+	return fold_build3_loc(location, COND_EXPR, empty_tree, arg_tree,
+			       call, empty_nil_tree);
+      }
+
     case BUILTIN_CLOSE:
     case BUILTIN_CLOSED:
       {
@@ -7949,6 +8053,36 @@ 
   return fntype->results()->size();
 }
 
+// Return whether this is a call to the predeclared function recover.
+
+bool
+Call_expression::is_recover_call() const
+{
+  return this->do_is_recover_call();
+}
+
+// Set the argument to the recover function.
+
+void
+Call_expression::set_recover_arg(Expression* arg)
+{
+  this->do_set_recover_arg(arg);
+}
+
+// Virtual functions also implemented by Builtin_call_expression.
+
+bool
+Call_expression::do_is_recover_call() const
+{
+  return false;
+}
+
+void
+Call_expression::do_set_recover_arg(Expression*)
+{
+  gcc_unreachable();
+}
+
 // Get the type.
 
 Type*
@@ -8940,6 +9074,7 @@ 
 				  "__go_bad_index",
 				  0,
 				  void_type_node);
+  TREE_NOTHROW(bad_index_fndecl) = 0;
   TREE_THIS_VOLATILE(bad_index_fndecl) = 1;
 
   if (this->end_ == NULL)
@@ -9274,6 +9409,7 @@ 
 				      "__go_bad_index",
 				      0,
 				      void_type_node);
+      TREE_NOTHROW(bad_index_fndecl) = 0;
       TREE_THIS_VOLATILE(bad_index_fndecl) = 1;
 
       tree bytes_tree = String_type::bytes_tree(context->gogo(), string_tree);
@@ -9300,17 +9436,21 @@ 
 	}
       end_tree = fold_convert(integer_type_node, end_tree);
       static tree strslice_fndecl;
-      return Gogo::call_builtin(&strslice_fndecl,
-				this->location(),
-				"__go_string_slice",
-				3,
-				string_type,
-				string_type,
-				string_tree,
-				integer_type_node,
-				start_tree,
-				integer_type_node,
-				end_tree);
+      tree ret = Gogo::call_builtin(&strslice_fndecl,
+				    this->location(),
+				    "__go_string_slice",
+				    3,
+				    string_type,
+				    string_type,
+				    string_tree,
+				    integer_type_node,
+				    start_tree,
+				    integer_type_node,
+				    end_tree);
+      // This will panic if the bounds are out of range for the
+      // string.
+      TREE_NOTHROW(strslice_fndecl) = 0;
+      return ret;
     }
 }
 
@@ -9517,6 +9657,9 @@ 
 				 (insert
 				  ? boolean_true_node
 				  : boolean_false_node));
+  // This can panic on a map of interface type if the interface holds
+  // an uncomparable or unhashable type.
+  TREE_NOTHROW(map_index_fndecl) = 0;
 
   tree val_type_tree = type->val_type()->get_tree(context->gogo());
   if (val_type_tree == error_mark_node)
@@ -9645,6 +9788,7 @@ 
 					  "__go_bad_index",
 					  0,
 					  void_type_node);
+	  TREE_NOTHROW(bad_index_fndecl) = 0;
 	  TREE_THIS_VOLATILE(bad_index_fndecl) = 1;
 	  struct_tree = build2(COMPOUND_EXPR, TREE_TYPE(struct_tree),
 			       build3(COND_EXPR, void_type_node, compare,
@@ -10001,8 +10145,8 @@ 
 	  for (size_t i = 0; i < count; ++i)
 	    retvals->push_back(Expression::make_call_result(call, i));
 	}
-      s = Statement::make_return_statement(no->func_value(), retvals,
-					   location);
+      s = Statement::make_return_statement(no->func_value()->type()->results(),
+					   retvals, location);
     }
   gogo->add_statement(s);
 
@@ -12392,6 +12536,46 @@ 
   return new Type_descriptor_expression(type, location);
 }
 
+// An expression which evaluates to the address of an unnamed label.
+
+class Label_addr_expression : public Expression
+{
+ public:
+  Label_addr_expression(Label* label, source_location location)
+    : Expression(EXPRESSION_LABEL_ADDR, location),
+      label_(label)
+  { }
+
+ protected:
+  Type*
+  do_type()
+  { return Type::make_pointer_type(Type::make_void_type()); }
+
+  void
+  do_determine_type(const Type_context*)
+  { }
+
+  Expression*
+  do_copy()
+  { return new Label_addr_expression(this->label_, this->location()); }
+
+  tree
+  do_get_tree(Translate_context*)
+  { return this->label_->get_addr(this->location()); }
+
+ private:
+  // The label whose address we are taking.
+  Label* label_;
+};
+
+// Make an expression for the address of an unnamed label.
+
+Expression*
+Expression::make_label_addr(Label* label, source_location location)
+{
+  return new Label_addr_expression(label, location);
+}
+
 // Make a reference count decrement of an lvalue.
 
 Expression*
diff -r da79ac110978 go/expressions.h
--- a/go/expressions.h	Sun Jun 13 23:06:30 2010 -0700
+++ b/go/expressions.h	Sun Jun 13 23:07:43 2010 -0700
@@ -43,6 +43,7 @@ 
 class Temporary_statement;
 class Refcounts;
 class Refcount_entry;
+class Label;
 
 // The base class for all expressions.
 
@@ -93,7 +94,8 @@ 
     EXPRESSION_SEND,
     EXPRESSION_REFCOUNT_ADJUST,
     EXPRESSION_REFCOUNT_DECREMENT_LVALUE,
-    EXPRESSION_TYPE_DESCRIPTOR
+    EXPRESSION_TYPE_DESCRIPTOR,
+    EXPRESSION_LABEL_ADDR
   };
 
   Expression(Expression_classification, source_location);
@@ -248,7 +250,7 @@ 
   static Expression*
   make_cast(Type*, Expression*, source_location);
 
-  // Make a composit_literal.
+  // Make a composite literal.
   static Expression*
   make_composite_literal(Type*, bool has_keys, Expression_list*,
 			 source_location);
@@ -288,6 +290,11 @@ 
   static Expression*
   make_type_descriptor(Type* type, source_location);
 
+  // Make an expression which evaluates to the address of an unnamed
+  // label.
+  static Expression*
+  make_label_addr(Label*, source_location);
+
   // Return the expression classification.
   Expression_classification
   classification() const
@@ -1203,7 +1210,7 @@ 
     : Expression(EXPRESSION_CALL, location),
       fn_(fn), args_(args), type_(NULL), tree_(NULL), refcount_entries_(NULL),
       is_value_discarded_(false), varargs_are_lowered_(false),
-      is_being_copied_(false)
+      is_being_copied_(false), is_deferred_(false)
   { }
 
   // The function to call.
@@ -1228,6 +1235,25 @@ 
   size_t
   result_count() const;
 
+  // Return whether this is a call to the predeclared function
+  // recover.
+  bool
+  is_recover_call() const;
+
+  // Set the argument for a call to recover.
+  void
+  set_recover_arg(Expression*);
+
+  // Whether this call is being deferred.
+  bool
+  is_deferred() const
+  { return this->is_deferred_; }
+
+  // Note that the call is being deferred.
+  void
+  set_is_deferred()
+  { this->is_deferred_ = true; }
+
  protected:
   int
   do_traverse(Traverse*);
@@ -1267,6 +1293,17 @@ 
   virtual tree
   do_get_tree(Translate_context*);
 
+  virtual bool
+  do_is_recover_call() const;
+
+  virtual void
+  do_set_recover_arg(Expression*);
+
+  // Let a builtin expression change the argument list.
+  void
+  set_args(Expression_list* args)
+  { this->args_ = args; }
+
  private:
   Expression*
   lower_varargs(Gogo*, Named_object*);
@@ -1307,6 +1344,8 @@ 
   bool varargs_are_lowered_;
   // True if the value is being copied.
   bool is_being_copied_;
+  // True if the call is an argument to a defer statement.
+  bool is_deferred_;
 };
 
 // An expression which represents a pointer to a function.
diff -r da79ac110978 go/go-lang.c
--- a/go/go-lang.c	Sun Jun 13 23:06:30 2010 -0700
+++ b/go/go-lang.c	Sun Jun 13 23:07:43 2010 -0700
@@ -20,6 +20,7 @@ 
 #include "diagnostic.h"
 #include "langhooks.h"
 #include "langhooks-def.h"
+#include "except.h"
 #include "target.h"
 
 #include <mpfr.h>
@@ -133,6 +134,10 @@ 
   if (targetm.supports_split_stack (false))
     flag_split_stack = 1;
 
+  /* Exceptions are used to handle recovering from panics.  */
+  flag_exceptions = 1;
+  using_eh_for_cleanups ();
+
   return CL_Go;
 }
 
@@ -262,6 +267,24 @@ 
   return GS_UNHANDLED;
 }
 
+/* Return a decl for the exception personality function.  The function
+   itself is implemented in libgo/runtime/go-unwind.c.  */
+
+static tree
+go_langhook_eh_personality (void)
+{
+  static tree personality_decl;
+  if (personality_decl == NULL_TREE)
+    {
+      const char* name = (USING_SJLJ_EXCEPTIONS
+			  ? "__gccgo_personality_sj0"
+			  : "__gccgo_personality_v0");
+      personality_decl = build_personality_function (name);
+      go_preserve_from_gc (personality_decl);
+    }
+  return personality_decl;
+}
+
 /* Functions called directly by the generic backend.  */
 
 tree
@@ -323,6 +346,7 @@ 
 #undef LANG_HOOKS_GETDECLS
 #undef LANG_HOOKS_WRITE_GLOBALS
 #undef LANG_HOOKS_GIMPLIFY_EXPR
+#undef LANG_HOOKS_EH_PERSONALITY
 
 #define LANG_HOOKS_NAME			"GNU Go"
 #define LANG_HOOKS_INIT			go_langhook_init
@@ -338,6 +362,7 @@ 
 #define LANG_HOOKS_GETDECLS		go_langhook_getdecls
 #define LANG_HOOKS_WRITE_GLOBALS	go_langhook_write_globals
 #define LANG_HOOKS_GIMPLIFY_EXPR	go_langhook_gimplify_expr
+#define LANG_HOOKS_EH_PERSONALITY	go_langhook_eh_personality
 
 struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;
 
diff -r da79ac110978 go/go.cc
--- a/go/go.cc	Sun Jun 13 23:06:30 2010 -0700
+++ b/go/go.cc	Sun Jun 13 23:07:43 2010 -0700
@@ -130,6 +130,9 @@ 
   // Use temporary variables to force order of evaluation.
   ::gogo->order_evaluations();
 
+  // Build thunks for functions which call recover.
+  ::gogo->build_recover_thunks();
+
   // Convert complicated go and defer statements into simpler ones.
   ::gogo->simplify_thunk_statements();
 
diff -r da79ac110978 go/gogo-tree.cc
--- a/go/gogo-tree.cc	Sun Jun 13 23:06:30 2010 -0700
+++ b/go/gogo-tree.cc	Sun Jun 13 23:07:43 2010 -0700
@@ -133,6 +133,20 @@ 
 					  long_double_type_node,
 					  NULL_TREE),
 		 true);
+
+  // We use __builtin_return_address in the thunk we build for
+  // functions which call recover.
+  define_builtin(BUILT_IN_RETURN_ADDRESS, "__builtin_return_address", NULL,
+		 build_function_type_list(ptr_type_node,
+					  unsigned_type_node,
+					  NULL_TREE),
+		 false);
+
+  // The compiler uses __builtin_trap for some exception handling
+  // cases.
+  define_builtin(BUILT_IN_TRAP, "__builtin_trap", NULL,
+		 build_function_type(void_type_node, void_list_node),
+		 false);
 }
 
 // Get the name to use for the import control function.  If there is a
@@ -731,16 +745,17 @@ 
 {
   if (this->tree_ != NULL_TREE)
     {
-      // If this is a local variable whose address is taken, we must
-      // rebuild the INDIRECT_REF each time to avoid invalid sharing.
+      // If this is a variable whose address is taken, we must rebuild
+      // the INDIRECT_REF each time to avoid invalid sharing.
       tree ret = this->tree_;
-      if (this->classification_ == NAMED_OBJECT_VAR
-	  && this->var_value()->is_in_heap()
+      if (((this->classification_ == NAMED_OBJECT_VAR
+	    && this->var_value()->is_in_heap())
+	   || (this->classification_ == NAMED_OBJECT_RESULT_VAR
+	       && this->result_var_value()->is_in_heap()))
 	  && ret != error_mark_node)
 	{
 	  gcc_assert(TREE_CODE(ret) == INDIRECT_REF);
-	  ret = build_fold_indirect_ref_loc(this->location(),
-					    TREE_OPERAND(ret, 0));
+	  ret = build_fold_indirect_ref(TREE_OPERAND(ret, 0));
 	}
       return ret;
     }
@@ -886,25 +901,48 @@ 
     case NAMED_OBJECT_RESULT_VAR:
       {
 	Result_variable* result = this->u_.result_var_value;
-	int index = result->index();
-
-	Function* function = result->function();
-	tree return_value = function->return_value();
-	const Typed_identifier_list* results = function->type()->results();
-	if (results->size() == 1)
-	  {
-	    gcc_assert(index == 0);
-	    return return_value;
+	Type* type = result->type();
+	if (type->is_error_type() || type->is_undefined())
+	  {
+	    // Force the error.
+	    type->base();
+	    decl = error_mark_node;
 	  }
 	else
 	  {
-	    tree field;
-	    for (field = TYPE_FIELDS(TREE_TYPE(return_value));
-		 index > 0;
-		 --index, field = TREE_CHAIN(field))
-	      gcc_assert(field != NULL_TREE);
-	    return build3(COMPONENT_REF, TREE_TYPE(field), return_value,
-			  field, NULL_TREE);
+	    gcc_assert(result->function() == function->func_value());
+	    source_location loc = function->location();
+	    tree result_type = type->get_tree(gogo);
+	    tree init;
+	    if (!result->is_in_heap())
+	      init = type->get_init_tree(gogo, false);
+	    else
+	      {
+		result_type = build_pointer_type(result_type);
+		tree space = gogo->allocate_memory(TYPE_SIZE_UNIT(result_type),
+						   loc);
+		tree subinit = type->get_init_tree(gogo, true);
+		if (subinit == NULL_TREE)
+		  init = fold_convert_loc(loc, result_type, space);
+		else
+		  {
+		    space = save_expr(space);
+		    space = fold_convert_loc(loc, result_type, space);
+		    tree spaceref = build_fold_indirect_ref_loc(loc, space);
+		    tree set = fold_build2_loc(loc, MODIFY_EXPR, void_type_node,
+					       spaceref, subinit);
+		    init = fold_build2_loc(loc, COMPOUND_EXPR, TREE_TYPE(space),
+					   set, space);
+		  }
+	      }
+	    decl = build_decl(loc, VAR_DECL, name, result_type);
+	    tree fnid = function->get_id(gogo);
+	    tree fndecl = function->func_value()->get_or_make_decl(gogo,
+								   function,
+								   fnid);
+	    DECL_CONTEXT(decl) = fndecl;
+	    DECL_INITIAL(decl) = init;
+	    TREE_USED(decl) = 1;
 	  }
       }
       break;
@@ -954,8 +992,10 @@ 
   // If this is a local variable whose address is taken, then we
   // actually store it in the heap.  For uses of the variable we need
   // to return a reference to that heap location.
-  if (this->classification_ == NAMED_OBJECT_VAR
-      && this->var_value()->is_in_heap()
+  if (((this->classification_ == NAMED_OBJECT_VAR
+	&& this->var_value()->is_in_heap())
+       || (this->classification_ == NAMED_OBJECT_RESULT_VAR
+	   && this->result_var_value()->is_in_heap()))
       && ret != error_mark_node)
     {
       gcc_assert(POINTER_TYPE_P(TREE_TYPE(ret)));
@@ -1002,14 +1042,16 @@ 
   gcc_assert(this->preinit_ != NULL);
 
   // We want to add the variable assignment to the end of the preinit
-  // block.  The preinit block may have a TRY_FINALLY_EXPR; if it
-  // does, we want to add to the end of the regular statements.
+  // block.  The preinit block may have a TRY_FINALLY_EXPR and a
+  // TRY_CATCH_EXPR; if it does, we want to add to the end of the
+  // regular statements.
 
   Translate_context context(gogo, function, NULL, NULL_TREE);
   tree block_tree = this->preinit_->get_tree(&context);
   gcc_assert(TREE_CODE(block_tree) == BIND_EXPR);
   tree statements = BIND_EXPR_BODY(block_tree);
-  if (TREE_CODE(statements) == TRY_FINALLY_EXPR)
+  while (TREE_CODE(statements) == TRY_FINALLY_EXPR
+	 || TREE_CODE(statements) == TRY_CATCH_EXPR)
     statements = TREE_OPERAND(statements, 0);
 
   // It's possible to have pre-init statements without an initializer
@@ -1091,6 +1133,21 @@ 
 	  if (this->enclosing_ != NULL)
 	    DECL_STATIC_CHAIN(decl) = 1;
 
+	  // If a function calls the predeclared recover function, we
+	  // can't inline it, because recover behaves differently in a
+	  // function passed directly to defer.
+	  if (this->calls_recover_ && !this->is_recover_thunk_)
+	    DECL_UNINLINABLE(decl) = 1;
+
+	  // If this is a thunk created to call a function which calls
+	  // the predeclared recover function, we need to disable
+	  // stack splitting for the thunk.
+	  if (this->is_recover_thunk_)
+	    {
+	      tree attr = get_identifier("__no_split_stack__");
+	      DECL_ATTRIBUTES(decl) = tree_cons(attr, NULL_TREE, NULL_TREE);
+	    }
+
 	  go_preserve_from_gc(decl);
 
 	  if (this->closure_var_ != NULL)
@@ -1275,17 +1332,6 @@ 
   tree params = NULL_TREE;
   tree* pp = &params;
 
-  // If we have named return values, we allocate a tree to hold them
-  // in case there are any return statements which don't mention any
-  // expressions.  We can't just use DECL_RESULT because it might be a
-  // list of registers.
-  const Typed_identifier_list* results = this->type_->results();
-  if (results != NULL
-      && !results->empty()
-      && !results->front().name().empty())
-    this->return_value_ = create_tmp_var(TREE_TYPE(TREE_TYPE(fndecl)),
-					 "RETURN");
-
   tree declare_vars = NULL_TREE;
   for (Bindings::const_definitions_iterator p =
 	 this->block_->bindings()->begin_definitions();
@@ -1330,6 +1376,18 @@ 
 	      pp = &TREE_CHAIN(*pp);
 	    }
 	}
+      else if ((*p)->is_result_variable())
+	{
+	  tree var_decl = (*p)->get_tree(gogo, named_function);
+	  if ((*p)->result_var_value()->is_in_heap())
+	    {
+	      gcc_assert(TREE_CODE(var_decl) == INDIRECT_REF);
+	      var_decl = TREE_OPERAND(var_decl, 0);
+	    }
+	  gcc_assert(TREE_CODE(var_decl) == VAR_DECL);
+	  TREE_CHAIN(var_decl) = declare_vars;
+	  declare_vars = var_decl;
+	}
     }
   *pp = NULL_TREE;
 
@@ -1358,6 +1416,7 @@ 
       tree code = this->block_->get_tree(&context);
 
       tree init = NULL_TREE;
+      tree except = NULL_TREE;
       tree fini = NULL_TREE;
       source_location end_loc = this->block_->end_location();
 
@@ -1366,10 +1425,7 @@ 
 	{
 	  tree dv = build1(DECL_EXPR, void_type_node, v);
 	  SET_EXPR_LOCATION(dv, DECL_SOURCE_LOCATION(v));
-	  if (init == NULL_TREE)
-	    init = dv;
-	  else
-	    init = build2(COMPOUND_EXPR, void_type_node, init, dv);
+	  append_to_statement_list(dv, &init);
 	}
 
       // If there is a reference count queue, initialize it at the
@@ -1379,11 +1435,13 @@ 
       if (have_refcounts)
 	{
 	  tree iq = this->refcounts_->init_queue(gogo, this->location_);
-	  if (init == NULL_TREE)
-	    init = iq;
-	  else
-	    init = build2(COMPOUND_EXPR, void_type_node, init, iq);
-	}
+	  append_to_statement_list(iq, &init);
+	}
+
+      // Flush the reference count queue when we leave the function.
+      tree flush = NULL_TREE;
+      if (have_refcounts)
+	flush = this->refcounts_->flush_queue(gogo, true, end_loc);
 
       // If we have a defer stack, initialize it at the start of a
       // function.
@@ -1391,69 +1449,219 @@ 
 	{
 	  tree defer_init = build1(DECL_EXPR, void_type_node,
 				   this->defer_stack_);
-	  if (init == NULL_TREE)
-	    init = defer_init;
-	  else
-	    init = build2(COMPOUND_EXPR, void_type_node, init, defer_init);
-	}
-
-      // Clean up the defer stack when we leave the function.
-      if (this->defer_stack_ != NULL_TREE)
+	  SET_EXPR_LOCATION(defer_init, this->block_->start_location());
+	  append_to_statement_list(defer_init, &init);
+
+	  // Clean up the defer stack when we leave the function.
+	  this->build_defer_wrapper(gogo, named_function, flush, &except,
+				    &fini);
+	  flush = NULL_TREE;
+	}
+
+      if (flush != NULL_TREE)
 	{
 	  gcc_assert(fini == NULL_TREE);
-	  static tree undefer_fndecl;
-	  fini = Gogo::call_builtin(&undefer_fndecl,
+	  fini = flush;
+	}
+
+      if (code != NULL_TREE && code != error_mark_node)
+	{
+	  if (init != NULL_TREE)
+	    code = build2(COMPOUND_EXPR, void_type_node, init, code);
+	  if (except != NULL_TREE)
+	    code = build2(TRY_CATCH_EXPR, void_type_node, code,
+			  build2(CATCH_EXPR, void_type_node, NULL, except));
+	  if (fini != NULL_TREE)
+	    code = build2(TRY_FINALLY_EXPR, void_type_node, code, fini);
+	}
+
+      // Stick the code into the block we built for the receiver, if
+      // we built on.
+      if (bind != NULL_TREE && code != NULL_TREE && code != error_mark_node)
+	{
+	  BIND_EXPR_BODY(bind) = code;
+	  code = bind;
+	}
+
+      DECL_SAVED_TREE(fndecl) = code;
+    }
+}
+
+// Build the wrappers around function code needed if the function has
+// any defer statements.  This sets *EXCEPT to an exception handler
+// and *FINI to a finally handler.  FLUSH is run in *FINI if not NULL.
+
+void
+Function::build_defer_wrapper(Gogo* gogo, Named_object* named_function,
+			      tree flush, tree *except, tree *fini)
+{
+  source_location end_loc = this->block_->end_location();
+
+  // Add an exception handler.  This is used if a panic occurs.  Its
+  // purpose is to stop the stack unwinding if a deferred function
+  // calls recover.  There are more details in
+  // libgo/runtime/go-unwind.c.
+  tree stmt_list = NULL_TREE;
+  static tree check_fndecl;
+  tree call = Gogo::call_builtin(&check_fndecl,
+				 end_loc,
+				 "__go_check_defer",
+				 1,
+				 void_type_node,
+				 ptr_type_node,
+				 this->defer_stack(end_loc));
+  append_to_statement_list(call, &stmt_list);
+
+  tree retval = this->return_value(gogo, named_function, end_loc, &stmt_list);
+  tree set;
+  if (retval == NULL_TREE)
+    set = NULL_TREE;
+  else
+    set = fold_build2_loc(end_loc, MODIFY_EXPR, void_type_node,
+			  DECL_RESULT(this->fndecl_), retval);
+  tree ret_stmt = fold_build1_loc(end_loc, RETURN_EXPR, void_type_node, set);
+  append_to_statement_list(ret_stmt, &stmt_list);
+
+  gcc_assert(*except == NULL_TREE);
+  *except = stmt_list;
+
+  // Add some finally code to run the defer functions.  This is used
+  // both in the normal case, when no panic occurs, and also if a
+  // panic occurs to run any further defer functions.  Of course, it
+  // is possible for a defer function to call panic which should be
+  // caught by another defer function.  To handle that we use a loop.
+  //  finish:
+  //   try { __go_undefer(); } catch { __go_check_defer(); goto finish; }
+  //   if (return values are named) return named_vals;
+
+  stmt_list = NULL;
+
+  tree label = create_artificial_label(end_loc);
+  tree define_label = fold_build1_loc(end_loc, LABEL_EXPR, void_type_node,
+				      label);
+  append_to_statement_list(define_label, &stmt_list);
+
+  static tree undefer_fndecl;
+  tree undefer = Gogo::call_builtin(&undefer_fndecl,
 				    end_loc,
 				    "__go_undefer",
 				    1,
 				    void_type_node,
 				    ptr_type_node,
-				    this->defer_stack_);
-	}
-
-      // Flush the reference count queue when we leave the function.
-      if (have_refcounts)
-	{
-	  tree flush = this->refcounts_->flush_queue(gogo, true, end_loc);
-	  if (fini == NULL_TREE)
-	    fini = flush;
+				    this->defer_stack(end_loc));
+  TREE_NOTHROW(undefer_fndecl) = 0;
+
+  tree defer = Gogo::call_builtin(&check_fndecl,
+				  end_loc,
+				  "__go_check_defer",
+				  1,
+				  void_type_node,
+				  ptr_type_node,
+				  this->defer_stack(end_loc));
+  tree jump = fold_build1_loc(end_loc, GOTO_EXPR, void_type_node, label);
+  tree catch_body = build2(COMPOUND_EXPR, void_type_node, defer, jump);
+  catch_body = build2(CATCH_EXPR, void_type_node, NULL, catch_body);
+  tree try_catch = build2(TRY_CATCH_EXPR, void_type_node, undefer, catch_body);
+
+  append_to_statement_list(try_catch, &stmt_list);
+
+  if (flush != NULL_TREE)
+    append_to_statement_list(flush, &stmt_list);
+
+  if (this->type_->results() != NULL
+      && !this->type_->results()->empty()
+      && !this->type_->results()->front().name().empty())
+    {
+      // If the result variables are named, we need to return them
+      // again, because they might have been changed by a defer
+      // function.
+      retval = this->return_value(gogo, named_function, end_loc,
+				  &stmt_list);
+      set = fold_build2_loc(end_loc, MODIFY_EXPR, void_type_node,
+			    DECL_RESULT(this->fndecl_), retval);
+      ret_stmt = fold_build1_loc(end_loc, RETURN_EXPR, void_type_node, set);
+      append_to_statement_list(ret_stmt, &stmt_list);
+    }
+  
+  gcc_assert(*fini == NULL_TREE);
+  *fini = stmt_list;
+}
+
+// Return the value to assign to DECL_RESULT(this->fndecl_).  This may
+// also add statements to STMT_LIST, which need to be executed before
+// the assignment.  This is used for a return statement with no
+// explicit values.
+
+tree
+Function::return_value(Gogo* gogo, Named_object* named_function,
+		       source_location location, tree* stmt_list) const
+{
+  const Typed_identifier_list* results = this->type_->results();
+  if (results == NULL || results->empty())
+    return NULL_TREE;
+
+  // In the case of an exception handler created for functions with
+  // defer statements, the result variables may be unnamed.
+  bool is_named = !results->front().name().empty();
+  if (is_named)
+    gcc_assert(this->named_results_ != NULL
+	       && this->named_results_->size() == results->size());
+
+  tree retval;
+  if (results->size() == 1)
+    {
+      if (is_named)
+	return this->named_results_->front()->get_tree(gogo, named_function);
+      else
+	return results->front().type()->get_init_tree(gogo, false);
+    }
+  else
+    {
+      tree rettype = TREE_TYPE(DECL_RESULT(this->fndecl_));
+      retval = create_tmp_var(rettype, "RESULT");
+      tree field = TYPE_FIELDS(rettype);
+      int index = 0;
+      for (Typed_identifier_list::const_iterator pr = results->begin();
+	   pr != results->end();
+	   ++pr, ++index, field = TREE_CHAIN(field))
+	{
+	  gcc_assert(field != NULL);
+	  tree val;
+	  if (is_named)
+	    val = (*this->named_results_)[index]->get_tree(gogo,
+							   named_function);
 	  else
-	    fini = build2(COMPOUND_EXPR, void_type_node, fini, flush);
-	}
-
-      if (code != NULL_TREE && code != error_mark_node)
-	{
-	  if (init != NULL_TREE)
-	    code = build2(COMPOUND_EXPR, void_type_node, init, code);
-	  if (fini != NULL_TREE)
-	    code = build2(TRY_FINALLY_EXPR, void_type_node, code, fini);
-	}
-
-      // Stick the code into the block we built for the receiver, if
-      // we built on.
-      if (bind != NULL_TREE && code != NULL_TREE && code != error_mark_node)
-	{
-	  BIND_EXPR_BODY(bind) = code;
-	  code = bind;
-	}
-
-      DECL_SAVED_TREE(fndecl) = code;
+	    val = pr->type()->get_init_tree(gogo, false);
+	  tree set = fold_build2_loc(location, MODIFY_EXPR, void_type_node,
+				     build3(COMPONENT_REF, TREE_TYPE(field),
+					    retval, field, NULL_TREE),
+				     val);
+	  append_to_statement_list(set, stmt_list);
+	}
+      return retval;
     }
 }
 
 // Get the tree for the variable holding the defer stack for this
-// function.
-
-tree
-Function::defer_stack()
+// function.  At least at present, the value of this variable is not
+// used.  However, a pointer to this variable is used as a marker for
+// the functions on the defer stack associated with this function.
+// Doing things this way permits inlining a function which uses defer.
+
+tree
+Function::defer_stack(source_location location)
 {
   if (this->defer_stack_ == NULL_TREE)
     {
       tree var = create_tmp_var(ptr_type_node, "DEFER");
       DECL_INITIAL(var) = null_pointer_node;
+      DECL_SOURCE_LOCATION(var) = location;
+      TREE_ADDRESSABLE(var) = 1;
       this->defer_stack_ = var;
     }
-  return this->defer_stack_;
+  return fold_convert_loc(location, ptr_type_node,
+			  build_fold_addr_expr_loc(location,
+						   this->defer_stack_));
 }
 
 // Get a tree for the statements in a block.
@@ -1535,25 +1743,6 @@ 
 
   tree statements = NULL_TREE;
 
-  // Named result variables--the only sort of result variable we will
-  // see in the bindings--must be explicitly zero-initialized.  Since
-  // these are not regular DECLs, this is not done anywhere else.
-  for (Bindings::const_definitions_iterator pv =
-	 this->bindings_->begin_definitions();
-       pv != this->bindings_->end_definitions();
-       ++pv)
-    {
-      if ((*pv)->is_result_variable())
-	{
-	  Result_variable* rv = (*pv)->result_var_value();
-	  tree init_tree = rv->type()->get_init_tree(gogo, false);
-	  tree statement = build2(MODIFY_EXPR, void_type_node,
-				  (*pv)->get_tree(gogo, context->function()),
-				  init_tree);
-	  append_to_statement_list(statement, &statements);
-	}
-    }
-
   // Expand the statements.
 
   for (std::vector<Statement*>::const_iterator p = this->statements_.begin();
@@ -1613,6 +1802,18 @@ 
   return this->decl_;
 }
 
+// Return an expression for the address of this label.
+
+tree
+Label::get_addr(source_location location)
+{
+  tree decl = this->get_decl();
+  TREE_USED(decl) = 1;
+  TREE_ADDRESSABLE(decl) = 1;
+  return fold_convert_loc(location, ptr_type_node,
+			  build_fold_addr_expr_loc(location, decl));
+}
+
 // Get the LABEL_DECL for an unnamed label.
 
 tree
@@ -3787,33 +3988,41 @@ 
       if (blocking)
 	{
 	  static tree send_small_fndecl;
-	  return Gogo::call_builtin(&send_small_fndecl,
-				    location,
-				    "__go_send_small",
-				    3,
-				    void_type_node,
-				    ptr_type_node,
-				    channel,
-				    uint64_type_node,
-				    val,
-				    boolean_type_node,
-				    (for_select
-				     ? boolean_true_node
-				     : boolean_false_node));
+	  tree ret = Gogo::call_builtin(&send_small_fndecl,
+					location,
+					"__go_send_small",
+					3,
+					void_type_node,
+					ptr_type_node,
+					channel,
+					uint64_type_node,
+					val,
+					boolean_type_node,
+					(for_select
+					 ? boolean_true_node
+					 : boolean_false_node));
+	  // This can panic if there are too many operations on a
+	  // closed channel.
+	  TREE_NOTHROW(send_small_fndecl) = 0;
+	  return ret;
 	}
       else
 	{
 	  gcc_assert(!for_select);
 	  static tree send_nonblocking_small_fndecl;
-	  return Gogo::call_builtin(&send_nonblocking_small_fndecl,
-				    location,
-				    "__go_send_nonblocking_small",
-				    2,
-				    boolean_type_node,
-				    ptr_type_node,
-				    channel,
-				    uint64_type_node,
-				    val);
+	  tree ret = Gogo::call_builtin(&send_nonblocking_small_fndecl,
+					location,
+					"__go_send_nonblocking_small",
+					2,
+					boolean_type_node,
+					ptr_type_node,
+					channel,
+					uint64_type_node,
+					val);
+	  // This can panic if there are too many operations on a
+	  // closed channel.
+	  TREE_NOTHROW(send_nonblocking_small_fndecl) = 0;
+	  return ret;
 	}
     }
   else
@@ -3855,6 +4064,9 @@ 
 				    (for_select
 				     ? boolean_true_node
 				     : boolean_false_node));
+	  // This can panic if there are too many operations on a
+	  // closed channel.
+	  TREE_NOTHROW(send_big_fndecl) = 0;
 	}
       else
 	{
@@ -3869,6 +4081,9 @@ 
 				    channel,
 				    ptr_type_node,
 				    val);
+	  // This can panic if there are too many operations on a
+	  // closed channel.
+	  TREE_NOTHROW(send_nonblocking_big_fndecl) = 0;
 	}
 
       if (make_tmp == NULL_TREE)
@@ -3907,6 +4122,9 @@ 
 				     (for_select
 				      ? boolean_true_node
 				      : boolean_false_node));
+      // This can panic if there are too many operations on a closed
+      // channel.
+      TREE_NOTHROW(receive_small_fndecl) = 0;
       int bitsize = GET_MODE_BITSIZE(TYPE_MODE(type_tree));
       tree int_type_tree = go_type_for_size(bitsize, 1);
       return fold_convert_loc(location, type_tree,
@@ -3936,6 +4154,9 @@ 
 				     (for_select
 				      ? boolean_true_node
 				      : boolean_false_node));
+      // This can panic if there are too many operations on a closed
+      // channel.
+      TREE_NOTHROW(receive_big_fndecl) = 0;
       return build2(COMPOUND_EXPR, type_tree, make_tmp,
 		    build2(COMPOUND_EXPR, type_tree, call, tmp));
     }
diff -r da79ac110978 go/gogo.cc
--- a/go/gogo.cc	Sun Jun 13 23:06:30 2010 -0700
+++ b/go/gogo.cc	Sun Jun 13 23:07:43 2010 -0700
@@ -157,15 +157,21 @@ 
   print_type->set_is_builtin();
   this->globals_->add_function_declaration("println", NULL, print_type, loc);
 
-  Function_type* panic_type = Type::make_function_type(NULL, NULL, NULL, loc);
-  panic_type->set_is_varargs();
+  Type *empty = Type::make_interface_type(NULL, loc);
+  Typed_identifier_list* panic_parms = new Typed_identifier_list();
+  panic_parms->push_back(Typed_identifier("e", empty, loc));
+  Function_type *panic_type = Type::make_function_type(NULL, panic_parms,
+						       NULL, loc);
   panic_type->set_is_builtin();
   this->globals_->add_function_declaration("panic", NULL, panic_type, loc);
 
-  panic_type = Type::make_function_type(NULL, NULL, NULL, loc);
-  panic_type->set_is_varargs();
-  panic_type->set_is_builtin();
-  this->globals_->add_function_declaration("panicln", NULL, panic_type, loc);
+  Typed_identifier_list* recover_result = new Typed_identifier_list();
+  recover_result->push_back(Typed_identifier("", empty, loc));
+  Function_type* recover_type = Type::make_function_type(NULL, NULL,
+							 recover_result,
+							 loc);
+  recover_type->set_is_builtin();
+  this->globals_->add_function_declaration("recover", NULL, recover_type, loc);
 
   Function_type* close_type = Type::make_function_type(NULL, NULL, NULL, loc);
   close_type->set_is_varargs();
@@ -590,21 +596,7 @@ 
 	}
     }
 
-  const Typed_identifier_list* results = type->results();
-  if (results != NULL
-      && !results->empty()
-      && !results->front().name().empty())
-    {
-      int index = 0;
-      for (Typed_identifier_list::const_iterator p = results->begin();
-	   p != results->end();
-	   ++p, ++index)
-	{
-	  Result_variable* result = new Result_variable(p->type(), function,
-							index);
-	  block->bindings()->add_result_variable(p->name(), result);
-	}
-    }
+  function->create_named_result_variables();
 
   const std::string* pname;
   std::string nested_name;
@@ -1897,6 +1889,331 @@ 
   this->traverse(&order_eval);
 }
 
+// Traversal to convert calls to the predeclared recover function to
+// pass in an argument indicating whether it can recover from a panic
+// or not.
+
+class Convert_recover : public Traverse
+{
+ public:
+  Convert_recover(Named_object* arg)
+    : Traverse(traverse_expressions),
+      arg_(arg)
+  { }
+
+ protected:
+  int
+  expression(Expression**);
+
+ private:
+  // The argument to pass to the function.
+  Named_object* arg_;
+};
+
+// Convert calls to recover.
+
+int
+Convert_recover::expression(Expression** pp)
+{
+  Call_expression* ce = (*pp)->call_expression();
+  if (ce != NULL && ce->is_recover_call())
+    ce->set_recover_arg(Expression::make_var_reference(this->arg_,
+						       ce->location()));
+  return TRAVERSE_CONTINUE;
+}
+
+// Traversal for build_recover_thunks.
+
+class Build_recover_thunks : public Traverse
+{
+ public:
+  Build_recover_thunks(Gogo* gogo)
+    : Traverse(traverse_functions),
+      gogo_(gogo)
+  { }
+
+  int
+  function(Named_object*);
+
+ private:
+  Expression*
+  can_recover_arg(source_location);
+
+  // General IR.
+  Gogo* gogo_;
+};
+
+// If this function calls recover, turn it into a thunk.
+
+int
+Build_recover_thunks::function(Named_object* orig_no)
+{
+  Function* orig_func = orig_no->func_value();
+  if (!orig_func->calls_recover()
+      || orig_func->is_recover_thunk()
+      || orig_func->has_recover_thunk())
+    return TRAVERSE_CONTINUE;
+
+  Gogo* gogo = this->gogo_;
+  source_location location = orig_func->location();
+
+  static int count;
+  char buf[50];
+
+  Function_type* orig_fntype = orig_func->type();
+  Typed_identifier_list* new_params = new Typed_identifier_list();
+  std::string receiver_name;
+  if (orig_fntype->is_method())
+    {
+      const Typed_identifier* receiver = orig_fntype->receiver();
+      snprintf(buf, sizeof buf, "rt.%u", count);
+      ++count;
+      receiver_name = buf;
+      new_params->push_back(Typed_identifier(receiver_name, receiver->type(),
+					     receiver->location()));
+    }
+  const Typed_identifier_list* orig_params = orig_fntype->parameters();
+  if (orig_params != NULL && !orig_params->empty())
+    {
+      for (Typed_identifier_list::const_iterator p = orig_params->begin();
+	   p != orig_params->end();
+	   ++p)
+	{
+	  snprintf(buf, sizeof buf, "pt.%u", count);
+	  ++count;
+	  new_params->push_back(Typed_identifier(buf, p->type(),
+						 p->location()));
+	}
+    }
+  snprintf(buf, sizeof buf, "pr.%u", count);
+  ++count;
+  std::string can_recover_name = buf;
+  new_params->push_back(Typed_identifier(can_recover_name,
+					 Type::make_boolean_type(),
+					 orig_fntype->location()));
+
+  const Typed_identifier_list* orig_results = orig_fntype->results();
+  Typed_identifier_list* new_results;
+  if (orig_results == NULL || orig_results->empty())
+    new_results = NULL;
+  else
+    {
+      new_results = new Typed_identifier_list();
+      for (Typed_identifier_list::const_iterator p = orig_results->begin();
+	   p != orig_results->end();
+	   ++p)
+	new_results->push_back(*p);
+    }
+
+  Function_type *new_fntype = Type::make_function_type(NULL, new_params,
+						       new_results,
+						       orig_fntype->location());
+  if (orig_fntype->is_varargs())
+    new_fntype->set_is_varargs();
+
+  std::string name = orig_no->name() + "$recover";
+  Named_object *new_no = gogo->start_function(name, new_fntype, false,
+					      location);
+  Function *new_func = new_no->func_value();
+  if (orig_func->enclosing() != NULL)
+    new_func->set_enclosing(orig_func->enclosing());
+
+  // We build the code for the original function attached to the new
+  // function, and then swap the original and new function bodies.
+  // This means that existing references to the original function will
+  // then refer to the new function.  That makes this code a little
+  // confusing, in that the reference to NEW_NO really refers to the
+  // other function, not the one we are building.
+
+  Expression* closure = NULL;
+  if (orig_func->needs_closure())
+    {
+      Named_object* orig_closure_no = orig_func->closure_var();
+      Variable* orig_closure_var = orig_closure_no->var_value();
+      Variable* new_var = new Variable(orig_closure_var->type(), NULL, false,
+				       true, false, location);
+      snprintf(buf, sizeof buf, "closure.%u", count);
+      ++count;
+      Named_object* new_closure_no = Named_object::make_variable(buf, NULL,
+								 new_var);
+      new_func->set_closure_var(new_closure_no);
+      closure = Expression::make_var_reference(new_closure_no, location);
+    }
+
+  Expression* fn = Expression::make_func_reference(new_no, closure, location);
+
+  Expression_list* args = new Expression_list();
+  if (orig_fntype->is_method())
+    {
+      Named_object* rec_no = gogo->lookup(receiver_name, NULL);
+      gcc_assert(rec_no != NULL
+		 && rec_no->is_variable()
+		 && rec_no->var_value()->is_parameter());
+      args->push_back(Expression::make_var_reference(rec_no, location));
+    }
+  if (new_params != NULL)
+    {
+      // Note that we skip the last parameter, which is the boolean
+      // indicating whether recover can succed.
+      for (Typed_identifier_list::const_iterator p = new_params->begin();
+	   p + 1 != new_params->end();
+	   ++p)
+	{
+	  Named_object* p_no = gogo->lookup(p->name(), NULL);
+	  gcc_assert(p_no != NULL
+		     && p_no->is_variable()
+		     && p_no->var_value()->is_parameter());
+	  args->push_back(Expression::make_var_reference(p_no, location));
+	}
+    }
+  args->push_back(this->can_recover_arg(location));
+
+  Expression* call = Expression::make_call(fn, args, location);
+
+  Statement* s;
+  if (orig_fntype->results() == NULL || orig_fntype->results()->empty())
+    s = Statement::make_statement(call);
+  else
+    {
+      Expression_list* vals = new Expression_list();
+      vals->push_back(call);
+      s = Statement::make_return_statement(new_func->type()->results(),
+					   vals, location);
+    }
+  s->determine_types();
+  gogo->add_statement(s);
+
+  gogo->finish_function(location);
+
+  // Swap the function bodies and types.
+  new_func->swap_for_recover(orig_func);
+  orig_func->set_is_recover_thunk();
+  new_func->set_calls_recover();
+  new_func->set_has_recover_thunk();
+
+  Bindings* orig_bindings = orig_func->block()->bindings();
+  Bindings* new_bindings = new_func->block()->bindings();
+  if (orig_fntype->is_method())
+    {
+      // We changed the receiver to be a regular parameter.  We have
+      // to update the binding accordingly in both functions.
+      Named_object* orig_rec_no = orig_bindings->lookup_local(receiver_name);
+      gcc_assert(orig_rec_no != NULL
+		 && orig_rec_no->is_variable()
+		 && !orig_rec_no->var_value()->is_receiver());
+      orig_rec_no->var_value()->set_is_receiver();
+
+      Named_object* new_rec_no = new_bindings->lookup_local(receiver_name);
+      gcc_assert(new_rec_no != NULL
+		 && new_rec_no->is_variable()
+		 && !new_rec_no->var_value()->is_receiver());
+      new_rec_no->var_value()->set_is_not_receiver();
+    }
+
+  // Because we flipped blocks but not types, the can_recover
+  // parameter appears in the (now) old bindings as a parameter.
+  // Change it to a local variable, whereupon it will be discarded.
+  Named_object* can_recover_no = orig_bindings->lookup_local(can_recover_name);
+  gcc_assert(can_recover_no != NULL
+	     && can_recover_no->is_variable()
+	     && can_recover_no->var_value()->is_parameter());
+  orig_bindings->remove_binding(can_recover_no);
+
+  // Add the can_recover argument to the (now) new bindings, and
+  // attach it to any recover statements.
+  Variable* can_recover_var = new Variable(Type::make_boolean_type(), NULL,
+					   false, true, false, location);
+  can_recover_no = new_bindings->add_variable(can_recover_name, NULL,
+					      can_recover_var);
+  Convert_recover convert_recover(can_recover_no);
+  new_func->traverse(&convert_recover);
+
+  return TRAVERSE_CONTINUE;
+}
+
+// Return the expression to pass for the .can_recover parameter to the
+// new function.  This indicates whether a call to recover may return
+// non-nil.  The expression is
+// __go_can_recover(__builtin_return_address()).
+
+Expression*
+Build_recover_thunks::can_recover_arg(source_location location)
+{
+  static Named_object* builtin_return_address;
+  if (builtin_return_address == NULL)
+    {
+      const source_location bloc = BUILTINS_LOCATION;
+
+      Typed_identifier_list* param_types = new Typed_identifier_list();
+      Type* uint_type = Type::lookup_integer_type("uint");
+      param_types->push_back(Typed_identifier("l", uint_type, bloc));
+
+      Typed_identifier_list* return_types = new Typed_identifier_list();
+      Type* voidptr_type = Type::make_pointer_type(Type::make_void_type());
+      return_types->push_back(Typed_identifier("", voidptr_type, bloc));
+
+      Function_type* fntype = Type::make_function_type(NULL, param_types,
+						       return_types, bloc);
+      builtin_return_address =
+	Named_object::make_function_declaration("__builtin_return_address",
+						NULL, fntype, bloc);
+      const char* n = "__builtin_return_address";
+      builtin_return_address->func_declaration_value()->set_asm_name(n);
+    }
+
+  static Named_object* can_recover;
+  if (can_recover == NULL)
+    {
+      const source_location bloc = BUILTINS_LOCATION;
+      Typed_identifier_list* param_types = new Typed_identifier_list();
+      Type* voidptr_type = Type::make_pointer_type(Type::make_void_type());
+      param_types->push_back(Typed_identifier("a", voidptr_type, bloc));
+      Type* boolean_type = Type::make_boolean_type();
+      Typed_identifier_list* results = new Typed_identifier_list();
+      results->push_back(Typed_identifier("", boolean_type, bloc));
+      Function_type* fntype = Type::make_function_type(NULL, param_types,
+						       results, bloc);
+      can_recover = Named_object::make_function_declaration("__go_can_recover",
+							    NULL, fntype,
+							    bloc);
+      can_recover->func_declaration_value()->set_asm_name("__go_can_recover");
+    }
+
+  Expression* fn = Expression::make_func_reference(builtin_return_address,
+						   NULL, location);
+
+  mpz_t zval;
+  mpz_init_set_ui(zval, 0UL);
+  Expression* zexpr = Expression::make_integer(&zval, NULL, location);
+  mpz_clear(zval);
+  Expression_list *args = new Expression_list();
+  args->push_back(zexpr);
+
+  Expression* call = Expression::make_call(fn, args, location);
+
+  args = new Expression_list();
+  args->push_back(call);
+
+  fn = Expression::make_func_reference(can_recover, NULL, location);
+  return Expression::make_call(fn, args, location);
+}
+
+// Build thunks for functions which call recover.  We build a new
+// function with an extra parameter, which is whether a call to
+// recover can succeed.  We then move the body of this function to
+// that one.  We then turn this function into a thunk which calls the
+// new one, passing the value of
+// __go_can_recover(__builtin_return_address()).  The function will be
+// marked as not splitting the stack.  This will cooperate with the
+// implementation of defer to make recover do the right thing.
+
+void
+Gogo::build_recover_thunks()
+{
+  Build_recover_thunks build_recover_thunks(this);
+  this->traverse(&build_recover_thunks);
+}
+
 // Look for named types to see whether we need to create an interface
 // method table.
 
@@ -2199,10 +2516,39 @@ 
 
 Function::Function(Function_type* type, Function* enclosing, Block* block,
 		   source_location location)
-  : type_(type), enclosing_(enclosing), closure_var_(NULL), refcounts_(NULL),
-    block_(block), location_(location), fndecl_(NULL), return_value_(NULL),
-    defer_stack_(NULL)
-{
+  : type_(type), enclosing_(enclosing), named_results_(NULL),
+    closure_var_(NULL), refcounts_(NULL), block_(block), location_(location),
+    fndecl_(NULL), defer_stack_(NULL), calls_recover_(false),
+    is_recover_thunk_(false), has_recover_thunk_(false)
+{
+}
+
+// Create the named result variables.
+
+void
+Function::create_named_result_variables()
+{
+  const Typed_identifier_list* results = this->type_->results();
+  if (results == NULL
+      || results->empty()
+      || results->front().name().empty())
+    return;
+
+  this->named_results_ = new Named_results();
+  this->named_results_->reserve(results->size());
+
+  Block* block = this->block_;
+  int index = 0;
+  for (Typed_identifier_list::const_iterator p = results->begin();
+       p != results->end();
+       ++p, ++index)
+    {
+      Result_variable* result = new Result_variable(p->type(), this,
+						    index);
+      Named_object* no = block->bindings()->add_result_variable(p->name(),
+								result);
+      this->named_results_->push_back(no);
+    }
 }
 
 // Return the closure variable, creating it if necessary.
@@ -2243,7 +2589,11 @@ 
       char buf[20];
       snprintf(buf, sizeof buf, "%u", index);
       std::string n = no->name() + buf;
-      Type* var_type = no->var_value()->type();
+      Type* var_type;
+      if (no->is_variable())
+	var_type = no->var_value()->type();
+      else
+	var_type = no->result_var_value()->type();
       Type* field_type = Type::make_pointer_type(var_type);
       st->push_field(Struct_field(Typed_identifier(n, field_type, p->second)));
     }
@@ -2325,6 +2675,23 @@ 
     }
 }
 
+// Swap one function with another.  This is used when building the
+// thunk we use to call a function which calls recover.  It may not
+// work for any other case.
+
+void
+Function::swap_for_recover(Function *x)
+{
+  gcc_assert(this->enclosing_ == x->enclosing_);
+  gcc_assert(this->named_results_ == x->named_results_);
+  std::swap(this->closure_var_, x->closure_var_);
+  gcc_assert(this->refcounts_ == NULL && x->refcounts_ == NULL);
+  std::swap(this->block_, x->block_);
+  gcc_assert(this->location_ == x->location_);
+  gcc_assert(this->fndecl_ == NULL && x->fndecl_ == NULL);
+  gcc_assert(this->defer_stack_ == NULL && x->defer_stack_ == NULL);
+}
+
 // Traverse the tree.
 
 int
@@ -3517,6 +3884,28 @@ 
   return p->second;
 }
 
+// Remove an object from a set of bindings.  This is used for a
+// special case in thunks for functions which call recover.
+
+void
+Bindings::remove_binding(Named_object* no)
+{
+  Contour::iterator pb = this->bindings_.find(no->name());
+  gcc_assert(pb != this->bindings_.end());
+  this->bindings_.erase(pb);
+  for (std::vector<Named_object*>::iterator pn = this->named_objects_.begin();
+       pn != this->named_objects_.end();
+       ++pn)
+    {
+      if (*pn == no)
+	{
+	  this->named_objects_.erase(pn);
+	  return;
+	}
+    }
+  gcc_unreachable();
+}
+
 // Add a method to the list of objects.  This is not added to the
 // lookup table.  This is so that we have a single list of objects
 // declared at the top level, which we walk through when it's time to
diff -r da79ac110978 go/gogo.h
--- a/go/gogo.h	Sun Jun 13 23:06:30 2010 -0700
+++ b/go/gogo.h	Sun Jun 13 23:07:43 2010 -0700
@@ -393,6 +393,10 @@ 
   void
   order_evaluations();
 
+  // Build thunks for functions which call recover.
+  void
+  build_recover_thunks();
+
   // Simplify statements which might use thunks: go and defer
   // statements.
   void
@@ -785,6 +789,11 @@ 
  public:
   Block(Block* enclosing, source_location);
 
+  // Return the enclosing block.
+  const Block*
+  enclosing() const
+  { return this->enclosing_; }
+
   // Return the bindings of the block.
   Bindings*
   bindings()
@@ -906,6 +915,19 @@ 
   enclosing()
   { return this->enclosing_; }
 
+  // Set the enclosing function.  This is used when building thunks
+  // for functions which call recover.
+  void
+  set_enclosing(Function* enclosing)
+  {
+    gcc_assert(this->enclosing_ == NULL);
+    this->enclosing_ = enclosing;
+  }
+
+  // Create the named result variables in the outer block.
+  void
+  create_named_result_variables();
+
   // Add a new field to the closure variable.
   void
   add_closure_field(Named_object* var, source_location loc)
@@ -921,6 +943,15 @@ 
   Named_object*
   closure_var();
 
+  // Set the closure variable.  This is used when building thunks for
+  // functions which call recover.
+  void
+  set_closure_var(Named_object* v)
+  {
+    gcc_assert(this->closure_var_ == NULL);
+    this->closure_var_ = v;
+  }
+
   // Return the variable for a reference to field INDEX in the closure
   // variable.
   Named_object*
@@ -960,6 +991,43 @@ 
   Label*
   add_label_reference(const std::string& label_name);
 
+  // Whether this function calls the predeclared recover function.
+  bool
+  calls_recover() const
+  { return this->calls_recover_; }
+
+  // Record that this function calls the predeclared recover function.
+  // This is set during the lowering pass.
+  void
+  set_calls_recover()
+  { this->calls_recover_ = true; }
+
+  // Whether this is a recover thunk function.
+  bool
+  is_recover_thunk() const
+  { return this->is_recover_thunk_; }
+
+  // Record that this is a thunk built for a function which calls
+  // recover.
+  void
+  set_is_recover_thunk()
+  { this->is_recover_thunk_ = true; }
+
+  // Whether this function already has a recover thunk.
+  bool
+  has_recover_thunk() const
+  { return this->has_recover_thunk_; }
+
+  // Record that this function already has a recover thunk.
+  void
+  set_has_recover_thunk()
+  { this->has_recover_thunk_ = true; }
+
+  // Swap with another function.  Used only for the thunk which calls
+  // recover.
+  void
+  swap_for_recover(Function *);
+
   // Traverse the tree.
   int
   traverse(Traverse*);
@@ -984,17 +1052,14 @@ 
   void
   build_tree(Gogo*, Named_object*);
 
-  // Get a tree for the return value.
+  // Get the value to return when not explicitly specified.  May also
+  // add statements to execute first to STMT_LIST.
   tree
-  return_value()
-  {
-    gcc_assert(this->return_value_ != NULL);
-    return this->return_value_;
-  }
+  return_value(Gogo*, Named_object*, source_location, tree* stmt_list) const;
 
   // Get a tree for the variable holding the defer stack.
   tree
-  defer_stack();
+  defer_stack(source_location);
 
   // Export the function.
   void
@@ -1021,6 +1086,11 @@ 
   tree
   copy_parm_to_heap(Gogo*, tree);
 
+  void
+  build_defer_wrapper(Gogo*, Named_object*, tree, tree*, tree*);
+
+  typedef std::vector<Named_object*> Named_results;
+
   typedef std::vector<std::pair<Named_object*,
 				source_location> > Closure_fields;
 
@@ -1029,6 +1099,8 @@ 
   // The enclosing function.  This is NULL when there isn't one, which
   // is the normal case.
   Function* enclosing_;
+  // The named result variables, if any.
+  Named_results* named_results_;
   // If there is a closure, this is the list of variables which appear
   // in the closure.  This is created by the parser, and then resolved
   // to a real type when we lower parse trees.
@@ -1047,12 +1119,15 @@ 
   Labels labels_;
   // The function decl.
   tree fndecl_;
-  // If the function has named return values, this variable holds the
-  // value returned by a return statement which does not name a value.
-  tree return_value_;
-  // A variable holding the defer stack.  This is NULL unless we
-  // actually need a defer stack.
+  // A variable holding the defer stack variable.  This is NULL unless
+  // we actually need a defer stack.
   tree defer_stack_;
+  // True if this function calls the predeclared recover function.
+  bool calls_recover_;
+  // True if this a thunk built for a function which calls recover.
+  bool is_recover_thunk_;
+  // True if this function already has a recover thunk.
+  bool has_recover_thunk_;
 };
 
 // A function declaration.
@@ -1149,6 +1224,24 @@ 
   is_receiver() const
   { return this->is_receiver_; }
 
+  // Change this parameter to be a receiver.  This is used when
+  // creating the thunks created for functions which call recover.
+  void
+  set_is_receiver()
+  {
+    gcc_assert(this->is_parameter_);
+    this->is_receiver_ = true;
+  }
+
+  // Change this parameter to not be a receiver.  This is used when
+  // creating the thunks created for functions which call recover.
+  void
+  set_is_not_receiver()
+  {
+    gcc_assert(this->is_parameter_);
+    this->is_receiver_ = false;
+  }
+
   // Return whether this is the varargs parameter of a function.
   bool
   is_varargs_parameter() const
@@ -1349,7 +1442,8 @@ 
 {
  public:
   Result_variable(Type* type, Function* function, int index)
-    : type_(type), function_(function), index_(index)
+    : type_(type), function_(function), index_(index),
+      is_address_taken_(false)
   { }
 
   // Get the type of the result variable.
@@ -1367,6 +1461,21 @@ 
   index() const
   { return this->index_; }
 
+  // Whether this variable's address is taken.
+  bool
+  is_address_taken() const
+  { return this->is_address_taken_; }
+
+  // Note that something takes the address of this variable.
+  void
+  set_address_taken()
+  { this->is_address_taken_ = true; }
+
+  // Whether this variable should live in the heap.
+  bool
+  is_in_heap() const
+  { return this->is_address_taken_; }
+
  private:
   // Type of result variable.
   Type* type_;
@@ -1374,6 +1483,8 @@ 
   Function* function_;
   // Index in list of results.
   int index_;
+  // Whether something takes the address of this variable.
+  bool is_address_taken_;
 };
 
 // The value we keep for a named constant.  This lets us hold a type
@@ -2008,6 +2119,10 @@ 
   Named_object*
   lookup_local(const std::string&) const;
 
+  // Remove a name.
+  void
+  remove_binding(Named_object*);
+
   // Traverse the tree.  See the Traverse class.
   int
   traverse(Traverse*, bool is_global);
@@ -2115,6 +2230,10 @@ 
   tree
   get_decl();
 
+  // Return an expression for the address of this label.
+  tree
+  get_addr(source_location location);
+
  private:
   // The name of the label.
   std::string name_;
diff -r da79ac110978 go/parse.cc
--- a/go/parse.cc	Sun Jun 13 23:06:30 2010 -0700
+++ b/go/parse.cc	Sun Jun 13 23:07:43 2010 -0700
@@ -2237,13 +2237,7 @@ 
 Parse::enclosing_var_reference(Named_object* in_function, Named_object* var,
 			       source_location location)
 {
-  if (var->is_result_variable())
-    {
-      error_at(location, "reference to out parameter in an enclosing function");
-      return Expression::make_error(location);
-    }
-
-  gcc_assert(var->is_variable());
+  gcc_assert(var->is_variable() || var->is_result_variable());
 
   Named_object* this_function = this->gogo_->current_function();
   Named_object* closure = this_function->func_value()->closure_var();
@@ -3451,7 +3445,8 @@ 
   if (this->expression_may_start_here())
     vals = this->expression_list(NULL, false);
   const Function* function = this->gogo_->current_function()->func_value();
-  this->gogo_->add_statement(Statement::make_return_statement(function, vals,
+  const Typed_identifier_list* results = function->type()->results();
+  this->gogo_->add_statement(Statement::make_return_statement(results, vals,
 							      location));
 }
 
diff -r da79ac110978 go/statements.cc
--- a/go/statements.cc	Sun Jun 13 23:06:30 2010 -0700
+++ b/go/statements.cc	Sun Jun 13 23:07:43 2010 -0700
@@ -1548,10 +1548,8 @@ 
 	  else
 	    fntype = NULL;
 
-	  // The builtin functions panic and panicln do not return.
-	  if ((no->name() == "panic" || no->name() == "panicln")
-	      && fntype != NULL
-	      && fntype->is_builtin())
+	  // The builtin function panic does not return.
+	  if (fntype != NULL && fntype->is_builtin() && no->name() == "panic")
 	    return false;
 	}
     }
@@ -1699,6 +1697,11 @@ 
   if (fntype->is_method() || fntype->is_varargs())
     return false;
 
+  // A defer statement requires a thunk to set up for whether the
+  // function can call recover.
+  if (this->classification() == STATEMENT_DEFER)
+    return false;
+
   // We can only permit a single parameter of pointer type.
   const Typed_identifier_list* parameters = fntype->parameters();
   if (parameters != NULL
@@ -1760,10 +1763,11 @@ 
 bool
 Thunk_statement::do_traverse_assignments(Traverse_assignments* tassign)
 {
+  // FIXME: This doesn't work, because we might get a REFCOUNT_ADJUST
+  // which we will never execute.
   Expression* fn = this->call_->call_expression()->fn();
   Expression* fn2 = fn;
   tassign->value(&fn2, true, false);
-  gcc_assert(fn == fn2);
   return true;
 }
 
@@ -2003,6 +2007,15 @@ 
       Typed_identifier tid(Go_statement::thunk_field_fn, fntype, location);
       fields->push_back(Struct_field(tid));
     }
+  else if (ce->is_recover_call())
+    {
+      // The predeclared recover function has no argument.  However,
+      // we add an argument when building recover thunks.  Handle that
+      // here.
+      fields->push_back(Struct_field(Typed_identifier("can_recover",
+						      Type::make_boolean_type(),
+						      location)));
+    }
 
   if (fn->bound_method_expression() != NULL)
     {
@@ -2043,6 +2056,24 @@ 
 {
   source_location location = this->location();
 
+  Call_expression* ce = this->call_->call_expression();
+
+  bool may_call_recover = false;
+  if (this->classification() == STATEMENT_DEFER)
+    {
+      Func_expression* fn = ce->fn()->func_expression();
+      if (fn == NULL)
+	may_call_recover = true;
+      else
+	{
+	  const Named_object* no = fn->named_object();
+	  if (!no->is_function())
+	    may_call_recover = true;
+	  else
+	    may_call_recover = no->func_value()->calls_recover();
+	}
+    }
+
   // Build the type of the thunk.  The thunk takes a single parameter,
   // which is a pointer to the special structure we build.
   const char* const parameter_name = "__go_thunk_parameter";
@@ -2050,14 +2081,77 @@ 
   Type* pointer_to_struct_type = Type::make_pointer_type(this->struct_type_);
   thunk_parameters->push_back(Typed_identifier(parameter_name,
 					       pointer_to_struct_type,
-					       this->location()));
+					       location));
+
+  Typed_identifier_list* thunk_results = NULL;
+  if (may_call_recover)
+    {
+      // When deferring a function which may call recover, add a
+      // return value, to disable tail call optimizations which will
+      // break the way we check whether recover is permitted.
+      thunk_results = new Typed_identifier_list();
+      thunk_results->push_back(Typed_identifier("", Type::make_boolean_type(),
+						location));
+    }
+
   Function_type* thunk_type = Type::make_function_type(NULL, thunk_parameters,
-						       NULL, this->location());
+						       thunk_results,
+						       location);
 
   // Start building the thunk.
   Named_object* function = gogo->start_function(thunk_name, thunk_type, true,
 						location);
 
+  // For a defer statement, start with a call to
+  // __go_set_defer_retaddr.  */
+  Label* retaddr_label = NULL; 
+  if (may_call_recover)
+    {
+      retaddr_label = gogo->add_label_reference("retaddr");
+      Expression* arg = Expression::make_label_addr(retaddr_label, location);
+      Expression_list* args = new Expression_list();
+      args->push_back(arg);
+
+      static Named_object* set_defer_retaddr;
+      if (set_defer_retaddr == NULL)
+	{
+	  const source_location bloc = BUILTINS_LOCATION;
+	  Typed_identifier_list* param_types = new Typed_identifier_list();
+	  Type *voidptr_type = Type::make_pointer_type(Type::make_void_type());
+	  param_types->push_back(Typed_identifier("r", voidptr_type, bloc));
+
+	  Typed_identifier_list* result_types = new Typed_identifier_list();
+	  result_types->push_back(Typed_identifier("",
+						   Type::make_boolean_type(),
+						   bloc));
+
+	  Function_type* t = Type::make_function_type(NULL, param_types,
+						      result_types, bloc);
+	  set_defer_retaddr =
+	    Named_object::make_function_declaration("__go_set_defer_retaddr",
+						    NULL, t, bloc);
+	  const char* n = "__go_set_defer_retaddr";
+	  set_defer_retaddr->func_declaration_value()->set_asm_name(n);
+	}
+
+      Expression* fn = Expression::make_func_reference(set_defer_retaddr,
+						       NULL, location);
+      Expression* call = Expression::make_call(fn, args, location);
+
+      // This is a hack to prevent the middle-end from deleting the
+      // label.
+      gogo->start_block(location);
+      gogo->add_statement(Statement::make_goto_statement(retaddr_label,
+							 location));
+      Block* then_block = gogo->finish_block(location);
+      then_block->determine_types();
+
+      Statement* s = Statement::make_if_statement(call, then_block, NULL,
+						  location);
+      s->determine_types();
+      gogo->add_statement(s);
+    }
+
   // Get a reference to the parameter.
   Named_object* named_parameter = gogo->lookup(parameter_name, NULL);
   gcc_assert(named_parameter != NULL && named_parameter->is_variable());
@@ -2067,7 +2161,6 @@ 
   Expression* thunk_parameter = Expression::make_var_reference(named_parameter,
 							       location);
 
-  Call_expression* ce = this->call_->call_expression();
   Bound_method_expression* bound_method = ce->fn()->bound_method_expression();
   Interface_field_reference_expression* interface_method =
     ce->fn()->interface_field_reference_expression();
@@ -2125,6 +2218,12 @@ 
   Expression* call = Expression::make_call(func_to_call, call_params, location);
   // We need to lower in case this is a builtin function.
   call = call->lower(gogo, function, -1);
+  if (may_call_recover)
+    {
+      Call_expression* ce = call->call_expression();
+      if (ce != NULL)
+	ce->set_is_deferred();
+    }
 
   Statement* call_statement = Statement::make_statement(call);
 
@@ -2134,6 +2233,20 @@ 
 
   gogo->add_statement(call_statement);
 
+  // If this is a defer statement, the label comes immediately after
+  // the call.
+  if (may_call_recover)
+    {
+      gogo->add_label_definition("retaddr", location);
+
+      Expression_list* vals = new Expression_list();
+      vals->push_back(Expression::make_boolean(false, location));
+      const Typed_identifier_list* results =
+	function->func_value()->type()->results();
+      gogo->add_statement(Statement::make_return_statement(results, vals,
+							  location));
+    }
+
   // FIXME: Now we need to decrement the reference count of the
   // parameter.
 
@@ -2226,6 +2339,8 @@ 
 tree
 Defer_statement::do_get_tree(Translate_context* context)
 {
+  source_location loc = this->location();
+
   tree fn_tree;
   tree arg_tree;
   this->get_fn_and_arg(context, &fn_tree, &arg_tree);
@@ -2243,20 +2358,19 @@ 
       fn_arg_type = build_pointer_type(subfntype);
     }
 
-  tree defer_stack = context->function()->func_value()->defer_stack();
-
-  tree call = Gogo::call_builtin(&defer_fndecl,
-				 this->location(),
-				 "__go_defer",
-				 3,
-				 ptr_type_node,
-				 ptr_type_node,
-				 defer_stack,
-				 fn_arg_type,
-				 fn_tree,
-				 ptr_type_node,
-				 arg_tree);
-  return build2(MODIFY_EXPR, void_type_node, defer_stack, call);
+  tree defer_stack = context->function()->func_value()->defer_stack(loc);
+
+  return Gogo::call_builtin(&defer_fndecl,
+			    loc,
+			    "__go_defer",
+			    3,
+			    void_type_node,
+			    ptr_type_node,
+			    defer_stack,
+			    fn_arg_type,
+			    fn_tree,
+			    ptr_type_node,
+			    arg_tree);
 }
 
 // Make a defer statement.
@@ -2311,6 +2425,125 @@ 
   return true;
 }
 
+// Lower a return statement.  If we are returning a function call
+// which returns multiple values which match the current function,
+// split up the call's results.  If the function has named result
+// variables, and the return statement lists explicit values, then
+// implement it by assigning the values to the result variables and
+// changing the statement to not list any values.  This lets
+// panic/recover work correctly.
+
+Statement*
+Return_statement::do_lower(Gogo*, Block* enclosing)
+{
+  if (this->vals_ == NULL)
+    return this;
+
+  const Typed_identifier_list* results = this->results_;
+  if (results == NULL || results->empty())
+    return this;
+
+  // If the current function has multiple return values, and we are
+  // returning a single call expression, split up the call expression.
+  size_t results_count = results->size();
+  if (results_count > 1
+      && this->vals_->size() == 1
+      && this->vals_->front()->call_expression() != NULL)
+    {
+      Call_expression* call = this->vals_->front()->call_expression();
+      size_t count = results->size();
+      Expression_list* vals = new Expression_list;
+      for (size_t i = 0; i < count; ++i)
+	vals->push_back(Expression::make_call_result(call, i));
+      delete this->vals_;
+      this->vals_ = vals;
+    }
+
+  if (results->front().name().empty())
+    return this;
+
+  if (results_count != this->vals_->size())
+    {
+      // Presumably an error which will be reported in check_types.
+      return this;
+    }
+
+  // Assign to named return values and then return them.
+
+  source_location loc = this->location();
+  const Block* top = enclosing;
+  while (top->enclosing() != NULL)
+    top = top->enclosing();
+
+  const Bindings *bindings = top->bindings();
+  Block* b = new Block(enclosing, loc);
+
+  Expression_list* lhs = new Expression_list();
+  Expression_list* rhs = new Expression_list();
+
+  Expression_list::const_iterator pe = this->vals_->begin();
+  int i = 1;
+  for (Typed_identifier_list::const_iterator pr = results->begin();
+       pr != results->end();
+       ++pr, ++pe, ++i)
+    {
+      Named_object* rv = bindings->lookup_local(pr->name());
+      if (rv == NULL || !rv->is_result_variable())
+	{
+	  // Presumably an error.
+	  delete b;
+	  delete lhs;
+	  delete rhs;
+	  return this;
+	}
+
+      Expression* e = *pe;
+
+      // Check types now so that we give a good error message.  The
+      // result type is known.  We determine the expression type
+      // early.
+
+      Type *rvtype = rv->result_var_value()->type();
+      Type_context type_context(rvtype, false);
+      e->determine_type(&type_context);
+
+      std::string reason;
+      if (Type::are_compatible_for_assign(rvtype, e->type(), &reason))
+	{
+	  Expression* ve = Expression::make_var_reference(rv, e->location());
+	  lhs->push_back(ve);
+	  rhs->push_back(e);
+	}
+      else
+	{
+	  if (reason.empty())
+	    error_at(e->location(), "incompatible type for return value %d", i);
+	  else
+	    error_at(e->location(),
+		     "incompatible type for return value %d (%s)",
+		     i, reason.c_str());
+	}
+    }
+  gcc_assert(lhs->size() == rhs->size());
+
+  if (lhs->empty())
+    ;
+  else if (lhs->size() == 1)
+    {
+      b->add_statement(Statement::make_assignment(lhs->front(), rhs->front(),
+						  loc));
+      delete lhs;
+      delete rhs;
+    }
+  else
+    b->add_statement(Statement::make_tuple_assignment(lhs, rhs, loc));
+
+  b->add_statement(Statement::make_return_statement(this->results_, NULL,
+						    loc));
+
+  return Statement::make_block_statement(b, loc);
+}
+
 // Determine types.
 
 void
@@ -2318,40 +2551,16 @@ 
 {
   if (this->vals_ == NULL)
     return;
-  Function_type* type = this->function_->type();
-  const Typed_identifier_list* results = type->results();
-  if (results == NULL)
-    return;
-
-  // If the current function has multiple return values, and we are
-  // returning a single function call expression, split up the call
-  // expression.  We have to determine the type of the call expression
-  // first, because we don't know how many values it returns until
-  // method references are resolved.
-  if (results->size() > 1
-      && this->vals_->size() == 1
-      && this->vals_->front()->call_expression() != NULL)
-    {
-      Call_expression* call = this->vals_->front()->call_expression();
-      call->determine_type_no_context();
-      size_t count = call->result_count();
-      if (count > 1)
-	{
-	  Expression_list* vals = new Expression_list;
-	  for (size_t i = 0; i < count; ++i)
-	    vals->push_back(Expression::make_call_result(call, i));
-	  delete this->vals_;
-	  this->vals_ = vals;
-	}
-      return;
-    }
-
-  Typed_identifier_list::const_iterator pt = results->begin();
+  const Typed_identifier_list* results = this->results_;
+
+  Typed_identifier_list::const_iterator pt;
+  if (results != NULL)
+    pt = results->begin();
   for (Expression_list::iterator pe = this->vals_->begin();
        pe != this->vals_->end();
        ++pe)
     {
-      if (pt == results->end())
+      if (results == NULL || pt == results->end())
 	(*pe)->determine_type_no_context();
       else
 	{
@@ -2370,9 +2579,7 @@ 
   if (this->vals_ == NULL)
     return;
 
-  Function_type* type = this->function_->type();
-
-  const Typed_identifier_list* results = type->results();
+  const Typed_identifier_list* results = this->results_;
   if (results == NULL)
     {
       this->report_error(_("return with value in function "
@@ -2380,7 +2587,7 @@ 
       return;
     }
 
-  int i = 0;
+  int i = 1;
   Typed_identifier_list::const_iterator pt = results->begin();
   for (Expression_list::const_iterator pe = this->vals_->begin();
        pe != this->vals_->end();
@@ -2425,21 +2632,27 @@ 
 tree
 Return_statement::do_get_tree(Translate_context* context)
 {
-  tree fndecl = context->function()->func_value()->get_decl();
-
-  gcc_assert(this->function_ == context->function()->func_value());
-  Function_type* type = this->function_->type();
-  const Typed_identifier_list* results = type->results();
+  Function* function = context->function()->func_value();
+  tree fndecl = function->get_decl();
+
+  const Typed_identifier_list* results = this->results_;
 
   if (this->vals_ == NULL)
     {
-      tree retval;
-      if (VOID_TYPE_P(TREE_TYPE(TREE_TYPE(fndecl))))
-	retval = NULL_TREE;
-      else
-	retval = build2(MODIFY_EXPR, void_type_node, DECL_RESULT(fndecl),
-			context->function()->func_value()->return_value());
-      return this->build_stmt_1(RETURN_EXPR, retval);
+      tree stmt_list = NULL_TREE;
+      tree retval = function->return_value(context->gogo(),
+					   context->function(),
+					   this->location(),
+					   &stmt_list);
+      tree set;
+      if (retval == NULL_TREE)
+	set = NULL_TREE;
+      else
+	set = fold_build2_loc(this->location(), MODIFY_EXPR, void_type_node,
+			      DECL_RESULT(fndecl), retval);
+      append_to_statement_list(this->build_stmt_1(RETURN_EXPR, set),
+			       &stmt_list);
+      return stmt_list;
     }
   else if (this->vals_->size() == 1)
     {
@@ -2495,11 +2708,11 @@ 
 // Make a return statement.
 
 Statement*
-Statement::make_return_statement(const Function* function,
+Statement::make_return_statement(const Typed_identifier_list* results,
 				 Expression_list* vals,
 				 source_location location)
 {
-  return new Return_statement(function, vals, location);
+  return new Return_statement(results, vals, location);
 }
 
 // A break or continue statement.
diff -r da79ac110978 go/statements.h
--- a/go/statements.h	Sun Jun 13 23:06:30 2010 -0700
+++ b/go/statements.h	Sun Jun 13 23:07:43 2010 -0700
@@ -39,6 +39,7 @@ 
 class Case_clauses;
 class Type_case_clauses;
 class Select_clauses;
+class Typed_identifier_list;
 class Refcounts;
 class Refcount_entry;
 
@@ -206,7 +207,8 @@ 
 
   // Make a return statement.
   static Statement*
-  make_return_statement(const Function*, Expression_list*, source_location);
+  make_return_statement(const Typed_identifier_list*, Expression_list*,
+			source_location);
 
   // Make a break statement.
   static Statement*
@@ -567,10 +569,10 @@ 
 class Return_statement : public Statement
 {
  public:
-  Return_statement(const Function* function, Expression_list* vals,
+  Return_statement(const Typed_identifier_list* results, Expression_list* vals,
 		   source_location location)
     : Statement(STATEMENT_RETURN, location),
-      function_(function), vals_(vals), do_not_increment_(NULL)
+      results_(results), vals_(vals), do_not_increment_(NULL)
   { }
 
   // The list of values being returned.  This may be NULL.
@@ -597,6 +599,9 @@ 
   bool
   do_traverse_assignments(Traverse_assignments*);
 
+  Statement*
+  do_lower(Gogo*, Block*);
+
   void
   do_determine_types();
 
@@ -611,9 +616,10 @@ 
   do_get_tree(Translate_context*);
 
  private:
-  // The function we are returning from.  We use this to get the
-  // return types.
-  const Function* function_;
+  // The result types of the function we are returning from.  This is
+  // here because in some of the traversals it is inconvenient to get
+  // it.
+  const Typed_identifier_list* results_;
   // Return values.  This may be NULL.
   Expression_list* vals_;
   // List of variables whose reference count should not be
diff -r da79ac110978 go/types.cc
--- a/go/types.cc	Sun Jun 13 23:06:30 2010 -0700
+++ b/go/types.cc	Sun Jun 13 23:07:43 2010 -0700
@@ -6029,7 +6029,8 @@ 
 	    retvals->push_back(Expression::make_call_result(call, i));
 	}
       const Function* function = gogo->current_function()->func_value();
-      Statement* retstat = Statement::make_return_statement(function, retvals,
+      const Typed_identifier_list* results = function->type()->results();
+      Statement* retstat = Statement::make_return_statement(results, retvals,
 							    location);
       gogo->add_statement(retstat);
     }