compiler: inline functions with assignments and return statements

Support inlining functions that contain only assignments and return
statements, with expressions of either constants or parameters.
Functions that contain other kinds of statements or expressions are
not yet inlined.  With this change, about 100 functions in the
standard library are inlinable.

Change-Id: I3784ab8acb5c471386d338a5c92f97b8b6bfe21a
Reviewed-on: https://go-review.googlesource.com/c/150073
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
diff --git a/go/expressions.cc b/go/expressions.cc
index 5f00eff..7c464ce 100644
--- a/go/expressions.cc
+++ b/go/expressions.cc
@@ -786,6 +786,31 @@
     }
 }
 
+// The cost to inline a variable reference.  We currently only support
+// references to parameters.
+
+int
+Var_expression::do_inlining_cost() const
+{
+  if (this->variable_->is_variable())
+    {
+      if (this->variable_->var_value()->is_parameter())
+	return 1;
+    }
+  else if (this->variable_->is_result_variable())
+    return 1;
+
+  return 0x100000;
+}
+
+// Export a reference to a variable.
+
+void
+Var_expression::do_export(Export_function_body* efb) const
+{
+  efb->write_string(Gogo::unpack_hidden_name(this->variable_->name()));
+}
+
 // Get the backend representation for a reference to a variable.
 
 Bexpression*
@@ -1608,6 +1633,10 @@
   do_get_backend(Translate_context* context)
   { return context->backend()->boolean_constant_expression(this->val_); }
 
+  int
+  do_inlining_cost() const
+  { return 1; }
+
   void
   do_export(Export_function_body* efb) const
   { efb->write_c_string(this->val_ ? "$true" : "$false"); }
@@ -1997,6 +2026,10 @@
 					this->location());
   }
 
+  int
+  do_inlining_cost() const
+  { return 1; }
+
   void
   do_export(Export_function_body*) const;
 
@@ -2408,6 +2441,10 @@
   Bexpression*
   do_get_backend(Translate_context*);
 
+  int
+  do_inlining_cost() const
+  { return 1; }
+
   void
   do_export(Export_function_body*) const;
 
@@ -2617,6 +2654,10 @@
   Bexpression*
   do_get_backend(Translate_context*);
 
+  int
+  do_inlining_cost() const
+  { return 2; }
+
   void
   do_export(Export_function_body*) const;
 
@@ -3204,6 +3245,10 @@
   do_get_backend(Translate_context* context)
   { return context->backend()->nil_pointer_expression(); }
 
+  int
+  do_inlining_cost() const
+  { return 1; }
+
   void
   do_export(Export_function_body* efb) const
   { efb->write_c_string("$nil"); }
@@ -3654,6 +3699,25 @@
     }
 }
 
+// Cost of inlining a type conversion.
+
+int
+Type_conversion_expression::do_inlining_cost() const
+{
+  Type* type = this->type_;
+  Type* expr_type = this->expr_->type();
+  if (type->interface_type() != NULL || expr_type->interface_type() != NULL)
+    return 10;
+  else if (type->is_string_type() && expr_type->integer_type() != NULL)
+    return 10;
+  else if (type->is_string_type() && expr_type->is_slice_type())
+    return 10;
+  else if (type->is_slice_type() && expr_type->is_string_type())
+    return 10;
+  else
+    return 1;
+}
+
 // Output a type conversion in a constant expression.
 
 void
@@ -4677,7 +4741,11 @@
       efb->write_c_string("^");
       break;
     case OPERATOR_AND:
+      efb->write_c_string("&");
+      break;
     case OPERATOR_MULT:
+      efb->write_c_string("*");
+      break;
     default:
       go_unreachable();
     }
@@ -4704,6 +4772,12 @@
     case '^':
       op = OPERATOR_XOR;
       break;
+    case '&':
+      op = OPERATOR_AND;
+      break;
+    case '*':
+      op = OPERATOR_MULT;
+      break;
     default:
       go_unreachable();
     }
@@ -16195,7 +16269,7 @@
 Expression::import_expression(Import_expression* imp, Location loc)
 {
   int c = imp->peek_char();
-  if (c == '+' || c == '-' || c == '!' || c == '^')
+  if (c == '+' || c == '-' || c == '!' || c == '^' || c == '&' || c == '*')
     return Unary_expression::do_import(imp, loc);
   else if (c == '(')
     return Binary_expression::do_import(imp, loc);
@@ -16220,11 +16294,35 @@
 	   || (imp->version() < EXPORT_FORMAT_V3
 	       && imp->match_c_string("convert")))
     return Type_conversion_expression::do_import(imp, loc);
-  else
+
+  Import_function_body* ifb = imp->ifb();
+  if (ifb == NULL)
     {
       go_error_at(imp->location(), "import error: expected expression");
       return Expression::make_error(loc);
     }
+  if (ifb->saw_error())
+    return Expression::make_error(loc);
+  std::string id = ifb->read_identifier();
+  if (id.empty())
+    {
+      if (!ifb->saw_error())
+	go_error_at(imp->location(),
+		    "import error: expected identifier at %lu",
+		    static_cast<unsigned long>(ifb->off()));
+      ifb->set_saw_error();
+      return Expression::make_error(loc);
+    }
+  Named_object* var = ifb->block()->bindings()->lookup(id);
+  if (var == NULL)
+    {
+      if (!ifb->saw_error())
+	go_error_at(imp->location(), "import error: lookup of %qs failed",
+		    id.c_str());
+      ifb->set_saw_error();
+      return Expression::make_error(loc);
+    }
+  return Expression::make_var_reference(var, loc);
 }
 
 // Class Expression_list.
