Skip to content

Commit

Permalink
cmd/compile: make []byte("...") more efficient
Browse files Browse the repository at this point in the history
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: Brad Fitzpatrick <bradfitz@golang.org>
  • Loading branch information
randall77 committed Oct 10, 2018
1 parent 05581fc commit ceb0c37
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 14 deletions.
3 changes: 1 addition & 2 deletions src/cmd/compile/internal/gc/esc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion src/cmd/compile/internal/gc/go.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
37 changes: 33 additions & 4 deletions src/cmd/compile/internal/gc/walk.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/ssa/gen/generic.rules
Original file line number Diff line number Diff line change
Expand Up @@ -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]))
Expand Down
14 changes: 8 additions & 6 deletions src/cmd/compile/internal/ssa/rewritegeneric.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions test/codegen/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

0 comments on commit ceb0c37

Please sign in to comment.