From patchwork Mon Jun 14 06:14:41 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [gccgo] Compiler support for panic/recover Date: Sun, 13 Jun 2010 20:14:41 -0000 From: Ian Taylor X-Patchwork-Id: 55475 Message-Id: To: gcc-patches@gcc.gnu.org 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 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 @@ -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 = ¶ms; - // 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::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::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_results; + typedef std::vector > 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); }