diff --git a/go/expressions.h b/go/expressions.h
index 7061657..a18322c 100644
--- a/go/expressions.h
+++ b/go/expressions.h
@@ -941,7 +941,7 @@
 
   // Return the cost of this statement for inlining purposes.
   int
-  inlining_cost()
+  inlining_cost() const
   { return this->do_inlining_cost(); }
 
   // Return whether the expression is addressable--something which may
@@ -1093,7 +1093,7 @@
   // inlining.  The default cost is high, so we only need to define
   // this method for expressions that can be inlined.
   virtual int
-  do_inlining_cost()
+  do_inlining_cost() const
   { return 0x100000; }
 
   // Child class implements whether the expression is addressable.
@@ -1355,6 +1355,12 @@
   do_copy()
   { return this; }
 
+  int
+  do_inlining_cost() const;
+
+  void
+  do_export(Export_function_body*) const;
+
   bool
   do_is_addressable() const
   { return true; }
@@ -1602,6 +1608,12 @@
   static void
   export_string(String_dump* exp, const String_expression* str);
 
+  // Set the inlining cost a bit high since inlining may cause
+  // duplicated string literals.
+  int
+  do_inlining_cost() const
+  { return 5; }
+
   void
   do_export(Export_function_body*) const;
 
@@ -1686,6 +1698,9 @@
   Bexpression*
   do_get_backend(Translate_context* context);
 
+  int
+  do_inlining_cost() const;
+
   void
   do_export(Export_function_body*) const;
 
@@ -1877,6 +1892,10 @@
   Bexpression*
   do_get_backend(Translate_context*);
 
+  int
+  do_inlining_cost() const
+  { return 1; }
+
   void
   do_export(Export_function_body*) const;
 
@@ -2022,6 +2041,10 @@
   Bexpression*
   do_get_backend(Translate_context*);
 
+  int
+  do_inlining_cost() const
+  { return 1; }
+
   void
   do_export(Export_function_body*) const;
 
diff --git a/go/statements.cc b/go/statements.cc
index 60b7a70..4d10f60 100644
--- a/go/statements.cc
+++ b/go/statements.cc
@@ -12,6 +12,7 @@
 #include "expressions.h"
 #include "gogo.h"
 #include "export.h"
+#include "import.h"
 #include "runtime.h"
 #include "backend.h"
 #include "statements.h"
@@ -124,9 +125,41 @@
 // Read a statement from export data.
 
 Statement*
-Statement::import_statement(Import_function_body*, Location)
+Statement::import_statement(Import_function_body* ifb, Location loc)
 {
-  go_unreachable();
+  if (ifb->match_c_string("{"))
+    {
+      size_t nl = ifb->body().find('\n', ifb->off());
+      if (nl == std::string::npos)
+	{
+	  if (!ifb->saw_error())
+	    go_error_at(ifb->location(),
+			"import error: no newline after { at %lu",
+			static_cast<unsigned long>(ifb->off()));
+	  ifb->set_saw_error();
+	  return Statement::make_error_statement(loc);
+	}
+      ifb->set_off(nl + 1);
+      ifb->increment_indent();
+      Block* block = new Block(ifb->block(), loc);
+      bool ok = Block::import_block(block, ifb, loc);
+      ifb->decrement_indent();
+      if (!ok)
+	return Statement::make_error_statement(loc);
+      return Statement::make_block_statement(block, loc);
+    }
+  else if (ifb->match_c_string("return"))
+    {
+      // After lowering return statements have no expressions.  The
+      // return expressions are assigned to result parameters.
+      ifb->advance(6);
+      return Statement::make_return_statement(NULL, loc);
+    }
+
+  Expression* lhs = Expression::import_expression(ifb, loc);
+  ifb->require_c_string(" = ");
+  Expression* rhs = Expression::import_expression(ifb, loc);
+  return Statement::make_assignment(lhs, rhs, loc);
 }
 
 // If this is a thunk statement, return it.
@@ -834,6 +867,14 @@
     this->set_is_error();
 }
 
+void
+Assignment_statement::do_export_statement(Export_function_body* efb)
+{
+  this->lhs_->export_expression(efb);
+  efb->write_c_string(" = ");
+  this->rhs_->export_expression(efb);
+}
+
 // Flatten an assignment statement.  We may need a temporary for
 // interface conversion.
 
@@ -2844,6 +2885,16 @@
 					      retvals, loc);
 }
 
+// Export a return statement.  At this point all the expressions have
+// been converted to assignments to the result variables, so this is
+// simple.
+
+void
+Return_statement::do_export_statement(Export_function_body* efb)
+{
+  efb->write_c_string("return");
+}
+
 // Dump the AST representation for a return statement.
 
 void
diff --git a/go/statements.h b/go/statements.h
index 3b5c68a..621d301 100644
--- a/go/statements.h
+++ b/go/statements.h
@@ -631,6 +631,13 @@
   void
   do_check_types(Gogo*);
 
+  int
+  do_inlining_cost()
+  { return 1; }
+
+  void
+  do_export_statement(Export_function_body*);
+
   Statement*
   do_flatten(Gogo*, Named_object*, Block*, Statement_inserter*);
 
@@ -792,6 +799,13 @@
   do_may_fall_through() const
   { return false; }
 
+  int
+  do_inlining_cost()
+  { return 1; }
+
+  void
+  do_export_statement(Export_function_body*);
+
   Bstatement*
   do_get_backend(Translate_context*);