From: Keith Randall Date: Tue, 9 Oct 2018 00:46:45 +0000 (-0700) Subject: cmd/compile: make []byte("...") more efficient X-Git-Tag: go1.12beta1~828 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=ceb0c371d9a535826497289ac7d0b206a59526e6;p=gostls13.git cmd/compile: make []byte("...") more efficient Do []byte(string) conversions more efficiently when the string is a constant. Instead of calling stringtobyteslice, allocate just the space we need and encode the initialization directly. []byte("foo") rewrites to the following pseudocode: var s [3]byte // on heap or stack, depending on whether b escapes s = *(*[3]byte)(&"foo"[0]) // initialize s from the string b = s[:] which generates this assembly: 0x001d 00029 (tmp1.go:9) LEAQ type.[3]uint8(SB), AX 0x0024 00036 (tmp1.go:9) MOVQ AX, (SP) 0x0028 00040 (tmp1.go:9) CALL runtime.newobject(SB) 0x002d 00045 (tmp1.go:9) MOVQ 8(SP), AX 0x0032 00050 (tmp1.go:9) MOVBLZX go.string."foo"+2(SB), CX 0x0039 00057 (tmp1.go:9) MOVWLZX go.string."foo"(SB), DX 0x0040 00064 (tmp1.go:9) MOVW DX, (AX) 0x0043 00067 (tmp1.go:9) MOVB CL, 2(AX) // Then the slice is b = {AX, 3, 3} The generated code is still not optimal, as it still does load/store from read-only memory instead of constant stores. Next CL... Update #26498 Fixes #10170 Change-Id: I4b990b19f9a308f60c8f4f148934acffefe0a5bd Reviewed-on: https://go-review.googlesource.com/c/140698 Run-TryBot: Keith Randall TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- diff --git a/src/cmd/compile/internal/gc/esc.go b/src/cmd/compile/internal/gc/esc.go index eba66d9c67..5beb43d548 100644 --- a/src/cmd/compile/internal/gc/esc.go +++ b/src/cmd/compile/internal/gc/esc.go @@ -798,9 +798,8 @@ func (e *EscState) esc(n *Node, parent *Node) { // gathered here. if n.Esc != EscHeap && n.Type != nil && (n.Type.Width > maxStackVarSize || - (n.Op == ONEW || n.Op == OPTRLIT) && n.Type.Elem().Width >= 1<<16 || + (n.Op == ONEW || n.Op == OPTRLIT) && n.Type.Elem().Width >= maxImplicitStackVarSize || n.Op == OMAKESLICE && !isSmallMakeSlice(n)) { - // isSmallMakeSlice returns false for non-constant len/cap. // If that's the case, print a more accurate escape reason. var msgVerb, escapeMsg string diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go index d8ab5eb39c..605afd6407 100644 --- a/src/cmd/compile/internal/gc/go.go +++ b/src/cmd/compile/internal/gc/go.go @@ -13,8 +13,18 @@ import ( ) const ( - BADWIDTH = types.BADWIDTH + BADWIDTH = types.BADWIDTH + + // maximum size variable which we will allocate on the stack. + // This limit is for explicit variable declarations like "var x T" or "x := ...". maxStackVarSize = 10 * 1024 * 1024 + + // maximum size of implicit variables that we will allocate on the stack. + // p := new(T) allocating T on the stack + // p := &T{} allocating T on the stack + // s := make([]T, n) allocating [n]T on the stack + // s := []byte("...") allocating [n]byte on the stack + maxImplicitStackVarSize = 64 * 1024 ) // isRuntimePkg reports whether p is package runtime. diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go index 80fdc55b5d..c3201c1404 100644 --- a/src/cmd/compile/internal/gc/walk.go +++ b/src/cmd/compile/internal/gc/walk.go @@ -367,7 +367,7 @@ func isSmallMakeSlice(n *Node) bool { } t := n.Type - return smallintconst(l) && smallintconst(r) && (t.Elem().Width == 0 || r.Int64() < (1<<16)/t.Elem().Width) + return smallintconst(l) && smallintconst(r) && (t.Elem().Width == 0 || r.Int64() < maxImplicitStackVarSize/t.Elem().Width) } // walk the whole tree of the body of an @@ -1204,7 +1204,7 @@ opswitch: case ONEW: if n.Esc == EscNone { - if n.Type.Elem().Width >= 1<<16 { + if n.Type.Elem().Width >= maxImplicitStackVarSize { Fatalf("large ONEW with EscNone: %v", n) } r := temp(n.Type.Elem()) @@ -1593,8 +1593,36 @@ opswitch: n = mkcall("slicerunetostring", n.Type, init, a, n.Left) - // stringtoslicebyte(*32[byte], string) []byte; case OSTRARRAYBYTE: + s := n.Left + if Isconst(s, CTSTR) { + sc := s.Val().U.(string) + + // Allocate a [n]byte of the right size. + t := types.NewArray(types.Types[TUINT8], int64(len(sc))) + var a *Node + if n.Esc == EscNone && len(sc) <= maxImplicitStackVarSize { + a = nod(OADDR, temp(t), nil) + } else { + a = callnew(t) + } + p := temp(t.PtrTo()) // *[n]byte + init.Append(typecheck(nod(OAS, p, a), Etop)) + + // Copy from the static string data to the [n]byte. + if len(sc) > 0 { + as := nod(OAS, + nod(OIND, p, nil), + nod(OIND, convnop(nod(OSPTR, s, nil), t.PtrTo()), nil)) + init.Append(typecheck(as, Etop)) + } + + // Slice the [n]byte to a []byte. + n.Op = OSLICEARR + n.Left = p + n = walkexpr(n, init) + break + } a := nodnil() if n.Esc == EscNone { @@ -1604,7 +1632,8 @@ opswitch: a = nod(OADDR, temp(t), nil) } - n = mkcall("stringtoslicebyte", n.Type, init, a, conv(n.Left, types.Types[TSTRING])) + // stringtoslicebyte(*32[byte], string) []byte; + n = mkcall("stringtoslicebyte", n.Type, init, a, conv(s, types.Types[TSTRING])) case OSTRARRAYBYTETMP: // []byte(string) conversion that creates a slice diff --git a/src/cmd/compile/internal/ssa/gen/generic.rules b/src/cmd/compile/internal/ssa/gen/generic.rules index d490e32f3d..8d2691d29c 100644 --- a/src/cmd/compile/internal/ssa/gen/generic.rules +++ b/src/cmd/compile/internal/ssa/gen/generic.rules @@ -816,7 +816,7 @@ // Decomposing StringMake and lowering of StringPtr and StringLen // happens in a later pass, dec, so that these operations are available // to other passes for optimizations. -(StringPtr (StringMake (Const64 [c]) _)) -> (Const64 [c]) +(StringPtr (StringMake (Addr {s} base) _)) -> (Addr {s} base) (StringLen (StringMake _ (Const64 [c]))) -> (Const64 [c]) (ConstString {s}) && config.PtrSize == 4 && s.(string) == "" -> (StringMake (ConstNil) (Const32 [0])) diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index 2f239faa49..26341a9217 100644 --- a/src/cmd/compile/internal/ssa/rewritegeneric.go +++ b/src/cmd/compile/internal/ssa/rewritegeneric.go @@ -28748,9 +28748,9 @@ func rewriteValuegeneric_OpStringLen_0(v *Value) bool { return false } func rewriteValuegeneric_OpStringPtr_0(v *Value) bool { - // match: (StringPtr (StringMake (Const64 [c]) _)) + // match: (StringPtr (StringMake (Addr {s} base) _)) // cond: - // result: (Const64 [c]) + // result: (Addr {s} base) for { v_0 := v.Args[0] if v_0.Op != OpStringMake { @@ -28758,14 +28758,16 @@ func rewriteValuegeneric_OpStringPtr_0(v *Value) bool { } _ = v_0.Args[1] v_0_0 := v_0.Args[0] - if v_0_0.Op != OpConst64 { + if v_0_0.Op != OpAddr { break } t := v_0_0.Type - c := v_0_0.AuxInt - v.reset(OpConst64) + s := v_0_0.Aux + base := v_0_0.Args[0] + v.reset(OpAddr) v.Type = t - v.AuxInt = c + v.Aux = s + v.AddArg(base) return true } return false diff --git a/test/codegen/strings.go b/test/codegen/strings.go index ccb6bd4273..f4adfac0cc 100644 --- a/test/codegen/strings.go +++ b/test/codegen/strings.go @@ -13,3 +13,10 @@ func CountRunes(s string) int { // Issue #24923 // amd64:`.*countrunes` return len([]rune(s)) } + +func ToByteSlice() []byte { // Issue #24698 + // amd64:`LEAQ\ttype\.\[3\]uint8` + // amd64:`CALL\truntime\.newobject` + // amd64:-`.*runtime.stringtoslicebyte` + return []byte("foo") +}