From b9a59d9f2e78ce497b38a984f62094a53e7dfce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20M=C3=B6hrmann?= Date: Tue, 24 Apr 2018 15:13:08 +0200 Subject: [PATCH] cmd/compile: optimize len([]rune(string)) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new runtime function to count runes in a string. Modifies the compiler to detect the pattern len([]rune(string)) and replaces it with the new rune counting runtime function. RuneCount/lenruneslice/ASCII 27.8ns ± 2% 14.5ns ± 3% -47.70% (p=0.000 n=10+10) RuneCount/lenruneslice/Japanese 126ns ± 2% 60ns ± 2% -52.03% (p=0.000 n=10+10) RuneCount/lenruneslice/MixedLength 104ns ± 2% 50ns ± 1% -51.71% (p=0.000 n=10+9) Fixes #24923 Change-Id: Ie9c7e7391a4e2cca675c5cdcc1e5ce7d523948b9 Reviewed-on: https://go-review.googlesource.com/108985 Run-TryBot: Martin Möhrmann TryBot-Result: Gobot Gobot Reviewed-by: Josh Bleecher Snyder --- src/cmd/compile/internal/gc/builtin.go | 326 +++++++++--------- .../compile/internal/gc/builtin/runtime.go | 4 +- src/cmd/compile/internal/gc/order.go | 9 +- src/cmd/compile/internal/gc/walk.go | 12 + src/runtime/string_test.go | 38 ++ src/runtime/utf8.go | 9 + src/unicode/utf8/utf8_test.go | 21 +- test/codegen/strings.go | 15 + 8 files changed, 265 insertions(+), 169 deletions(-) create mode 100644 test/codegen/strings.go diff --git a/src/cmd/compile/internal/gc/builtin.go b/src/cmd/compile/internal/gc/builtin.go index 4259fb41530..3ca1adc1f76 100644 --- a/src/cmd/compile/internal/gc/builtin.go +++ b/src/cmd/compile/internal/gc/builtin.go @@ -46,112 +46,113 @@ var runtimeDecls = [...]struct { {"slicerunetostring", funcTag, 42}, {"stringtoslicebyte", funcTag, 43}, {"stringtoslicerune", funcTag, 46}, - {"decoderune", funcTag, 47}, - {"slicecopy", funcTag, 49}, - {"slicestringcopy", funcTag, 50}, - {"convI2I", funcTag, 51}, - {"convT2E", funcTag, 52}, - {"convT2E16", funcTag, 51}, - {"convT2E32", funcTag, 51}, - {"convT2E64", funcTag, 51}, - {"convT2Estring", funcTag, 52}, - {"convT2Eslice", funcTag, 52}, - {"convT2Enoptr", funcTag, 52}, - {"convT2I", funcTag, 52}, - {"convT2I16", funcTag, 51}, - {"convT2I32", funcTag, 51}, - {"convT2I64", funcTag, 51}, - {"convT2Istring", funcTag, 52}, - {"convT2Islice", funcTag, 52}, - {"convT2Inoptr", funcTag, 52}, - {"assertE2I", funcTag, 51}, - {"assertE2I2", funcTag, 53}, - {"assertI2I", funcTag, 51}, - {"assertI2I2", funcTag, 53}, - {"panicdottypeE", funcTag, 54}, - {"panicdottypeI", funcTag, 54}, - {"panicnildottype", funcTag, 55}, - {"ifaceeq", funcTag, 58}, - {"efaceeq", funcTag, 58}, - {"fastrand", funcTag, 60}, - {"makemap64", funcTag, 62}, - {"makemap", funcTag, 63}, - {"makemap_small", funcTag, 64}, - {"mapaccess1", funcTag, 65}, - {"mapaccess1_fast32", funcTag, 66}, - {"mapaccess1_fast64", funcTag, 66}, - {"mapaccess1_faststr", funcTag, 66}, - {"mapaccess1_fat", funcTag, 67}, - {"mapaccess2", funcTag, 68}, - {"mapaccess2_fast32", funcTag, 69}, - {"mapaccess2_fast64", funcTag, 69}, - {"mapaccess2_faststr", funcTag, 69}, - {"mapaccess2_fat", funcTag, 70}, - {"mapassign", funcTag, 65}, - {"mapassign_fast32", funcTag, 66}, - {"mapassign_fast32ptr", funcTag, 66}, - {"mapassign_fast64", funcTag, 66}, - {"mapassign_fast64ptr", funcTag, 66}, - {"mapassign_faststr", funcTag, 66}, - {"mapiterinit", funcTag, 71}, - {"mapdelete", funcTag, 71}, - {"mapdelete_fast32", funcTag, 72}, - {"mapdelete_fast64", funcTag, 72}, - {"mapdelete_faststr", funcTag, 72}, - {"mapiternext", funcTag, 73}, - {"makechan64", funcTag, 75}, - {"makechan", funcTag, 76}, - {"chanrecv1", funcTag, 78}, - {"chanrecv2", funcTag, 79}, - {"chansend1", funcTag, 81}, + {"slicecopy", funcTag, 48}, + {"slicestringcopy", funcTag, 49}, + {"decoderune", funcTag, 50}, + {"countrunes", funcTag, 51}, + {"convI2I", funcTag, 52}, + {"convT2E", funcTag, 53}, + {"convT2E16", funcTag, 52}, + {"convT2E32", funcTag, 52}, + {"convT2E64", funcTag, 52}, + {"convT2Estring", funcTag, 53}, + {"convT2Eslice", funcTag, 53}, + {"convT2Enoptr", funcTag, 53}, + {"convT2I", funcTag, 53}, + {"convT2I16", funcTag, 52}, + {"convT2I32", funcTag, 52}, + {"convT2I64", funcTag, 52}, + {"convT2Istring", funcTag, 53}, + {"convT2Islice", funcTag, 53}, + {"convT2Inoptr", funcTag, 53}, + {"assertE2I", funcTag, 52}, + {"assertE2I2", funcTag, 54}, + {"assertI2I", funcTag, 52}, + {"assertI2I2", funcTag, 54}, + {"panicdottypeE", funcTag, 55}, + {"panicdottypeI", funcTag, 55}, + {"panicnildottype", funcTag, 56}, + {"ifaceeq", funcTag, 59}, + {"efaceeq", funcTag, 59}, + {"fastrand", funcTag, 61}, + {"makemap64", funcTag, 63}, + {"makemap", funcTag, 64}, + {"makemap_small", funcTag, 65}, + {"mapaccess1", funcTag, 66}, + {"mapaccess1_fast32", funcTag, 67}, + {"mapaccess1_fast64", funcTag, 67}, + {"mapaccess1_faststr", funcTag, 67}, + {"mapaccess1_fat", funcTag, 68}, + {"mapaccess2", funcTag, 69}, + {"mapaccess2_fast32", funcTag, 70}, + {"mapaccess2_fast64", funcTag, 70}, + {"mapaccess2_faststr", funcTag, 70}, + {"mapaccess2_fat", funcTag, 71}, + {"mapassign", funcTag, 66}, + {"mapassign_fast32", funcTag, 67}, + {"mapassign_fast32ptr", funcTag, 67}, + {"mapassign_fast64", funcTag, 67}, + {"mapassign_fast64ptr", funcTag, 67}, + {"mapassign_faststr", funcTag, 67}, + {"mapiterinit", funcTag, 72}, + {"mapdelete", funcTag, 72}, + {"mapdelete_fast32", funcTag, 73}, + {"mapdelete_fast64", funcTag, 73}, + {"mapdelete_faststr", funcTag, 73}, + {"mapiternext", funcTag, 74}, + {"makechan64", funcTag, 76}, + {"makechan", funcTag, 77}, + {"chanrecv1", funcTag, 79}, + {"chanrecv2", funcTag, 80}, + {"chansend1", funcTag, 82}, {"closechan", funcTag, 23}, - {"writeBarrier", varTag, 83}, - {"typedmemmove", funcTag, 84}, - {"typedmemclr", funcTag, 85}, - {"typedslicecopy", funcTag, 86}, - {"selectnbsend", funcTag, 87}, - {"selectnbrecv", funcTag, 88}, - {"selectnbrecv2", funcTag, 90}, - {"selectsetpc", funcTag, 55}, - {"selectgo", funcTag, 91}, + {"writeBarrier", varTag, 84}, + {"typedmemmove", funcTag, 85}, + {"typedmemclr", funcTag, 86}, + {"typedslicecopy", funcTag, 87}, + {"selectnbsend", funcTag, 88}, + {"selectnbrecv", funcTag, 89}, + {"selectnbrecv2", funcTag, 91}, + {"selectsetpc", funcTag, 56}, + {"selectgo", funcTag, 92}, {"block", funcTag, 5}, - {"makeslice", funcTag, 93}, - {"makeslice64", funcTag, 94}, - {"growslice", funcTag, 95}, - {"memmove", funcTag, 96}, - {"memclrNoHeapPointers", funcTag, 97}, - {"memclrHasPointers", funcTag, 97}, - {"memequal", funcTag, 98}, - {"memequal8", funcTag, 99}, - {"memequal16", funcTag, 99}, - {"memequal32", funcTag, 99}, - {"memequal64", funcTag, 99}, - {"memequal128", funcTag, 99}, - {"int64div", funcTag, 100}, - {"uint64div", funcTag, 101}, - {"int64mod", funcTag, 100}, - {"uint64mod", funcTag, 101}, - {"float64toint64", funcTag, 102}, - {"float64touint64", funcTag, 103}, - {"float64touint32", funcTag, 104}, - {"int64tofloat64", funcTag, 105}, - {"uint64tofloat64", funcTag, 106}, - {"uint32tofloat64", funcTag, 107}, - {"complex128div", funcTag, 108}, - {"racefuncenter", funcTag, 109}, + {"makeslice", funcTag, 94}, + {"makeslice64", funcTag, 95}, + {"growslice", funcTag, 96}, + {"memmove", funcTag, 97}, + {"memclrNoHeapPointers", funcTag, 98}, + {"memclrHasPointers", funcTag, 98}, + {"memequal", funcTag, 99}, + {"memequal8", funcTag, 100}, + {"memequal16", funcTag, 100}, + {"memequal32", funcTag, 100}, + {"memequal64", funcTag, 100}, + {"memequal128", funcTag, 100}, + {"int64div", funcTag, 101}, + {"uint64div", funcTag, 102}, + {"int64mod", funcTag, 101}, + {"uint64mod", funcTag, 102}, + {"float64toint64", funcTag, 103}, + {"float64touint64", funcTag, 104}, + {"float64touint32", funcTag, 105}, + {"int64tofloat64", funcTag, 106}, + {"uint64tofloat64", funcTag, 107}, + {"uint32tofloat64", funcTag, 108}, + {"complex128div", funcTag, 109}, + {"racefuncenter", funcTag, 110}, {"racefuncexit", funcTag, 5}, - {"raceread", funcTag, 109}, - {"racewrite", funcTag, 109}, - {"racereadrange", funcTag, 110}, - {"racewriterange", funcTag, 110}, - {"msanread", funcTag, 110}, - {"msanwrite", funcTag, 110}, + {"raceread", funcTag, 110}, + {"racewrite", funcTag, 110}, + {"racereadrange", funcTag, 111}, + {"racewriterange", funcTag, 111}, + {"msanread", funcTag, 111}, + {"msanwrite", funcTag, 111}, {"support_popcnt", varTag, 11}, {"support_sse41", varTag, 11}, } func runtimeTypes() []*types.Type { - var typs [111]*types.Type + var typs [112]*types.Type typs[0] = types.Bytetype typs[1] = types.NewPtr(typs[0]) typs[2] = types.Types[TANY] @@ -199,69 +200,70 @@ func runtimeTypes() []*types.Type { typs[44] = types.NewArray(typs[40], 32) typs[45] = types.NewPtr(typs[44]) typs[46] = functype(nil, []*Node{anonfield(typs[45]), anonfield(typs[21])}, []*Node{anonfield(typs[41])}) - typs[47] = functype(nil, []*Node{anonfield(typs[21]), anonfield(typs[32])}, []*Node{anonfield(typs[40]), anonfield(typs[32])}) - typs[48] = types.Types[TUINTPTR] - typs[49] = functype(nil, []*Node{anonfield(typs[2]), anonfield(typs[2]), anonfield(typs[48])}, []*Node{anonfield(typs[32])}) - typs[50] = functype(nil, []*Node{anonfield(typs[2]), anonfield(typs[2])}, []*Node{anonfield(typs[32])}) - typs[51] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[2])}, []*Node{anonfield(typs[2])}) - typs[52] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[3])}, []*Node{anonfield(typs[2])}) - typs[53] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[2])}, []*Node{anonfield(typs[2]), anonfield(typs[11])}) - typs[54] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[1]), anonfield(typs[1])}, nil) - typs[55] = functype(nil, []*Node{anonfield(typs[1])}, nil) - typs[56] = types.NewPtr(typs[48]) - typs[57] = types.Types[TUNSAFEPTR] - typs[58] = functype(nil, []*Node{anonfield(typs[56]), anonfield(typs[57]), anonfield(typs[57])}, []*Node{anonfield(typs[11])}) - typs[59] = types.Types[TUINT32] - typs[60] = functype(nil, nil, []*Node{anonfield(typs[59])}) - typs[61] = types.NewMap(typs[2], typs[2]) - typs[62] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15]), anonfield(typs[3])}, []*Node{anonfield(typs[61])}) - typs[63] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[32]), anonfield(typs[3])}, []*Node{anonfield(typs[61])}) - typs[64] = functype(nil, nil, []*Node{anonfield(typs[61])}) - typs[65] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[61]), anonfield(typs[3])}, []*Node{anonfield(typs[3])}) - typs[66] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[61]), anonfield(typs[2])}, []*Node{anonfield(typs[3])}) - typs[67] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[61]), anonfield(typs[3]), anonfield(typs[1])}, []*Node{anonfield(typs[3])}) - typs[68] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[61]), anonfield(typs[3])}, []*Node{anonfield(typs[3]), anonfield(typs[11])}) - typs[69] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[61]), anonfield(typs[2])}, []*Node{anonfield(typs[3]), anonfield(typs[11])}) - typs[70] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[61]), anonfield(typs[3]), anonfield(typs[1])}, []*Node{anonfield(typs[3]), anonfield(typs[11])}) - typs[71] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[61]), anonfield(typs[3])}, nil) - typs[72] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[61]), anonfield(typs[2])}, nil) - typs[73] = functype(nil, []*Node{anonfield(typs[3])}, nil) - typs[74] = types.NewChan(typs[2], types.Cboth) - typs[75] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15])}, []*Node{anonfield(typs[74])}) - typs[76] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[32])}, []*Node{anonfield(typs[74])}) - typs[77] = types.NewChan(typs[2], types.Crecv) - typs[78] = functype(nil, []*Node{anonfield(typs[77]), anonfield(typs[3])}, nil) - typs[79] = functype(nil, []*Node{anonfield(typs[77]), anonfield(typs[3])}, []*Node{anonfield(typs[11])}) - typs[80] = types.NewChan(typs[2], types.Csend) - typs[81] = functype(nil, []*Node{anonfield(typs[80]), anonfield(typs[3])}, nil) - typs[82] = types.NewArray(typs[0], 3) - typs[83] = tostruct([]*Node{namedfield("enabled", typs[11]), namedfield("pad", typs[82]), namedfield("needed", typs[11]), namedfield("cgo", typs[11]), namedfield("alignme", typs[17])}) - typs[84] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[3]), anonfield(typs[3])}, nil) - typs[85] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[3])}, nil) - typs[86] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[2]), anonfield(typs[2])}, []*Node{anonfield(typs[32])}) - typs[87] = functype(nil, []*Node{anonfield(typs[80]), anonfield(typs[3])}, []*Node{anonfield(typs[11])}) - typs[88] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[77])}, []*Node{anonfield(typs[11])}) - typs[89] = types.NewPtr(typs[11]) - typs[90] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[89]), anonfield(typs[77])}, []*Node{anonfield(typs[11])}) - typs[91] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[1]), anonfield(typs[32])}, []*Node{anonfield(typs[32]), anonfield(typs[11])}) - typs[92] = types.NewSlice(typs[2]) - typs[93] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[32]), anonfield(typs[32])}, []*Node{anonfield(typs[92])}) - typs[94] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15]), anonfield(typs[15])}, []*Node{anonfield(typs[92])}) - typs[95] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[92]), anonfield(typs[32])}, []*Node{anonfield(typs[92])}) - typs[96] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[3]), anonfield(typs[48])}, nil) - typs[97] = functype(nil, []*Node{anonfield(typs[57]), anonfield(typs[48])}, nil) - typs[98] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[3]), anonfield(typs[48])}, []*Node{anonfield(typs[11])}) - typs[99] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[3])}, []*Node{anonfield(typs[11])}) - typs[100] = functype(nil, []*Node{anonfield(typs[15]), anonfield(typs[15])}, []*Node{anonfield(typs[15])}) - typs[101] = functype(nil, []*Node{anonfield(typs[17]), anonfield(typs[17])}, []*Node{anonfield(typs[17])}) - typs[102] = functype(nil, []*Node{anonfield(typs[13])}, []*Node{anonfield(typs[15])}) - typs[103] = functype(nil, []*Node{anonfield(typs[13])}, []*Node{anonfield(typs[17])}) - typs[104] = functype(nil, []*Node{anonfield(typs[13])}, []*Node{anonfield(typs[59])}) - typs[105] = functype(nil, []*Node{anonfield(typs[15])}, []*Node{anonfield(typs[13])}) - typs[106] = functype(nil, []*Node{anonfield(typs[17])}, []*Node{anonfield(typs[13])}) - typs[107] = functype(nil, []*Node{anonfield(typs[59])}, []*Node{anonfield(typs[13])}) - typs[108] = functype(nil, []*Node{anonfield(typs[19]), anonfield(typs[19])}, []*Node{anonfield(typs[19])}) - typs[109] = functype(nil, []*Node{anonfield(typs[48])}, nil) - typs[110] = functype(nil, []*Node{anonfield(typs[48]), anonfield(typs[48])}, nil) + typs[47] = types.Types[TUINTPTR] + typs[48] = functype(nil, []*Node{anonfield(typs[2]), anonfield(typs[2]), anonfield(typs[47])}, []*Node{anonfield(typs[32])}) + typs[49] = functype(nil, []*Node{anonfield(typs[2]), anonfield(typs[2])}, []*Node{anonfield(typs[32])}) + typs[50] = functype(nil, []*Node{anonfield(typs[21]), anonfield(typs[32])}, []*Node{anonfield(typs[40]), anonfield(typs[32])}) + typs[51] = functype(nil, []*Node{anonfield(typs[21])}, []*Node{anonfield(typs[32])}) + typs[52] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[2])}, []*Node{anonfield(typs[2])}) + typs[53] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[3])}, []*Node{anonfield(typs[2])}) + typs[54] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[2])}, []*Node{anonfield(typs[2]), anonfield(typs[11])}) + typs[55] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[1]), anonfield(typs[1])}, nil) + typs[56] = functype(nil, []*Node{anonfield(typs[1])}, nil) + typs[57] = types.NewPtr(typs[47]) + typs[58] = types.Types[TUNSAFEPTR] + typs[59] = functype(nil, []*Node{anonfield(typs[57]), anonfield(typs[58]), anonfield(typs[58])}, []*Node{anonfield(typs[11])}) + typs[60] = types.Types[TUINT32] + typs[61] = functype(nil, nil, []*Node{anonfield(typs[60])}) + typs[62] = types.NewMap(typs[2], typs[2]) + typs[63] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15]), anonfield(typs[3])}, []*Node{anonfield(typs[62])}) + typs[64] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[32]), anonfield(typs[3])}, []*Node{anonfield(typs[62])}) + typs[65] = functype(nil, nil, []*Node{anonfield(typs[62])}) + typs[66] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[62]), anonfield(typs[3])}, []*Node{anonfield(typs[3])}) + typs[67] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[62]), anonfield(typs[2])}, []*Node{anonfield(typs[3])}) + typs[68] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[62]), anonfield(typs[3]), anonfield(typs[1])}, []*Node{anonfield(typs[3])}) + typs[69] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[62]), anonfield(typs[3])}, []*Node{anonfield(typs[3]), anonfield(typs[11])}) + typs[70] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[62]), anonfield(typs[2])}, []*Node{anonfield(typs[3]), anonfield(typs[11])}) + typs[71] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[62]), anonfield(typs[3]), anonfield(typs[1])}, []*Node{anonfield(typs[3]), anonfield(typs[11])}) + typs[72] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[62]), anonfield(typs[3])}, nil) + typs[73] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[62]), anonfield(typs[2])}, nil) + typs[74] = functype(nil, []*Node{anonfield(typs[3])}, nil) + typs[75] = types.NewChan(typs[2], types.Cboth) + typs[76] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15])}, []*Node{anonfield(typs[75])}) + typs[77] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[32])}, []*Node{anonfield(typs[75])}) + typs[78] = types.NewChan(typs[2], types.Crecv) + typs[79] = functype(nil, []*Node{anonfield(typs[78]), anonfield(typs[3])}, nil) + typs[80] = functype(nil, []*Node{anonfield(typs[78]), anonfield(typs[3])}, []*Node{anonfield(typs[11])}) + typs[81] = types.NewChan(typs[2], types.Csend) + typs[82] = functype(nil, []*Node{anonfield(typs[81]), anonfield(typs[3])}, nil) + typs[83] = types.NewArray(typs[0], 3) + typs[84] = tostruct([]*Node{namedfield("enabled", typs[11]), namedfield("pad", typs[83]), namedfield("needed", typs[11]), namedfield("cgo", typs[11]), namedfield("alignme", typs[17])}) + typs[85] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[3]), anonfield(typs[3])}, nil) + typs[86] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[3])}, nil) + typs[87] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[2]), anonfield(typs[2])}, []*Node{anonfield(typs[32])}) + typs[88] = functype(nil, []*Node{anonfield(typs[81]), anonfield(typs[3])}, []*Node{anonfield(typs[11])}) + typs[89] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[78])}, []*Node{anonfield(typs[11])}) + typs[90] = types.NewPtr(typs[11]) + typs[91] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[90]), anonfield(typs[78])}, []*Node{anonfield(typs[11])}) + typs[92] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[1]), anonfield(typs[32])}, []*Node{anonfield(typs[32]), anonfield(typs[11])}) + typs[93] = types.NewSlice(typs[2]) + typs[94] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[32]), anonfield(typs[32])}, []*Node{anonfield(typs[93])}) + typs[95] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[15]), anonfield(typs[15])}, []*Node{anonfield(typs[93])}) + typs[96] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[93]), anonfield(typs[32])}, []*Node{anonfield(typs[93])}) + typs[97] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[3]), anonfield(typs[47])}, nil) + typs[98] = functype(nil, []*Node{anonfield(typs[58]), anonfield(typs[47])}, nil) + typs[99] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[3]), anonfield(typs[47])}, []*Node{anonfield(typs[11])}) + typs[100] = functype(nil, []*Node{anonfield(typs[3]), anonfield(typs[3])}, []*Node{anonfield(typs[11])}) + typs[101] = functype(nil, []*Node{anonfield(typs[15]), anonfield(typs[15])}, []*Node{anonfield(typs[15])}) + typs[102] = functype(nil, []*Node{anonfield(typs[17]), anonfield(typs[17])}, []*Node{anonfield(typs[17])}) + typs[103] = functype(nil, []*Node{anonfield(typs[13])}, []*Node{anonfield(typs[15])}) + typs[104] = functype(nil, []*Node{anonfield(typs[13])}, []*Node{anonfield(typs[17])}) + typs[105] = functype(nil, []*Node{anonfield(typs[13])}, []*Node{anonfield(typs[60])}) + typs[106] = functype(nil, []*Node{anonfield(typs[15])}, []*Node{anonfield(typs[13])}) + typs[107] = functype(nil, []*Node{anonfield(typs[17])}, []*Node{anonfield(typs[13])}) + typs[108] = functype(nil, []*Node{anonfield(typs[60])}, []*Node{anonfield(typs[13])}) + typs[109] = functype(nil, []*Node{anonfield(typs[19]), anonfield(typs[19])}, []*Node{anonfield(typs[19])}) + typs[110] = functype(nil, []*Node{anonfield(typs[47])}, nil) + typs[111] = functype(nil, []*Node{anonfield(typs[47]), anonfield(typs[47])}, nil) return typs[:] } diff --git a/src/cmd/compile/internal/gc/builtin/runtime.go b/src/cmd/compile/internal/gc/builtin/runtime.go index ae1850c72f4..1d3f17c0d1a 100644 --- a/src/cmd/compile/internal/gc/builtin/runtime.go +++ b/src/cmd/compile/internal/gc/builtin/runtime.go @@ -55,10 +55,12 @@ func slicebytetostringtmp([]byte) string func slicerunetostring(*[32]byte, []rune) string func stringtoslicebyte(*[32]byte, string) []byte func stringtoslicerune(*[32]rune, string) []rune -func decoderune(string, int) (retv rune, retk int) func slicecopy(to any, fr any, wid uintptr) int func slicestringcopy(to any, fr any) int +func decoderune(string, int) (retv rune, retk int) +func countrunes(string) int + // interface conversions func convI2I(typ *byte, elem any) (ret any) diff --git a/src/cmd/compile/internal/gc/order.go b/src/cmd/compile/internal/gc/order.go index 1a10587797c..45a3b5cc426 100644 --- a/src/cmd/compile/internal/gc/order.go +++ b/src/cmd/compile/internal/gc/order.go @@ -1098,7 +1098,14 @@ func (o *Order) expr(n, lhs *Node) *Node { OSTRARRAYBYTE, OSTRARRAYBYTETMP, OSTRARRAYRUNE: - o.call(n) + + if isRuneCount(n) { + // len([]rune(s)) is rewritten to runtime.countrunes(s) later. + n.Left.Left = o.expr(n.Left.Left, nil) + } else { + o.call(n) + } + if lhs == nil || lhs.Op != ONAME || instrumenting { n = o.copyExpr(n, n.Type, false) } diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go index 3046b9dda87..edf06579234 100644 --- a/src/cmd/compile/internal/gc/walk.go +++ b/src/cmd/compile/internal/gc/walk.go @@ -538,6 +538,12 @@ opswitch: n.Left = walkexpr(n.Left, init) case OLEN, OCAP: + if isRuneCount(n) { + // Replace len([]rune(string)) with runtime.countrunes(string). + n = mkcall("countrunes", n.Type, init, conv(n.Left.Left, types.Types[TSTRING])) + break + } + n.Left = walkexpr(n.Left, init) // replace len(*[10]int) with 10. @@ -4085,3 +4091,9 @@ func canMergeLoads() bool { } return false } + +// isRuneCount reports whether n is of the form len([]rune(string)). +// These are optimized into a call to runtime.runecount. +func isRuneCount(n *Node) bool { + return Debug['N'] == 0 && !instrumenting && n.Op == OLEN && n.Left.Op == OSTRARRAYRUNE +} diff --git a/src/runtime/string_test.go b/src/runtime/string_test.go index c6e925d2e07..678ff003636 100644 --- a/src/runtime/string_test.go +++ b/src/runtime/string_test.go @@ -9,6 +9,7 @@ import ( "strconv" "strings" "testing" + "unicode/utf8" ) // Strings and slices that don't escape and fit into tmpBuf are stack allocated, @@ -110,6 +111,43 @@ var stringdata = []struct{ name, data string }{ {"MixedLength", "$Ѐࠀက퀀𐀀\U00040000\U0010FFFF"}, } +var sinkInt int + +func BenchmarkRuneCount(b *testing.B) { + // Each sub-benchmark counts the runes in a string in a different way. + b.Run("lenruneslice", func(b *testing.B) { + for _, sd := range stringdata { + b.Run(sd.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + sinkInt += len([]rune(sd.data)) + } + }) + } + }) + b.Run("rangeloop", func(b *testing.B) { + for _, sd := range stringdata { + b.Run(sd.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + n := 0 + for range sd.data { + n++ + } + sinkInt += n + } + }) + } + }) + b.Run("utf8.RuneCountInString", func(b *testing.B) { + for _, sd := range stringdata { + b.Run(sd.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + sinkInt += utf8.RuneCountInString(sd.data) + } + }) + } + }) +} + func BenchmarkRuneIterate(b *testing.B) { b.Run("range", func(b *testing.B) { for _, sd := range stringdata { diff --git a/src/runtime/utf8.go b/src/runtime/utf8.go index 24ef1792140..6bf596581dd 100644 --- a/src/runtime/utf8.go +++ b/src/runtime/utf8.go @@ -39,6 +39,15 @@ const ( hicb = 0xBF // 1011 1111 ) +// countrunes returns the number of runes in s. +func countrunes(s string) int { + n := 0 + for range s { + n++ + } + return n +} + // decoderune returns the non-ASCII rune at the start of // s[k:] and the index after the rune in s. // diff --git a/src/unicode/utf8/utf8_test.go b/src/unicode/utf8/utf8_test.go index dc9c4251bd9..359461bd057 100644 --- a/src/unicode/utf8/utf8_test.go +++ b/src/unicode/utf8/utf8_test.go @@ -212,14 +212,25 @@ func TestSequencing(t *testing.T) { } } -// Check that a range loop and a []int conversion visit the same runes. +func runtimeRuneCount(s string) int { + return len([]rune(s)) // Replaced by gc with call to runtime.countrunes(s). +} + +// Check that a range loop, len([]rune(string)) optimization and +// []rune conversions visit the same runes. // Not really a test of this package, but the assumption is used here and -// it's good to verify -func TestIntConversion(t *testing.T) { +// it's good to verify. +func TestRuntimeConversion(t *testing.T) { for _, ts := range testStrings { + count := RuneCountInString(ts) + if n := runtimeRuneCount(ts); n != count { + t.Errorf("%q: len([]rune()) counted %d runes; got %d from RuneCountInString", ts, n, count) + break + } + runes := []rune(ts) - if RuneCountInString(ts) != len(runes) { - t.Errorf("%q: expected %d runes; got %d", ts, len(runes), RuneCountInString(ts)) + if n := len(runes); n != count { + t.Errorf("%q: []rune() has length %d; got %d from RuneCountInString", ts, n, count) break } i := 0 diff --git a/test/codegen/strings.go b/test/codegen/strings.go new file mode 100644 index 00000000000..ccb6bd4273a --- /dev/null +++ b/test/codegen/strings.go @@ -0,0 +1,15 @@ +// asmcheck + +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package codegen + +// This file contains code generation tests related to the handling of +// string types. + +func CountRunes(s string) int { // Issue #24923 + // amd64:`.*countrunes` + return len([]rune(s)) +}