cmd/compile: rewrite empty makeslice to zerobase pointer

make\(\[\][a-zA-Z0-9]+, 0\) is seen 52 times in the go source.
And at least 391 times on internet:
https://grep.app/search?q=make%5C%28%5C%5B%5C%5D%5Ba-zA-Z0-9%5D%2B%2C%200%5C%29&regexp=true
This used to compile to calling runtime.makeslice.
However we can copy what we do for []T{}, just use a zerobase pointer.

On my machine this is 10x faster (from 3ns to 0.3ns).
Note that an empty loop also runs in 0.3ns,
so this really is free when you count superscallar execution.

Change-Id: I1cfe7e69f5a7a4dabbc71912ce6a4f8a2d4a7f3c
Reviewed-on: https://go-review.googlesource.com/c/go/+/454036
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Keith Randall <khr@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
Run-TryBot: Jakub Ciolek <jakub@ciolek.dev>
This commit is contained in:
Jorropo 2022-11-30 09:45:29 +01:00 committed by Keith Randall
parent 57236fe9f7
commit fc814056aa
4 changed files with 118 additions and 0 deletions

View File

@ -2067,6 +2067,17 @@
&& canLoadUnaligned(config) && config.PtrSize == 8
=> (MakeResult (Eq64 (Load <typ.Int64> sptr mem) (Const64 <typ.Int64> [int64(read64(scon,0,config.ctxt.Arch.ByteOrder))])) mem)
// Recognise make([]T, 0) and replace it with a pointer to the zerobase
(SelectN [0] call:(StaticLECall _ (Const(64|32) [0]) (Const(64|32) [0]) _))
&& isSameCall(call.Aux, "runtime.makeslice")
&& clobberIfDead(call)
=> (Addr {ir.Syms.Zerobase} (SB))
(SelectN [1] call:(StaticLECall _ (Const(64|32) [0]) (Const(64|32) [0]) mem))
&& isSameCall(call.Aux, "runtime.makeslice")
&& clobberIfDead(call)
=> mem
// Evaluate constant address comparisons.
(EqPtr x x) => (ConstBool [true])
(NeqPtr x x) => (ConstBool [false])

View File

@ -585,6 +585,7 @@ func fprint(w io.Writer, n Node) {
"cmd/internal/obj",
"cmd/compile/internal/base",
"cmd/compile/internal/types",
"cmd/compile/internal/ir",
}, n.Arch.imports...) {
fmt.Fprintf(w, "import %q\n", path)
}

View File

@ -4,6 +4,7 @@ package ssa
import "math"
import "cmd/compile/internal/types"
import "cmd/compile/internal/ir"
func rewriteValuegeneric(v *Value) bool {
switch v.Op {
@ -26379,6 +26380,7 @@ func rewriteValuegeneric_OpSelectN(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
config := b.Func.Config
typ := &b.Func.Config.Types
// match: (SelectN [0] (MakeResult x ___))
// result: x
for {
@ -26409,6 +26411,104 @@ func rewriteValuegeneric_OpSelectN(v *Value) bool {
v.copyOf(z)
return true
}
// match: (SelectN [0] call:(StaticLECall _ (Const64 [0]) (Const64 [0]) _))
// cond: isSameCall(call.Aux, "runtime.makeslice") && clobberIfDead(call)
// result: (Addr {ir.Syms.Zerobase} (SB))
for {
if auxIntToInt64(v.AuxInt) != 0 {
break
}
call := v_0
if call.Op != OpStaticLECall || len(call.Args) != 4 {
break
}
_ = call.Args[2]
call_1 := call.Args[1]
if call_1.Op != OpConst64 || auxIntToInt64(call_1.AuxInt) != 0 {
break
}
call_2 := call.Args[2]
if call_2.Op != OpConst64 || auxIntToInt64(call_2.AuxInt) != 0 || !(isSameCall(call.Aux, "runtime.makeslice") && clobberIfDead(call)) {
break
}
v.reset(OpAddr)
v.Aux = symToAux(ir.Syms.Zerobase)
v0 := b.NewValue0(v.Pos, OpSB, typ.Uintptr)
v.AddArg(v0)
return true
}
// match: (SelectN [0] call:(StaticLECall _ (Const32 [0]) (Const32 [0]) _))
// cond: isSameCall(call.Aux, "runtime.makeslice") && clobberIfDead(call)
// result: (Addr {ir.Syms.Zerobase} (SB))
for {
if auxIntToInt64(v.AuxInt) != 0 {
break
}
call := v_0
if call.Op != OpStaticLECall || len(call.Args) != 4 {
break
}
_ = call.Args[2]
call_1 := call.Args[1]
if call_1.Op != OpConst32 || auxIntToInt32(call_1.AuxInt) != 0 {
break
}
call_2 := call.Args[2]
if call_2.Op != OpConst32 || auxIntToInt32(call_2.AuxInt) != 0 || !(isSameCall(call.Aux, "runtime.makeslice") && clobberIfDead(call)) {
break
}
v.reset(OpAddr)
v.Aux = symToAux(ir.Syms.Zerobase)
v0 := b.NewValue0(v.Pos, OpSB, typ.Uintptr)
v.AddArg(v0)
return true
}
// match: (SelectN [1] call:(StaticLECall _ (Const64 [0]) (Const64 [0]) mem))
// cond: isSameCall(call.Aux, "runtime.makeslice") && clobberIfDead(call)
// result: mem
for {
if auxIntToInt64(v.AuxInt) != 1 {
break
}
call := v_0
if call.Op != OpStaticLECall || len(call.Args) != 4 {
break
}
mem := call.Args[3]
call_1 := call.Args[1]
if call_1.Op != OpConst64 || auxIntToInt64(call_1.AuxInt) != 0 {
break
}
call_2 := call.Args[2]
if call_2.Op != OpConst64 || auxIntToInt64(call_2.AuxInt) != 0 || !(isSameCall(call.Aux, "runtime.makeslice") && clobberIfDead(call)) {
break
}
v.copyOf(mem)
return true
}
// match: (SelectN [1] call:(StaticLECall _ (Const32 [0]) (Const32 [0]) mem))
// cond: isSameCall(call.Aux, "runtime.makeslice") && clobberIfDead(call)
// result: mem
for {
if auxIntToInt64(v.AuxInt) != 1 {
break
}
call := v_0
if call.Op != OpStaticLECall || len(call.Args) != 4 {
break
}
mem := call.Args[3]
call_1 := call.Args[1]
if call_1.Op != OpConst32 || auxIntToInt32(call_1.AuxInt) != 0 {
break
}
call_2 := call.Args[2]
if call_2.Op != OpConst32 || auxIntToInt32(call_2.AuxInt) != 0 || !(isSameCall(call.Aux, "runtime.makeslice") && clobberIfDead(call)) {
break
}
v.copyOf(mem)
return true
}
// match: (SelectN [0] call:(StaticCall {sym} s1:(Store _ (Const64 [sz]) s2:(Store _ src s3:(Store {t} _ dst mem)))))
// cond: sz >= 0 && isSameCall(sym, "runtime.memmove") && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3, call)
// result: (Move {types.Types[types.TUINT8]} [int64(sz)] dst src mem)

View File

@ -337,6 +337,12 @@ func SliceMakeCopyNoMemmoveDifferentLen(s []int) []int {
return a
}
func SliceMakeEmptyPointerToZerobase() []int {
// amd64:`LEAQ.+runtime\.zerobase`
// amd64:-`.*runtime\.makeslice`
return make([]int, 0)
}
// ---------------------- //
// Nil check of &s[0] //
// ---------------------- //