From 20b603dba4bea71cbdf9dde7db44d6b5bbcb7654 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Wed, 19 Jun 2019 04:53:51 +0000 Subject: [PATCH] compiler: stack allocate a buffer for non-escaping string ops For string concatenation, string to/from byte or rune slice conversion, and int to string conversion, if the result does not escape, we can allocate a small (32-element, or 4-byte for int to string) buffer on stack, and pass it to the runtime function. If the result fits in the buffer, it doesn't need to do a heap allocation. Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/182538 From-SVN: r272468 --- gcc/go/gofrontend/MERGE | 2 +- gcc/go/gofrontend/expressions.cc | 98 ++++++++++++++++++++++++++++++++++------ gcc/go/gofrontend/expressions.h | 19 ++++++-- 3 files changed, 101 insertions(+), 18 deletions(-) diff --git a/gcc/go/gofrontend/MERGE b/gcc/go/gofrontend/MERGE index 16abddf..e62501f 100644 --- a/gcc/go/gofrontend/MERGE +++ b/gcc/go/gofrontend/MERGE @@ -1,4 +1,4 @@ -62d1b667f3e85f72a186b04aad36d701160a4611 +0e4aa31b26a20b6a6a2ca102b85ba8c8b8cdf876 The first line of this file holds the git revision number of the last merge done from the gofrontend repository. diff --git a/gcc/go/gofrontend/expressions.cc b/gcc/go/gofrontend/expressions.cc index e5e6ba7..766864a 100644 --- a/gcc/go/gofrontend/expressions.cc +++ b/gcc/go/gofrontend/expressions.cc @@ -3739,8 +3739,11 @@ Type_conversion_expression::do_flatten(Gogo*, Named_object*, this->expr_ = Expression::make_temporary_reference(temp, this->location()); } - // For interface conversion, decide if we can allocate on stack. - if (this->type()->interface_type() != NULL) + // For interface conversion and string to/from slice conversions, + // decide if we can allocate on stack. + if (this->type()->interface_type() != NULL + || this->type()->is_string_type() + || this->expr_->type()->is_string_type()) { Node* n = Node::make_node(this); if ((n->encoding() & ESCAPE_MASK) == Node::ESCAPE_NONE) @@ -3984,9 +3987,21 @@ Type_conversion_expression::do_get_backend(Translate_context* context) return se->get_backend(context); } + Expression* buf; + if (this->no_escape_) + { + Type* byte_type = Type::lookup_integer_type("uint8"); + Expression* buflen = + Expression::make_integer_ul(4, NULL, loc); + Type* array_type = Type::make_array_type(byte_type, buflen); + buf = Expression::make_allocation(array_type, loc); + buf->allocation_expression()->set_allocate_on_stack(); + buf->allocation_expression()->set_no_zero(); + } + else + buf = Expression::make_nil(loc); Expression* i2s_expr = - Runtime::make_call(Runtime::INTSTRING, loc, 2, - Expression::make_nil(loc), this->expr_); + Runtime::make_call(Runtime::INTSTRING, loc, 2, buf, this->expr_); return Expression::make_cast(type, i2s_expr, loc)->get_backend(context); } else if (type->is_string_type() && expr_type->is_slice_type()) @@ -4019,7 +4034,21 @@ Type_conversion_expression::do_get_backend(Translate_context* context) go_assert(e->integer_type()->is_rune()); code = Runtime::SLICERUNETOSTRING; } - return Runtime::make_call(code, loc, 2, Expression::make_nil(loc), + + Expression* buf; + if (this->no_escape_) + { + Type* byte_type = Type::lookup_integer_type("uint8"); + Expression* buflen = + Expression::make_integer_ul(tmp_string_buf_size, NULL, loc); + Type* array_type = Type::make_array_type(byte_type, buflen); + buf = Expression::make_allocation(array_type, loc); + buf->allocation_expression()->set_allocate_on_stack(); + buf->allocation_expression()->set_no_zero(); + } + else + buf = Expression::make_nil(loc); + return Runtime::make_call(code, loc, 2, buf, this->expr_)->get_backend(context); } else if (type->is_slice_type() && expr_type->is_string_type()) @@ -4035,9 +4064,20 @@ Type_conversion_expression::do_get_backend(Translate_context* context) go_assert(e->integer_type()->is_rune()); code = Runtime::STRINGTOSLICERUNE; } - Expression* s2a = Runtime::make_call(code, loc, 2, - Expression::make_nil(loc), - this->expr_); + + Expression* buf; + if (this->no_escape_) + { + Expression* buflen = + Expression::make_integer_ul(tmp_string_buf_size, NULL, loc); + Type* array_type = Type::make_array_type(e, buflen); + buf = Expression::make_allocation(array_type, loc); + buf->allocation_expression()->set_allocate_on_stack(); + buf->allocation_expression()->set_no_zero(); + } + else + buf = Expression::make_nil(loc); + Expression* s2a = Runtime::make_call(code, loc, 2, buf, this->expr_); return Expression::make_unsafe_cast(type, s2a, loc)->get_backend(context); } else if (type->is_numeric_type()) @@ -7428,7 +7468,35 @@ String_concat_expression::do_flatten(Gogo*, Named_object*, tce->set_no_copy(true); } - Expression* nil_arg = Expression::make_nil(loc); + Expression* buf = NULL; + Node* n = Node::make_node(this); + if ((n->encoding() & ESCAPE_MASK) == Node::ESCAPE_NONE) + { + size_t size = 0; + for (Expression_list::iterator p = this->exprs_->begin(); + p != this->exprs_->end(); + ++p) + { + std::string s; + if ((*p)->string_constant_value(&s)) + size += s.length(); + } + // Make a buffer on stack if the result does not escape. + // But don't do this if we know it won't fit. + if (size < (size_t)tmp_string_buf_size) + { + Type* byte_type = Type::lookup_integer_type("uint8"); + Expression* buflen = + Expression::make_integer_ul(tmp_string_buf_size, NULL, loc); + Expression::make_integer_ul(tmp_string_buf_size, NULL, loc); + Type* array_type = Type::make_array_type(byte_type, buflen); + buf = Expression::make_allocation(array_type, loc); + buf->allocation_expression()->set_allocate_on_stack(); + buf->allocation_expression()->set_no_zero(); + } + } + if (buf == NULL) + buf = Expression::make_nil(loc); Expression* call; switch (this->exprs_->size()) { @@ -7462,7 +7530,7 @@ String_concat_expression::do_flatten(Gogo*, Named_object*, code = Runtime::CONCATSTRING5; break; } - call = Runtime::make_call(code, loc, 2, nil_arg, arg); + call = Runtime::make_call(code, loc, 2, buf, arg); } break; @@ -7473,7 +7541,7 @@ String_concat_expression::do_flatten(Gogo*, Named_object*, Expression::make_slice_composite_literal(arg_type, this->exprs_, loc); sce->set_storage_does_not_escape(); - call = Runtime::make_call(Runtime::CONCATSTRINGS, loc, 2, nil_arg, + call = Runtime::make_call(Runtime::CONCATSTRINGS, loc, 2, buf, sce); } break; @@ -14254,6 +14322,8 @@ Allocation_expression::do_copy() this->location()); if (this->allocate_on_stack_) alloc->set_allocate_on_stack(); + if (this->no_zero_) + alloc->set_no_zero(); return alloc; } @@ -14279,10 +14349,12 @@ Allocation_expression::do_get_backend(Translate_context* context) Named_object* fn = context->function(); go_assert(fn != NULL); Bfunction* fndecl = fn->func_value()->get_or_make_decl(gogo, fn); - Bexpression* zero = gogo->backend()->zero_expression(btype); + Bexpression* init = (this->no_zero_ + ? NULL + : gogo->backend()->zero_expression(btype)); Bvariable* temp = gogo->backend()->temporary_variable(fndecl, context->bblock(), btype, - zero, true, loc, &decl); + init, true, loc, &decl); Bexpression* ret = gogo->backend()->var_expression(temp, loc); ret = gogo->backend()->address_expression(ret, loc); ret = gogo->backend()->compound_expression(decl, ret, loc); diff --git a/gcc/go/gofrontend/expressions.h b/gcc/go/gofrontend/expressions.h index 1595eb1..38dee04 100644 --- a/gcc/go/gofrontend/expressions.h +++ b/gcc/go/gofrontend/expressions.h @@ -1822,9 +1822,8 @@ class Type_conversion_expression : public Expression // True if a string([]byte) conversion can reuse the backing store // without copying. Only used in string([]byte) conversion. bool no_copy_; - // True if a conversion to interface does not escape, so it does - // not need a heap allocation. Only used in type-to-interface - // conversion. + // True if a conversion does not escape. Used in type-to-interface + // conversions and slice-to/from-string conversions. bool no_escape_; }; @@ -3561,13 +3560,19 @@ class Allocation_expression : public Expression public: Allocation_expression(Type* type, Location location) : Expression(EXPRESSION_ALLOCATION, location), - type_(type), allocate_on_stack_(false) + type_(type), allocate_on_stack_(false), + no_zero_(false) { } void set_allocate_on_stack() { this->allocate_on_stack_ = true; } + // Mark that the allocated memory doesn't need zeroing. + void + set_no_zero() + { this->no_zero_ = true; } + protected: int do_traverse(Traverse*); @@ -3596,6 +3601,8 @@ class Allocation_expression : public Expression Type* type_; // Whether or not this is a stack allocation. bool allocate_on_stack_; + // Whether we don't need to zero the allocated memory. + bool no_zero_; }; // A general composite literal. This is lowered to a type specific @@ -4541,4 +4548,8 @@ class Numeric_constant Type* type_; }; +// Temporary buffer size for string conversions. +// Also known to the runtime as tmpStringBufSize in runtime/string.go. +static const int tmp_string_buf_size = 32; + #endif // !defined(GO_EXPRESSIONS_H) -- 2.7.4