aboutsummaryrefslogtreecommitdiff
path: root/src/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'src/runtime')
-rw-r--r--src/runtime/string.go72
-rw-r--r--src/runtime/string_test.go11
2 files changed, 68 insertions, 15 deletions
diff --git a/src/runtime/string.go b/src/runtime/string.go
index 6f7de4de1e..9ec6f320eb 100644
--- a/src/runtime/string.go
+++ b/src/runtime/string.go
@@ -8,7 +8,18 @@ import (
"unsafe"
)
-func concatstrings(a []string) string {
+// The constant is known to the compiler.
+// There is no fundamental theory behind this number.
+const tmpStringBufSize = 32
+
+type tmpBuf [tmpStringBufSize]byte
+
+// concatstrings implements a Go string concatenation x+y+z+...
+// The operands are passed in the slice a.
+// If buf != nil, the compiler has determined that the result does not
+// escape the calling function, so the string data can be stored in buf
+// if small enough.
+func concatstrings(buf *tmpBuf, a []string) string {
idx := 0
l := 0
count := 0
@@ -27,10 +38,14 @@ func concatstrings(a []string) string {
if count == 0 {
return ""
}
- if count == 1 {
+
+ // If there is just one string and either it is not on the stack
+ // or our result does not escape the calling frame (buf != nil),
+ // then we can return that string directly.
+ if count == 1 && (buf != nil || !stringDataOnStack(a[idx])) {
return a[idx]
}
- s, b := rawstring(l)
+ s, b := rawstringtmp(buf, l)
l = 0
for _, x := range a {
copy(b[l:], x)
@@ -39,34 +54,61 @@ func concatstrings(a []string) string {
return s
}
-func concatstring2(a [2]string) string {
- return concatstrings(a[:])
+func concatstring2(buf *tmpBuf, a [2]string) string {
+ return concatstrings(buf, a[:])
}
-func concatstring3(a [3]string) string {
- return concatstrings(a[:])
+func concatstring3(buf *tmpBuf, a [3]string) string {
+ return concatstrings(buf, a[:])
}
-func concatstring4(a [4]string) string {
- return concatstrings(a[:])
+func concatstring4(buf *tmpBuf, a [4]string) string {
+ return concatstrings(buf, a[:])
}
-func concatstring5(a [5]string) string {
- return concatstrings(a[:])
+func concatstring5(buf *tmpBuf, a [5]string) string {
+ return concatstrings(buf, a[:])
}
-func slicebytetostring(b []byte) string {
- if raceenabled && len(b) > 0 {
+// Buf is a fixed-size buffer for the result,
+// it is not nil if the result does not escape.
+func slicebytetostring(buf *tmpBuf, b []byte) string {
+ l := len(b)
+ if l == 0 {
+ // Turns out to be a relatively common case.
+ // Consider that you want to parse out data between parens in "foo()bar",
+ // you find the indices and convert the subslice to string.
+ return ""
+ }
+ if raceenabled && l > 0 {
racereadrangepc(unsafe.Pointer(&b[0]),
- uintptr(len(b)),
+ uintptr(l),
getcallerpc(unsafe.Pointer(&b)),
funcPC(slicebytetostring))
}
- s, c := rawstring(len(b))
+ s, c := rawstringtmp(buf, l)
copy(c, b)
return s
}
+// stringDataOnStack reports whether the string's data is
+// stored on the current goroutine's stack.
+func stringDataOnStack(s string) bool {
+ ptr := uintptr((*stringStruct)(unsafe.Pointer(&s)).str)
+ stk := getg().stack
+ return stk.lo <= ptr && ptr < stk.hi
+}
+
+func rawstringtmp(buf *tmpBuf, l int) (s string, b []byte) {
+ if buf != nil && l <= len(buf) {
+ b = buf[:l]
+ s = slicebytetostringtmp(b)
+ } else {
+ s, b = rawstring(l)
+ }
+ return
+}
+
func slicebytetostringtmp(b []byte) string {
// Return a "string" referring to the actual []byte bytes.
// This is only for use by internal compiler optimizations
diff --git a/src/runtime/string_test.go b/src/runtime/string_test.go
index 03c8948467..249f431e18 100644
--- a/src/runtime/string_test.go
+++ b/src/runtime/string_test.go
@@ -175,3 +175,14 @@ func TestCompareTempString(t *testing.T) {
t.Fatalf("want 0 allocs, got %v", n)
}
}
+
+func TestStringOnStack(t *testing.T) {
+ s := ""
+ for i := 0; i < 3; i++ {
+ s = "a" + s + "b" + s + "c"
+ }
+
+ if want := "aaabcbabccbaabcbabccc"; s != want {
+ t.Fatalf("want: '%v', got '%v'", want, s)
+ }
+}