Commit ceb0c371 authored by Keith Randall's avatar Keith Randall Committed by Keith Randall

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 <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 05581fcc
......@@ -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
......
......@@ -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.
......
......@@ -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
......
......@@ -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 <t> [c]) _)) -> (Const64 <t> [c])
(StringPtr (StringMake (Addr <t> {s} base) _)) -> (Addr <t> {s} base)
(StringLen (StringMake _ (Const64 <t> [c]))) -> (Const64 <t> [c])
(ConstString {s}) && config.PtrSize == 4 && s.(string) == "" ->
(StringMake (ConstNil) (Const32 <typ.Int> [0]))
......
......@@ -28748,9 +28748,9 @@ func rewriteValuegeneric_OpStringLen_0(v *Value) bool {
return false
}
func rewriteValuegeneric_OpStringPtr_0(v *Value) bool {
// match: (StringPtr (StringMake (Const64 <t> [c]) _))
// match: (StringPtr (StringMake (Addr <t> {s} base) _))
// cond:
// result: (Const64 <t> [c])
// result: (Addr <t> {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
......
......@@ -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")
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment