[dev.simd] all: merge master (ca37d24) into dev.simd

Conflicts:

- src/cmd/compile/internal/typecheck/builtin.go

Merge List:

+ 2025-11-20 ca37d24e0b net/http: drop unused "broken" field from persistConn
+ 2025-11-20 4b740af56a cmd/internal/obj/x86: handle global reference in From3 in dynlink mode
+ 2025-11-20 790384c6c2 spec: adjust rule for type parameter on RHS of alias declaration
+ 2025-11-20 a49b0302d0 net/http: correctly close fake net.Conns
+ 2025-11-20 32f5aadd2f cmd/compile: stack allocate backing stores during append
+ 2025-11-20 a18aff8057 runtime: select GC mark workers during start-the-world
+ 2025-11-20 829779f4fe runtime: split findRunnableGCWorker in two
+ 2025-11-20 ab59569099 go/version: use "custom" as an example of a version suffix
+ 2025-11-19 c4bb9653ba cmd/compile: Implement LoweredZeroLoop with LSX Instruction on loong64
+ 2025-11-19 7f2ae21fb4 cmd/internal/obj/loong64: add MULW.D.W[U] instructions
+ 2025-11-19 a2946f2385 crypto: add Encapsulator and Decapsulator interfaces
+ 2025-11-19 6b83bd7146 crypto/ecdh: add KeyExchanger interface
+ 2025-11-19 4fef9f8b55 go/types, types2: fix object path for grouped declaration statements
+ 2025-11-19 33529db142 spec: escape double-ampersands
+ 2025-11-19 dc42565a20 cmd/compile: fix control flow for unsigned divisions proof relations
+ 2025-11-19 e64023dcbf cmd/compile: cleanup useless if statement in prove
+ 2025-11-19 2239520d1c test: go fmt prove.go tests
+ 2025-11-19 489d3dafb7 math: switch s390x math.Pow to generic implementation
+ 2025-11-18 8c41a482f9 runtime: add dlog.hexdump
+ 2025-11-18 e912618bd2 runtime: add hexdumper
+ 2025-11-18 2cf9d4b62f Revert "net/http: do not discard body content when closing it within request handlers"
+ 2025-11-18 4d0658bb08 cmd/compile: prefer fixed registers for values
+ 2025-11-18 ba634ca5c7 cmd/compile: fold boolean NOT into branches
+ 2025-11-18 8806d53c10 cmd/link: align sections, not symbols after DWARF compress
+ 2025-11-18 c93766007d runtime: do not print recovered when double panic with the same value
+ 2025-11-18 9859b43643 cmd/asm,cmd/compile,cmd/internal/obj/riscv: use compressed instructions on riscv64
+ 2025-11-17 b9ef0633f6 cmd/internal/sys,internal/goarch,runtime: enable the use of compressed instructions on riscv64
+ 2025-11-17 a087dea869 debug/elf: sync new loong64 relocation types up to LoongArch ELF psABI v20250521
+ 2025-11-17 e1a12c781f cmd/compile: use 32x32->64 multiplies on arm64
+ 2025-11-17 6caab99026 runtime: relax TestMemoryLimit on darwin a bit more
+ 2025-11-17 eda2e8c683 runtime: clear frame pointer at thread entry points
+ 2025-11-17 6919858338 runtime: rename findrunnable references to findRunnable
+ 2025-11-17 8e734ec954 go/ast: fix BasicLit.End position for raw strings containing \r
+ 2025-11-17 592775ec7d crypto/mlkem: avoid a few unnecessary inverse NTT calls
+ 2025-11-17 590cf18daf crypto/mlkem/mlkemtest: add derandomized Encapsulate768/1024
+ 2025-11-17 c12c337099 cmd/compile: teach prove about subtract idioms
+ 2025-11-17 bc15963813 cmd/compile: clean up prove pass
+ 2025-11-17 1297fae708 go/token: add (*File).End method
+ 2025-11-17 65c09eafdf runtime: hoist invariant code out of heapBitsSmallForAddrInline
+ 2025-11-17 594129b80c internal/runtime/maps: update doc for table.Clear
+ 2025-11-15 c58d075e9a crypto/rsa: deprecate PKCS#1 v1.5 encryption
+ 2025-11-14 d55ecea9e5 runtime: usleep before stealing runnext only if not in syscall
+ 2025-11-14 410ef44f00 cmd: update x/tools to 59ff18c
+ 2025-11-14 50128a2154 runtime: support runtime.freegc in size-specialized mallocs for noscan objects
+ 2025-11-14 c3708350a4 cmd/go: tests: rename git-min-vers->git-sha256
+ 2025-11-14 aea881230d std: fix printf("%q", int) mistakes
+ 2025-11-14 120f1874ef runtime: add more precise test of assist credit handling for runtime.freegc
+ 2025-11-14 fecfcaa4f6 runtime: add runtime.freegc to reduce GC work
+ 2025-11-14 5a347b775e runtime: set GOEXPERIMENT=runtimefreegc to disabled by default
+ 2025-11-14 1a03d0db3f runtime: skip tests for GOEXPERIMENT=arenas that do not handle clobberfree=1
+ 2025-11-14 cb0d9980f5 net/http: do not discard body content when closing it within request handlers
+ 2025-11-14 03ed43988f cmd/compile: allow multi-field structs to be stored directly in interfaces
+ 2025-11-14 1bb1f2bf0c runtime: put AddCleanup cleanup arguments in their own allocation
+ 2025-11-14 9fd2e44439 runtime: add AddCleanup benchmark
+ 2025-11-14 80c91eedbb runtime: ensure weak handles end up in their own allocation
+ 2025-11-14 7a8d0b5d53 runtime: add debug mode to extend _Grunning-without-P windows
+ 2025-11-14 710abf74da internal/runtime/cgobench: add Go function call benchmark for comparison
+ 2025-11-14 b24aec598b doc, cmd/internal/obj/riscv: document the riscv64 assembler
+ 2025-11-14 a0e738c657 cmd/compile/internal: remove incorrect riscv64 SLTI rule
+ 2025-11-14 2cdcc4150b cmd/compile: fold negation into multiplication
+ 2025-11-14 b57962b7c7 bytes: fix panic in bytes.Buffer.Peek
+ 2025-11-14 0a569528ea cmd/compile: optimize comparisons with single bit difference
+ 2025-11-14 1e5e6663e9 cmd/compile: remove unnecessary casts and types from riscv64 rules
+ 2025-11-14 ddd8558e61 go/types, types2: swap object.color for Checker.objPathIdx
+ 2025-11-14 9daaab305c cmd/link/internal/ld: make runtime.buildVersion with experiments valid
+ 2025-11-13 d50a571ddf test: fix tests to work with sizespecializedmalloc turned off
+ 2025-11-13 704f841eab cmd/trace: annotation proc start/stop with thread and proc always
+ 2025-11-13 17a02b9106 net/http: remove unused isLitOrSingle and isNotToken
+ 2025-11-13 ff61991aed cmd/go: fix flaky TestScript/mod_get_direct
+ 2025-11-13 129d0cb543 net/http/cgi: accept INCLUDED as protocol for server side includes
+ 2025-11-13 77c5130100 go/types: minor simplification
+ 2025-11-13 7601cd3880 go/types: generate cycles.go
+ 2025-11-13 7a372affd9 go/types, types2: rename definedType to declaredType and clarify docs

Change-Id: Ibaa9bdb982364892f80e511c1bb12661fcd5fb86
This commit is contained in:
Cherry Mui 2025-11-20 14:40:43 -05:00
commit e3d4645693
347 changed files with 10387 additions and 2812 deletions

2
api/next/73627.txt Normal file
View file

@ -0,0 +1,2 @@
pkg crypto/mlkem/mlkemtest, func Encapsulate1024(*mlkem.EncapsulationKey1024, []uint8) ([]uint8, []uint8, error) #73627
pkg crypto/mlkem/mlkemtest, func Encapsulate768(*mlkem.EncapsulationKey768, []uint8) ([]uint8, []uint8, error) #73627

12
api/next/75300.txt Normal file
View file

@ -0,0 +1,12 @@
pkg crypto, type Decapsulator interface { Decapsulate, Encapsulator } #75300
pkg crypto, type Decapsulator interface, Decapsulate([]uint8) ([]uint8, error) #75300
pkg crypto, type Decapsulator interface, Encapsulator() Encapsulator #75300
pkg crypto, type Encapsulator interface { Bytes, Encapsulate } #75300
pkg crypto, type Encapsulator interface, Bytes() []uint8 #75300
pkg crypto, type Encapsulator interface, Encapsulate() ([]uint8, []uint8) #75300
pkg crypto/ecdh, type KeyExchanger interface { Curve, ECDH, PublicKey } #75300
pkg crypto/ecdh, type KeyExchanger interface, Curve() Curve #75300
pkg crypto/ecdh, type KeyExchanger interface, ECDH(*PublicKey) ([]uint8, error) #75300
pkg crypto/ecdh, type KeyExchanger interface, PublicKey() *PublicKey #75300
pkg crypto/mlkem, method (*DecapsulationKey1024) Encapsulator() crypto.Encapsulator #75300
pkg crypto/mlkem, method (*DecapsulationKey768) Encapsulator() crypto.Encapsulator #75300

4
api/next/75302.txt Normal file
View file

@ -0,0 +1,4 @@
pkg crypto/rsa, func DecryptPKCS1v15 //deprecated #75302
pkg crypto/rsa, func DecryptPKCS1v15SessionKey //deprecated #75302
pkg crypto/rsa, func EncryptPKCS1v15 //deprecated #75302
pkg crypto/rsa, type PKCS1v15DecryptOptions //deprecated #75302

38
api/next/75562.txt Normal file
View file

@ -0,0 +1,38 @@
pkg debug/elf, const R_LARCH_TLS_DESC32 = 13 #75562
pkg debug/elf, const R_LARCH_TLS_DESC32 R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_DESC64 = 14 #75562
pkg debug/elf, const R_LARCH_TLS_DESC64 R_LARCH #75562
pkg debug/elf, const R_LARCH_CALL36 = 110 #75562
pkg debug/elf, const R_LARCH_CALL36 R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_DESC_PC_HI20 = 111 #75562
pkg debug/elf, const R_LARCH_TLS_DESC_PC_HI20 R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_DESC_PC_LO12 = 112 #75562
pkg debug/elf, const R_LARCH_TLS_DESC_PC_LO12 R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_DESC64_PC_LO20 = 113 #75562
pkg debug/elf, const R_LARCH_TLS_DESC64_PC_LO20 R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_DESC64_PC_HI12 = 114 #75562
pkg debug/elf, const R_LARCH_TLS_DESC64_PC_HI12 R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_DESC_HI20 = 115 #75562
pkg debug/elf, const R_LARCH_TLS_DESC_HI20 R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_DESC_LO12 = 116 #75562
pkg debug/elf, const R_LARCH_TLS_DESC_LO12 R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_DESC64_LO20 = 117 #75562
pkg debug/elf, const R_LARCH_TLS_DESC64_LO20 R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_DESC64_HI12 = 118 #75562
pkg debug/elf, const R_LARCH_TLS_DESC64_HI12 R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_DESC_LD = 119 #75562
pkg debug/elf, const R_LARCH_TLS_DESC_LD R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_DESC_CALL = 120 #75562
pkg debug/elf, const R_LARCH_TLS_DESC_CALL R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_LE_HI20_R = 121 #75562
pkg debug/elf, const R_LARCH_TLS_LE_HI20_R R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_LE_ADD_R = 122 #75562
pkg debug/elf, const R_LARCH_TLS_LE_ADD_R R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_LE_LO12_R = 123 #75562
pkg debug/elf, const R_LARCH_TLS_LE_LO12_R R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_LD_PCREL20_S2 = 124 #75562
pkg debug/elf, const R_LARCH_TLS_LD_PCREL20_S2 R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_GD_PCREL20_S2 = 125 #75562
pkg debug/elf, const R_LARCH_TLS_GD_PCREL20_S2 R_LARCH #75562
pkg debug/elf, const R_LARCH_TLS_DESC_PCREL20_S2 = 126 #75562
pkg debug/elf, const R_LARCH_TLS_DESC_PCREL20_S2 R_LARCH #75562

1
api/next/75849.txt Normal file
View file

@ -0,0 +1 @@
pkg go/token, method (*File) End() Pos #75849

1
api/next/76031.txt Normal file
View file

@ -0,0 +1 @@
pkg go/ast, type BasicLit struct, ValueEnd token.Pos #76031

View file

@ -1039,6 +1039,12 @@ The value of <code>GOMIPS64</code> environment variable (<code>hardfloat</code>
<code>GOMIPS64_hardfloat</code> or <code>GOMIPS64_softfloat</code>. <code>GOMIPS64_hardfloat</code> or <code>GOMIPS64_softfloat</code>.
</p> </p>
<h3 id="riscv64">RISCV64</h3>
<p>
Reference: <a href="/pkg/cmd/internal/obj/riscv">Go RISCV64 Assembly Instructions Reference Manual</a>
</p>
<h3 id="unsupported_opcodes">Unsupported opcodes</h3> <h3 id="unsupported_opcodes">Unsupported opcodes</h3>
<p> <p>

View file

@ -1,6 +1,6 @@
<!--{ <!--{
"Title": "The Go Programming Language Specification", "Title": "The Go Programming Language Specification",
"Subtitle": "Language version go1.26 (Nov 12, 2025)", "Subtitle": "Language version go1.26 (Nov 18, 2025)",
"Path": "/ref/spec" "Path": "/ref/spec"
}--> }-->
@ -2487,11 +2487,15 @@ type set[P comparable] = map[P]bool
</pre> </pre>
<p> <p>
In an alias declaration the given type cannot be a type parameter. In an alias declaration the given type cannot be a type parameter declared in the same declaration.
</p> </p>
<pre> <pre>
type A[P any] = P // illegal: P is a type parameter type A[P any] = P // illegal: P is a type parameter declared in the declaration of A
func f[P any]() {
type A = P // ok: T is a type parameter declared by the enclosing function
}
</pre> </pre>
<h4 id="Type_definitions">Type definitions</h4> <h4 id="Type_definitions">Type definitions</h4>
@ -2601,8 +2605,8 @@ In a type definition the given type cannot be a type parameter.
<pre> <pre>
type T[P any] P // illegal: P is a type parameter type T[P any] P // illegal: P is a type parameter
func f[T any]() { func f[P any]() {
type L T // illegal: T is a type parameter declared by the enclosing function type L P // illegal: P is a type parameter declared by the enclosing function
} }
</pre> </pre>
@ -4857,7 +4861,7 @@ For instance, <code>x / y * z</code> is the same as <code>(x / y) * z</code>.
x &lt;= f() // x &lt;= f() x &lt;= f() // x &lt;= f()
^a &gt;&gt; b // (^a) >> b ^a &gt;&gt; b // (^a) >> b
f() || g() // f() || g() f() || g() // f() || g()
x == y+1 &amp;&amp; &lt;-chanInt &gt; 0 // (x == (y+1)) && ((&lt;-chanInt) > 0) x == y+1 &amp;&amp; &lt;-chanInt &gt; 0 // (x == (y+1)) &amp;&amp; ((&lt;-chanInt) > 0)
</pre> </pre>
@ -6867,7 +6871,7 @@ type Tree[K cmp.Ordered, V any] struct {
} }
func (t *Tree[K, V]) walk(yield func(key K, val V) bool) bool { func (t *Tree[K, V]) walk(yield func(key K, val V) bool) bool {
return t == nil || t.left.walk(yield) && yield(t.key, t.value) && t.right.walk(yield) return t == nil || t.left.walk(yield) &amp;&amp; yield(t.key, t.value) &amp;&amp; t.right.walk(yield)
} }
func (t *Tree[K, V]) Walk(yield func(key K, val V) bool) { func (t *Tree[K, V]) Walk(yield func(key K, val V) bool) {

View file

@ -0,0 +1,2 @@
The new [Encapsulator] and [Decapsulator] interfaces allow accepting abstract
KEM encapsulation or decapsulation keys.

View file

@ -0,0 +1,2 @@
The new [KeyExchanger] interface, implemented by [PrivateKey], makes it possible
to accept abstract ECDH private keys, e.g. those implemented in hardware.

View file

@ -0,0 +1,3 @@
The new [DecapsulationKey768.Encapsulator] and
[DecapsulationKey1024.Encapsulator] methods implement the new
[crypto.Decapsulator] interface.

View file

@ -0,0 +1,3 @@
The new [crypto/mlkem/mlkemtest] package exposes the [Encapsulate768] and
[Encapsulate1024] functions which implement derandomized ML-KEM encapsulation,
for use with known-answer tests.

View file

@ -0,0 +1,2 @@
Unsafe PKCS #1 v1.5 encryption padding (implemented by [EncryptPKCS1v15],
[DecryptPKCS1v15], and [DecryptPKCS1v15SessionKey]) is now deprecated.

View file

@ -0,0 +1,4 @@
Additional `R_LARCH_*` constants from [LoongArch ELF psABI v20250521][laelf-20250521]
(global version v2.40) are defined for use with LoongArch systems.
[laelf-20250521]: https://github.com/loongson/la-abi-specs/blob/v2.40/laelf.adoc

View file

@ -0,0 +1,5 @@
The new [BasicLit.ValueEnd] field records the precise end position of
a literal so that the [BasicLit.End] method can now always return the
correct answer. (Previously it was computed using a heuristic that was
incorrect for multi-line raw string literals in Windows source files,
due to removal of carriage returns.)

View file

@ -0,0 +1 @@
The new [File.End] convenience method returns the file's end position.

View file

@ -86,7 +86,7 @@ func (b *Buffer) Peek(n int) ([]byte, error) {
if b.Len() < n { if b.Len() < n {
return b.buf[b.off:], io.EOF return b.buf[b.off:], io.EOF
} }
return b.buf[b.off:n], nil return b.buf[b.off : b.off+n], nil
} }
// empty reports whether the unread portion of the buffer is empty. // empty reports whether the unread portion of the buffer is empty.

View file

@ -533,19 +533,25 @@ func TestReadString(t *testing.T) {
var peekTests = []struct { var peekTests = []struct {
buffer string buffer string
skip int
n int n int
expected string expected string
err error err error
}{ }{
{"", 0, "", nil}, {"", 0, 0, "", nil},
{"aaa", 3, "aaa", nil}, {"aaa", 0, 3, "aaa", nil},
{"foobar", 2, "fo", nil}, {"foobar", 0, 2, "fo", nil},
{"a", 2, "a", io.EOF}, {"a", 0, 2, "a", io.EOF},
{"helloworld", 4, 3, "owo", nil},
{"helloworld", 5, 5, "world", nil},
{"helloworld", 5, 6, "world", io.EOF},
{"helloworld", 10, 1, "", io.EOF},
} }
func TestPeek(t *testing.T) { func TestPeek(t *testing.T) {
for _, test := range peekTests { for _, test := range peekTests {
buf := NewBufferString(test.buffer) buf := NewBufferString(test.buffer)
buf.Next(test.skip)
bytes, err := buf.Peek(test.n) bytes, err := buf.Peek(test.n)
if string(bytes) != test.expected { if string(bytes) != test.expected {
t.Errorf("expected %q, got %q", test.expected, bytes) t.Errorf("expected %q, got %q", test.expected, bytes)
@ -553,8 +559,8 @@ func TestPeek(t *testing.T) {
if err != test.err { if err != test.err {
t.Errorf("expected error %v, got %v", test.err, err) t.Errorf("expected error %v, got %v", test.err, err)
} }
if buf.Len() != len(test.buffer) { if buf.Len() != len(test.buffer)-test.skip {
t.Errorf("bad length after peek: %d, want %d", buf.Len(), len(test.buffer)) t.Errorf("bad length after peek: %d, want %d", buf.Len(), len(test.buffer)-test.skip)
} }
} }
} }

View file

@ -169,3 +169,8 @@ TEXT ·a34(SB), 0, $0-0
SHLXQ AX, CX, R15 SHLXQ AX, CX, R15
ADDQ $1, R15 ADDQ $1, R15
RET RET
// Ensure from3 get GOT-rewritten without errors.
TEXT ·a35(SB), 0, $0-0
VGF2P8AFFINEQB $0, runtime·writeBarrier(SB), Z1, Z1
RET

View file

@ -212,6 +212,12 @@ lable2:
SRLV $32, R4, R5 // 85804500 SRLV $32, R4, R5 // 85804500
SRLV $32, R4 // 84804500 SRLV $32, R4 // 84804500
// MULW.D.W[U] instructions
MULWVW R4, R5 // a5101f00
MULWVW R4, R5, R6 // a6101f00
MULWVWU R4, R5 // a5901f00
MULWVWU R4, R5, R6 // a6901f00
MASKEQZ R4, R5, R6 // a6101300 MASKEQZ R4, R5, R6 // a6101300
MASKNEZ R4, R5, R6 // a6901300 MASKNEZ R4, R5, R6 // a6901300

View file

@ -29,6 +29,7 @@ var (
) )
var DebugFlags struct { var DebugFlags struct {
CompressInstructions int `help:"use compressed instructions when possible (if supported by architecture)"`
MayMoreStack string `help:"call named function before all stack growth checks"` MayMoreStack string `help:"call named function before all stack growth checks"`
PCTab string `help:"print named pc-value table\nOne of: pctospadj, pctofile, pctoline, pctoinline, pctopcdata"` PCTab string `help:"print named pc-value table\nOne of: pctospadj, pctofile, pctoline, pctoinline, pctopcdata"`
} }
@ -47,6 +48,8 @@ func init() {
flag.Var(objabi.NewDebugFlag(&DebugFlags, nil), "d", "enable debugging settings; try -d help") flag.Var(objabi.NewDebugFlag(&DebugFlags, nil), "d", "enable debugging settings; try -d help")
objabi.AddVersionFlag() // -V objabi.AddVersionFlag() // -V
objabi.Flagcount("S", "print assembly and machine code", &PrintOut) objabi.Flagcount("S", "print assembly and machine code", &PrintOut)
DebugFlags.CompressInstructions = 1
} }
// MultiFlag allows setting a value multiple times to collect a list, as in -I=dir1 -I=dir2. // MultiFlag allows setting a value multiple times to collect a list, as in -I=dir1 -I=dir2.

View file

@ -40,6 +40,7 @@ func main() {
log.Fatalf("unrecognized architecture %s", GOARCH) log.Fatalf("unrecognized architecture %s", GOARCH)
} }
ctxt := obj.Linknew(architecture.LinkArch) ctxt := obj.Linknew(architecture.LinkArch)
ctxt.CompressInstructions = flags.DebugFlags.CompressInstructions != 0
ctxt.Debugasm = flags.PrintOut ctxt.Debugasm = flags.PrintOut
ctxt.Debugvlog = flags.DebugV ctxt.Debugvlog = flags.DebugV
ctxt.Flag_dynlink = *flags.Dynlink ctxt.Flag_dynlink = *flags.Dynlink

View file

@ -20,6 +20,7 @@ type DebugFlags struct {
Append int `help:"print information about append compilation"` Append int `help:"print information about append compilation"`
Checkptr int `help:"instrument unsafe pointer conversions\n0: instrumentation disabled\n1: conversions involving unsafe.Pointer are instrumented\n2: conversions to unsafe.Pointer force heap allocation" concurrent:"ok"` Checkptr int `help:"instrument unsafe pointer conversions\n0: instrumentation disabled\n1: conversions involving unsafe.Pointer are instrumented\n2: conversions to unsafe.Pointer force heap allocation" concurrent:"ok"`
Closure int `help:"print information about closure compilation"` Closure int `help:"print information about closure compilation"`
CompressInstructions int `help:"use compressed instructions when possible (if supported by architecture)"`
Converthash string `help:"hash value for use in debugging changes to platform-dependent float-to-[u]int conversion" concurrent:"ok"` Converthash string `help:"hash value for use in debugging changes to platform-dependent float-to-[u]int conversion" concurrent:"ok"`
Defer int `help:"print information about defer compilation"` Defer int `help:"print information about defer compilation"`
DisableNil int `help:"disable nil checks" concurrent:"ok"` DisableNil int `help:"disable nil checks" concurrent:"ok"`

View file

@ -177,6 +177,7 @@ func ParseFlags() {
Flag.WB = true Flag.WB = true
Debug.ConcurrentOk = true Debug.ConcurrentOk = true
Debug.CompressInstructions = 1
Debug.MaxShapeLen = 500 Debug.MaxShapeLen = 500
Debug.AlignHot = 1 Debug.AlignHot = 1
Debug.InlFuncsWithClosures = 1 Debug.InlFuncsWithClosures = 1
@ -299,6 +300,7 @@ func ParseFlags() {
} }
parseSpectre(Flag.Spectre) // left as string for RecordFlags parseSpectre(Flag.Spectre) // left as string for RecordFlags
Ctxt.CompressInstructions = Debug.CompressInstructions != 0
Ctxt.Flag_shared = Ctxt.Flag_dynlink || Ctxt.Flag_shared Ctxt.Flag_shared = Ctxt.Flag_dynlink || Ctxt.Flag_shared
Ctxt.Flag_optimize = Flag.N == 0 Ctxt.Flag_optimize = Flag.N == 0
Ctxt.Debugasm = int(Flag.S) Ctxt.Debugasm = int(Flag.S)

View file

@ -44,6 +44,11 @@ func Funcs(fns []*ir.Func) {
*as.lhs = ir.BlankNode *as.lhs = ir.BlankNode
*as.rhs = zero *as.rhs = zero
} }
if len(assigns) > 0 {
// k.Defn might be pointing at one of the
// assignments we're overwriting.
k.Defn = nil
}
} }
} }
} }

View file

@ -124,3 +124,21 @@ func parseLeaks(s string) leaks {
copy(l[:], s[4:]) copy(l[:], s[4:])
return l return l
} }
func ParseLeaks(s string) leaks {
return parseLeaks(s)
}
// Any reports whether the value flows anywhere at all.
func (l leaks) Any() bool {
// TODO: do mutator/callee matter?
if l.Heap() >= 0 || l.Mutator() >= 0 || l.Callee() >= 0 {
return true
}
for i := range numEscResults {
if l.Result(i) >= 0 {
return true
}
}
return false
}

View file

@ -22,6 +22,7 @@ import (
"cmd/compile/internal/pkginit" "cmd/compile/internal/pkginit"
"cmd/compile/internal/reflectdata" "cmd/compile/internal/reflectdata"
"cmd/compile/internal/rttype" "cmd/compile/internal/rttype"
"cmd/compile/internal/slice"
"cmd/compile/internal/ssa" "cmd/compile/internal/ssa"
"cmd/compile/internal/ssagen" "cmd/compile/internal/ssagen"
"cmd/compile/internal/staticinit" "cmd/compile/internal/staticinit"
@ -271,6 +272,8 @@ func Main(archInit func(*ssagen.ArchInfo)) {
base.Timer.Start("fe", "escapes") base.Timer.Start("fe", "escapes")
escape.Funcs(typecheck.Target.Funcs) escape.Funcs(typecheck.Target.Funcs)
slice.Funcs(typecheck.Target.Funcs)
loopvar.LogTransformations(transformed) loopvar.LogTransformations(transformed)
// Collect information for go:nowritebarrierrec // Collect information for go:nowritebarrierrec

View file

@ -192,6 +192,7 @@ type CallExpr struct {
IsDDD bool IsDDD bool
GoDefer bool // whether this call is part of a go or defer statement GoDefer bool // whether this call is part of a go or defer statement
NoInline bool // whether this call must not be inlined NoInline bool // whether this call must not be inlined
UseBuf bool // use stack buffer for backing store (OAPPEND only)
} }
func NewCallExpr(pos src.XPos, op Op, fun Node, args []Node) *CallExpr { func NewCallExpr(pos src.XPos, op Op, fun Node, args []Node) *CallExpr {
@ -1280,3 +1281,28 @@ func MethodExprFunc(n Node) *types.Field {
base.Fatalf("unexpected node: %v (%v)", n, n.Op()) base.Fatalf("unexpected node: %v (%v)", n, n.Op())
panic("unreachable") panic("unreachable")
} }
// A MoveToHeapExpr takes a slice as input and moves it to the
// heap (by copying the backing store if it is not already
// on the heap).
type MoveToHeapExpr struct {
miniExpr
Slice Node
// An expression that evaluates to a *runtime._type
// that represents the slice element type.
RType Node
// If PreserveCapacity is true, the capacity of
// the resulting slice, and all of the elements in
// [len:cap], must be preserved.
// If PreserveCapacity is false, the resulting
// slice may have any capacity >= len, with any
// elements in the resulting [len:cap] range zeroed.
PreserveCapacity bool
}
func NewMoveToHeapExpr(pos src.XPos, slice Node) *MoveToHeapExpr {
n := &MoveToHeapExpr{Slice: slice}
n.pos = pos
n.op = OMOVE2HEAP
return n
}

View file

@ -574,7 +574,7 @@ func exprFmt(n Node, s fmt.State, prec int) {
// Special case for rune constants. // Special case for rune constants.
if typ == types.RuneType || typ == types.UntypedRune { if typ == types.RuneType || typ == types.UntypedRune {
if x, ok := constant.Uint64Val(val); ok && x <= utf8.MaxRune { if x, ok := constant.Uint64Val(val); ok && x <= utf8.MaxRune {
fmt.Fprintf(s, "%q", x) fmt.Fprintf(s, "%q", rune(x))
return return
} }
} }

View file

@ -43,7 +43,7 @@ type Name struct {
Func *Func // TODO(austin): nil for I.M Func *Func // TODO(austin): nil for I.M
Offset_ int64 Offset_ int64
val constant.Value val constant.Value
Opt any // for use by escape analysis Opt any // for use by escape or slice analysis
Embed *[]Embed // list of embedded files, for ONAME var Embed *[]Embed // list of embedded files, for ONAME var
// For a local variable (not param) or extern, the initializing assignment (OAS or OAS2). // For a local variable (not param) or extern, the initializing assignment (OAS or OAS2).

View file

@ -293,6 +293,7 @@ const (
OLINKSYMOFFSET // offset within a name OLINKSYMOFFSET // offset within a name
OJUMPTABLE // A jump table structure for implementing dense expression switches OJUMPTABLE // A jump table structure for implementing dense expression switches
OINTERFACESWITCH // A type switch with interface cases OINTERFACESWITCH // A type switch with interface cases
OMOVE2HEAP // Promote a stack-backed slice to heap
// opcodes for generics // opcodes for generics
ODYNAMICDOTTYPE // x = i.(T) where T is a type parameter (or derived from a type parameter) ODYNAMICDOTTYPE // x = i.(T) where T is a type parameter (or derived from a type parameter)

View file

@ -1175,6 +1175,34 @@ func (n *MakeExpr) editChildrenWithHidden(edit func(Node) Node) {
} }
} }
func (n *MoveToHeapExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) }
func (n *MoveToHeapExpr) copy() Node {
c := *n
c.init = copyNodes(c.init)
return &c
}
func (n *MoveToHeapExpr) doChildren(do func(Node) bool) bool {
if doNodes(n.init, do) {
return true
}
if n.Slice != nil && do(n.Slice) {
return true
}
return false
}
func (n *MoveToHeapExpr) doChildrenWithHidden(do func(Node) bool) bool {
return n.doChildren(do)
}
func (n *MoveToHeapExpr) editChildren(edit func(Node) Node) {
editNodes(n.init, edit)
if n.Slice != nil {
n.Slice = edit(n.Slice).(Node)
}
}
func (n *MoveToHeapExpr) editChildrenWithHidden(edit func(Node) Node) {
n.editChildren(edit)
}
func (n *Name) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } func (n *Name) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) }
func (n *NilExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } func (n *NilExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) }

View file

@ -151,18 +151,19 @@ func _() {
_ = x[OLINKSYMOFFSET-140] _ = x[OLINKSYMOFFSET-140]
_ = x[OJUMPTABLE-141] _ = x[OJUMPTABLE-141]
_ = x[OINTERFACESWITCH-142] _ = x[OINTERFACESWITCH-142]
_ = x[ODYNAMICDOTTYPE-143] _ = x[OMOVE2HEAP-143]
_ = x[ODYNAMICDOTTYPE2-144] _ = x[ODYNAMICDOTTYPE-144]
_ = x[ODYNAMICTYPE-145] _ = x[ODYNAMICDOTTYPE2-145]
_ = x[OTAILCALL-146] _ = x[ODYNAMICTYPE-146]
_ = x[OGETG-147] _ = x[OTAILCALL-147]
_ = x[OGETCALLERSP-148] _ = x[OGETG-148]
_ = x[OEND-149] _ = x[OGETCALLERSP-149]
_ = x[OEND-150]
} }
const _Op_name = "XXXNAMENONAMETYPELITERALNILADDSUBORXORADDSTRADDRANDANDAPPENDBYTES2STRBYTES2STRTMPRUNES2STRSTR2BYTESSTR2BYTESTMPSTR2RUNESSLICE2ARRSLICE2ARRPTRASAS2AS2DOTTYPEAS2FUNCAS2MAPRAS2RECVASOPCALLCALLFUNCCALLMETHCALLINTERCAPCLEARCLOSECLOSURECOMPLITMAPLITSTRUCTLITARRAYLITSLICELITPTRLITCONVCONVIFACECONVNOPCOPYDCLDCLFUNCDELETEDOTDOTPTRDOTMETHDOTINTERXDOTDOTTYPEDOTTYPE2EQNELTLEGEGTDEREFINDEXINDEXMAPKEYSTRUCTKEYLENMAKEMAKECHANMAKEMAPMAKESLICEMAKESLICECOPYMULDIVMODLSHRSHANDANDNOTNEWNOTBITNOTPLUSNEGORORPANICPRINTPRINTLNPARENSENDSLICESLICEARRSLICESTRSLICE3SLICE3ARRSLICEHEADERSTRINGHEADERRECOVERRECVRUNESTRSELRECV2MINMAXREALIMAGCOMPLEXUNSAFEADDUNSAFESLICEUNSAFESLICEDATAUNSAFESTRINGUNSAFESTRINGDATAMETHEXPRMETHVALUEBLOCKBREAKCASECONTINUEDEFERFALLFORGOTOIFLABELGORANGERETURNSELECTSWITCHTYPESWINLCALLMAKEFACEITABIDATASPTRCFUNCCHECKNILRESULTINLMARKLINKSYMOFFSETJUMPTABLEINTERFACESWITCHDYNAMICDOTTYPEDYNAMICDOTTYPE2DYNAMICTYPETAILCALLGETGGETCALLERSPEND" const _Op_name = "XXXNAMENONAMETYPELITERALNILADDSUBORXORADDSTRADDRANDANDAPPENDBYTES2STRBYTES2STRTMPRUNES2STRSTR2BYTESSTR2BYTESTMPSTR2RUNESSLICE2ARRSLICE2ARRPTRASAS2AS2DOTTYPEAS2FUNCAS2MAPRAS2RECVASOPCALLCALLFUNCCALLMETHCALLINTERCAPCLEARCLOSECLOSURECOMPLITMAPLITSTRUCTLITARRAYLITSLICELITPTRLITCONVCONVIFACECONVNOPCOPYDCLDCLFUNCDELETEDOTDOTPTRDOTMETHDOTINTERXDOTDOTTYPEDOTTYPE2EQNELTLEGEGTDEREFINDEXINDEXMAPKEYSTRUCTKEYLENMAKEMAKECHANMAKEMAPMAKESLICEMAKESLICECOPYMULDIVMODLSHRSHANDANDNOTNEWNOTBITNOTPLUSNEGORORPANICPRINTPRINTLNPARENSENDSLICESLICEARRSLICESTRSLICE3SLICE3ARRSLICEHEADERSTRINGHEADERRECOVERRECVRUNESTRSELRECV2MINMAXREALIMAGCOMPLEXUNSAFEADDUNSAFESLICEUNSAFESLICEDATAUNSAFESTRINGUNSAFESTRINGDATAMETHEXPRMETHVALUEBLOCKBREAKCASECONTINUEDEFERFALLFORGOTOIFLABELGORANGERETURNSELECTSWITCHTYPESWINLCALLMAKEFACEITABIDATASPTRCFUNCCHECKNILRESULTINLMARKLINKSYMOFFSETJUMPTABLEINTERFACESWITCHMOVE2HEAPDYNAMICDOTTYPEDYNAMICDOTTYPE2DYNAMICTYPETAILCALLGETGGETCALLERSPEND"
var _Op_index = [...]uint16{0, 3, 7, 13, 17, 24, 27, 30, 33, 35, 38, 44, 48, 54, 60, 69, 81, 90, 99, 111, 120, 129, 141, 143, 146, 156, 163, 170, 177, 181, 185, 193, 201, 210, 213, 218, 223, 230, 237, 243, 252, 260, 268, 274, 278, 287, 294, 298, 301, 308, 314, 317, 323, 330, 338, 342, 349, 357, 359, 361, 363, 365, 367, 369, 374, 379, 387, 390, 399, 402, 406, 414, 421, 430, 443, 446, 449, 452, 455, 458, 461, 467, 470, 473, 479, 483, 486, 490, 495, 500, 507, 512, 516, 521, 529, 537, 543, 552, 563, 575, 582, 586, 593, 601, 604, 607, 611, 615, 622, 631, 642, 657, 669, 685, 693, 702, 707, 712, 716, 724, 729, 733, 736, 740, 742, 747, 749, 754, 760, 766, 772, 778, 785, 793, 797, 802, 806, 811, 819, 825, 832, 845, 854, 869, 883, 898, 909, 917, 921, 932, 935} var _Op_index = [...]uint16{0, 3, 7, 13, 17, 24, 27, 30, 33, 35, 38, 44, 48, 54, 60, 69, 81, 90, 99, 111, 120, 129, 141, 143, 146, 156, 163, 170, 177, 181, 185, 193, 201, 210, 213, 218, 223, 230, 237, 243, 252, 260, 268, 274, 278, 287, 294, 298, 301, 308, 314, 317, 323, 330, 338, 342, 349, 357, 359, 361, 363, 365, 367, 369, 374, 379, 387, 390, 399, 402, 406, 414, 421, 430, 443, 446, 449, 452, 455, 458, 461, 467, 470, 473, 479, 483, 486, 490, 495, 500, 507, 512, 516, 521, 529, 537, 543, 552, 563, 575, 582, 586, 593, 601, 604, 607, 611, 615, 622, 631, 642, 657, 669, 685, 693, 702, 707, 712, 716, 724, 729, 733, 736, 740, 742, 747, 749, 754, 760, 766, 772, 778, 785, 793, 797, 802, 806, 811, 819, 825, 832, 845, 854, 869, 878, 892, 907, 918, 926, 930, 941, 944}
func (i Op) String() string { func (i Op) String() string {
if i >= Op(len(_Op_index)-1) { if i >= Op(len(_Op_index)-1) {

View file

@ -42,6 +42,7 @@ func (*Decl) isStmt() {}
type Stmt interface { type Stmt interface {
Node Node
isStmt() isStmt()
PtrInit() *Nodes
} }
// A miniStmt is a miniNode with extra fields common to statements. // A miniStmt is a miniNode with extra fields common to statements.

View file

@ -29,6 +29,11 @@ type symsStruct struct {
GCWriteBarrier [8]*obj.LSym GCWriteBarrier [8]*obj.LSym
Goschedguarded *obj.LSym Goschedguarded *obj.LSym
Growslice *obj.LSym Growslice *obj.LSym
GrowsliceBuf *obj.LSym
MoveSlice *obj.LSym
MoveSliceNoScan *obj.LSym
MoveSliceNoCap *obj.LSym
MoveSliceNoCapNoScan *obj.LSym
InterfaceSwitch *obj.LSym InterfaceSwitch *obj.LSym
MallocGC *obj.LSym MallocGC *obj.LSym
MallocGCSmallNoScan [27]*obj.LSym MallocGCSmallNoScan [27]*obj.LSym

View file

@ -575,6 +575,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
case ssa.OpLOONG64LoweredZeroLoop: case ssa.OpLOONG64LoweredZeroLoop:
ptrReg := v.Args[0].Reg() ptrReg := v.Args[0].Reg()
countReg := v.RegTmp() countReg := v.RegTmp()
flagReg := int16(loong64.REGTMP)
var off int64 var off int64
n := v.AuxInt n := v.AuxInt
loopSize := int64(64) loopSize := int64(64)
@ -587,58 +588,119 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
// vs // vs
// 16 instuctions in the straightline code // 16 instuctions in the straightline code
// Might as well use straightline code. // Might as well use straightline code.
v.Fatalf("ZeroLoop size tool small %d", n) v.Fatalf("ZeroLoop size too small %d", n)
} }
// Put iteration count in a register.
// MOVV $n/loopSize, countReg // MOVV $n/loopSize, countReg
p := s.Prog(loong64.AMOVV) // MOVBU ir.Syms.Loong64HasLSX, flagReg
p.From.Type = obj.TYPE_CONST // BNE flagReg, lsxInit
p.From.Offset = n / loopSize // genericInit:
p.To.Type = obj.TYPE_REG // for off = 0; off < loopSize; off += 8 {
p.To.Reg = countReg // zero8(s, ptrReg, off)
cntInit := p // }
// ADDV $loopSize, ptrReg
// SUBV $1, countReg
// BNE countReg, genericInit
// JMP tail
// lsxInit:
// VXORV V31, V31, V31, v31 = 0
// for off = 0; off < loopSize; off += 16 {
// zero16(s, V31, ptrReg, off)
// }
// ADDV $loopSize, ptrReg
// SUBV $1, countReg
// BNE countReg, lsxInit
// tail:
// n %= loopSize
// for off = 0; n >= 8; off += 8, n -= 8 {
// zero8(s, ptrReg, off)
// }
//
// if n != 0 {
// zero8(s, ptrReg, off+n-8)
// }
// Zero loopSize bytes starting at ptrReg. p1 := s.Prog(loong64.AMOVV)
for range loopSize / 8 { p1.From.Type = obj.TYPE_CONST
// MOVV ZR, off(ptrReg) p1.From.Offset = n / loopSize
p1.To.Type = obj.TYPE_REG
p1.To.Reg = countReg
p2 := s.Prog(loong64.AMOVBU)
p2.From.Type = obj.TYPE_MEM
p2.From.Name = obj.NAME_EXTERN
p2.From.Sym = ir.Syms.Loong64HasLSX
p2.To.Type = obj.TYPE_REG
p2.To.Reg = flagReg
p3 := s.Prog(loong64.ABNE)
p3.From.Type = obj.TYPE_REG
p3.From.Reg = flagReg
p3.To.Type = obj.TYPE_BRANCH
for off = 0; off < loopSize; off += 8 {
zero8(s, ptrReg, off) zero8(s, ptrReg, off)
off += 8
} }
// Increment ptrReg by loopSize. p4 := s.Prog(loong64.AADDV)
// ADDV $loopSize, ptrReg p4.From.Type = obj.TYPE_CONST
p = s.Prog(loong64.AADDV) p4.From.Offset = loopSize
p.From.Type = obj.TYPE_CONST p4.To.Type = obj.TYPE_REG
p.From.Offset = loopSize p4.To.Reg = ptrReg
p.To.Type = obj.TYPE_REG
p.To.Reg = ptrReg
// Decrement loop count. p5 := s.Prog(loong64.ASUBV)
// SUBV $1, countReg p5.From.Type = obj.TYPE_CONST
p = s.Prog(loong64.ASUBV) p5.From.Offset = 1
p.From.Type = obj.TYPE_CONST p5.To.Type = obj.TYPE_REG
p.From.Offset = 1 p5.To.Reg = countReg
p.To.Type = obj.TYPE_REG
p.To.Reg = countReg
// Jump to loop header if we're not done yet. p6 := s.Prog(loong64.ABNE)
// BNE countReg, loop header p6.From.Type = obj.TYPE_REG
p = s.Prog(loong64.ABNE) p6.From.Reg = countReg
p.From.Type = obj.TYPE_REG p6.To.Type = obj.TYPE_BRANCH
p.From.Reg = countReg p6.To.SetTarget(p3.Link)
p.To.Type = obj.TYPE_BRANCH
p.To.SetTarget(cntInit.Link) p7 := s.Prog(obj.AJMP)
p7.To.Type = obj.TYPE_BRANCH
p8 := s.Prog(loong64.AVXORV)
p8.From.Type = obj.TYPE_REG
p8.From.Reg = loong64.REG_V31
p8.To.Type = obj.TYPE_REG
p8.To.Reg = loong64.REG_V31
p3.To.SetTarget(p8)
for off = 0; off < loopSize; off += 16 {
zero16(s, loong64.REG_V31, ptrReg, off)
}
p9 := s.Prog(loong64.AADDV)
p9.From.Type = obj.TYPE_CONST
p9.From.Offset = loopSize
p9.To.Type = obj.TYPE_REG
p9.To.Reg = ptrReg
p10 := s.Prog(loong64.ASUBV)
p10.From.Type = obj.TYPE_CONST
p10.From.Offset = 1
p10.To.Type = obj.TYPE_REG
p10.To.Reg = countReg
p11 := s.Prog(loong64.ABNE)
p11.From.Type = obj.TYPE_REG
p11.From.Reg = countReg
p11.To.Type = obj.TYPE_BRANCH
p11.To.SetTarget(p8.Link)
p12 := s.Prog(obj.ANOP)
p7.To.SetTarget(p12)
// Multiples of the loop size are now done. // Multiples of the loop size are now done.
n %= loopSize n %= loopSize
off = 0
// Write any fractional portion. // Write any fractional portion.
for n >= 8 { for off = 0; n >= 8; off += 8 {
// MOVV ZR, off(ptrReg) // MOVV ZR, off(ptrReg)
zero8(s, ptrReg, off) zero8(s, ptrReg, off)
off += 8
n -= 8 n -= 8
} }
@ -1341,3 +1403,14 @@ func zero8(s *ssagen.State, reg int16, off int64) {
p.To.Reg = reg p.To.Reg = reg
p.To.Offset = off p.To.Offset = off
} }
// zero16 zeroes 16 bytes at reg+off.
func zero16(s *ssagen.State, regZero, regBase int16, off int64) {
// VMOVQ regZero, off(regBase)
p := s.Prog(loong64.AVMOVQ)
p.From.Type = obj.TYPE_REG
p.From.Reg = regZero
p.To.Type = obj.TYPE_MEM
p.To.Reg = regBase
p.To.Offset = off
}

View file

@ -0,0 +1,455 @@
// Copyright 2025 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 slice
// This file implements a stack-allocation optimization
// for the backing store of slices.
//
// Consider the code:
//
// var s []int
// for i := range ... {
// s = append(s, i)
// }
// return s
//
// Some of the append operations will need to do an allocation
// by calling growslice. This will happen on the 1st, 2nd, 4th,
// 8th, etc. append calls. The allocations done by all but the
// last growslice call will then immediately be garbage.
//
// We'd like to avoid doing some of those intermediate
// allocations if possible.
//
// If we can determine that the "return s" statement is the
// *only* way that the backing store for s escapes, then we
// can rewrite the code to something like:
//
// var s []int
// for i := range N {
// s = append(s, i)
// }
// s = move2heap(s)
// return s
//
// Using the move2heap runtime function, which does:
//
// move2heap(s):
// If s is not backed by a stackframe-allocated
// backing store, return s. Otherwise, copy s
// to the heap and return the copy.
//
// Now we can treat the backing store of s allocated at the
// append site as not escaping. Previous stack allocation
// optimizations now apply, which can use a fixed-size
// stack-allocated backing store for s when appending.
// (See ../ssagen/ssa.go:(*state).append)
//
// It is tricky to do this optimization safely. To describe
// our analysis, we first define what an "exclusive" slice
// variable is.
//
// A slice variable (a variable of slice type) is called
// "exclusive" if, when it has a reference to a
// stackframe-allocated backing store, it is the only
// variable with such a reference.
//
// In other words, a slice variable is exclusive if
// any of the following holds:
// 1) It points to a heap-allocated backing store
// 2) It points to a stack-allocated backing store
// for any parent frame.
// 3) It is the only variable that references its
// backing store.
// 4) It is nil.
//
// The nice thing about exclusive slice variables is that
// it is always safe to do
// s = move2heap(s)
// whenever s is an exclusive slice variable. Because no
// one else has a reference to the backing store, no one
// else can tell that we moved the backing store from one
// location to another.
//
// Note that exclusiveness is a dynamic property. A slice
// variable may be exclusive during some parts of execution
// and not exclusive during others.
//
// The following operations set or preserve the exclusivity
// of a slice variable s:
// s = nil
// s = append(s, ...)
// s = s[i:j]
// ... = s[i]
// s[i] = ...
// f(s) where f does not escape its argument
// Other operations destroy exclusivity. A non-exhaustive list includes:
// x = s
// *p = s
// f(s) where f escapes its argument
// return s
// To err on the safe side, we white list exclusivity-preserving
// operations and we asssume that any other operations that mention s
// destroy its exclusivity.
//
// Our strategy is to move the backing store of s to the heap before
// any exclusive->nonexclusive transition. That way, s will only ever
// have a reference to a stack backing store while it is exclusive.
//
// move2heap for a variable s is implemented with:
// if s points to within the stack frame {
// s2 := make([]T, s.len, s.cap)
// copy(s2[:s.cap], s[:s.cap])
// s = s2
// }
// Note that in general we need to copy all of s[:cap(s)] elements when
// moving to the heap. As an optimization, we keep track of slice variables
// whose capacity, and the elements in s[len(s):cap(s)], are never accessed.
// For those slice variables, we can allocate to the next size class above
// the length, which saves memory and copying cost.
import (
"cmd/compile/internal/base"
"cmd/compile/internal/escape"
"cmd/compile/internal/ir"
"cmd/compile/internal/reflectdata"
)
func Funcs(all []*ir.Func) {
if base.Flag.N != 0 {
return
}
for _, fn := range all {
analyze(fn)
}
}
func analyze(fn *ir.Func) {
type sliceInfo struct {
// Slice variable.
s *ir.Name
// Count of uses that this pass understands.
okUses int32
// Count of all uses found.
allUses int32
// A place where the slice variable transitions from
// exclusive to nonexclusive.
// We could keep track of more than one, but one is enough for now.
// Currently, this can be either a return statement or
// an assignment.
// TODO: other possible transitions?
transition ir.Stmt
// Each s = append(s, ...) instance we found.
appends []*ir.CallExpr
// Weight of the number of s = append(s, ...) instances we found.
// The optimizations we do are only really useful if there are at
// least weight 2. (Note: appends in loops have weight >= 2.)
appendWeight int
// Whether we ever do cap(s), or other operations that use cap(s)
// (possibly implicitly), like s[i:j].
capUsed bool
}
// Every variable (*ir.Name) that we are tracking will have
// a non-nil *sliceInfo in its Opt field.
haveLocalSlice := false
maxStackSize := int64(base.Debug.VariableMakeThreshold)
var namedRets []*ir.Name
for _, s := range fn.Dcl {
if !s.Type().IsSlice() {
continue
}
if s.Type().Elem().Size() > maxStackSize {
continue
}
if !base.VariableMakeHash.MatchPos(s.Pos(), nil) {
continue
}
s.Opt = &sliceInfo{s: s} // start tracking s
haveLocalSlice = true
if s.Class == ir.PPARAMOUT {
namedRets = append(namedRets, s)
}
}
if !haveLocalSlice {
return
}
// Keep track of loop depth while walking.
loopDepth := 0
// tracking returns the info for the slice variable if n is a slice
// variable that we're still considering, or nil otherwise.
tracking := func(n ir.Node) *sliceInfo {
if n == nil || n.Op() != ir.ONAME {
return nil
}
s := n.(*ir.Name)
if s.Opt == nil {
return nil
}
return s.Opt.(*sliceInfo)
}
// addTransition(n, loc) records that s experiences an exclusive->nonexclusive
// transition somewhere within loc.
addTransition := func(i *sliceInfo, loc ir.Stmt) {
if i.transition != nil {
// We only keep track of a single exclusive->nonexclusive transition
// for a slice variable. If we find more than one, give up.
// (More than one transition location would be fine, but we would
// start to get worried about introducing too much additional code.)
i.s.Opt = nil
return
}
i.transition = loc
}
// Examine an x = y assignment that occurs somewhere within statement stmt.
assign := func(x, y ir.Node, stmt ir.Stmt) {
if i := tracking(x); i != nil {
// s = y. Check for understood patterns for y.
if y == nil || y.Op() == ir.ONIL {
// s = nil is ok.
i.okUses++
} else if y.Op() == ir.OSLICELIT {
// s = []{...} is ok.
// Note: this reveals capacity. Should it?
i.okUses++
i.capUsed = true
} else if y.Op() == ir.OSLICE {
y := y.(*ir.SliceExpr)
if y.X == i.s {
// s = s[...:...] is ok
i.okUses += 2
i.capUsed = true
}
} else if y.Op() == ir.OAPPEND {
y := y.(*ir.CallExpr)
if y.Args[0] == i.s {
// s = append(s, ...) is ok
i.okUses += 2
i.appends = append(i.appends, y)
i.appendWeight += 1 + loopDepth
}
// TODO: s = append(nil, ...)?
}
// Note that technically s = make([]T, ...) preserves exclusivity, but
// we don't track that because we assume users who wrote that know
// better than the compiler does.
// TODO: figure out how to handle s = fn(..., s, ...)
// It would be nice to maintain exclusivity of s in this situation.
// But unfortunately, fn can return one of its other arguments, which
// may be a slice with a stack-allocated backing store other than s.
// (which may have preexisting references to its backing store).
//
// Maybe we could do it if s is the only argument?
}
if i := tracking(y); i != nil {
// ... = s
// Treat this as an exclusive->nonexclusive transition.
i.okUses++
addTransition(i, stmt)
}
}
var do func(ir.Node) bool
do = func(n ir.Node) bool {
if n == nil {
return false
}
switch n.Op() {
case ir.ONAME:
if i := tracking(n); i != nil {
// A use of a slice variable. Count it.
i.allUses++
}
case ir.ODCL:
n := n.(*ir.Decl)
if i := tracking(n.X); i != nil {
i.okUses++
}
case ir.OINDEX:
n := n.(*ir.IndexExpr)
if i := tracking(n.X); i != nil {
// s[i] is ok.
i.okUses++
}
case ir.OLEN:
n := n.(*ir.UnaryExpr)
if i := tracking(n.X); i != nil {
// len(s) is ok
i.okUses++
}
case ir.OCAP:
n := n.(*ir.UnaryExpr)
if i := tracking(n.X); i != nil {
// cap(s) is ok
i.okUses++
i.capUsed = true
}
case ir.OADDR:
n := n.(*ir.AddrExpr)
if n.X.Op() == ir.OINDEX {
n := n.X.(*ir.IndexExpr)
if i := tracking(n.X); i != nil {
// &s[i] is definitely a nonexclusive transition.
// (We need this case because s[i] is ok, but &s[i] is not.)
i.s.Opt = nil
}
}
case ir.ORETURN:
n := n.(*ir.ReturnStmt)
for _, x := range n.Results {
if i := tracking(x); i != nil {
i.okUses++
// We go exclusive->nonexclusive here
addTransition(i, n)
}
}
if len(n.Results) == 0 {
// Uses of named result variables are implicit here.
for _, x := range namedRets {
if i := tracking(x); i != nil {
addTransition(i, n)
}
}
}
case ir.OCALLFUNC:
n := n.(*ir.CallExpr)
for idx, arg := range n.Args {
if i := tracking(arg); i != nil {
if !argLeak(n, idx) {
// Passing s to a nonescaping arg is ok.
i.okUses++
i.capUsed = true
}
}
}
case ir.ORANGE:
// Range over slice is ok.
n := n.(*ir.RangeStmt)
if i := tracking(n.X); i != nil {
i.okUses++
}
case ir.OAS:
n := n.(*ir.AssignStmt)
assign(n.X, n.Y, n)
case ir.OAS2:
n := n.(*ir.AssignListStmt)
for i := range len(n.Lhs) {
assign(n.Lhs[i], n.Rhs[i], n)
}
case ir.OCLOSURE:
n := n.(*ir.ClosureExpr)
for _, v := range n.Func.ClosureVars {
do(v.Outer)
}
}
if n.Op() == ir.OFOR || n.Op() == ir.ORANGE {
// Note: loopDepth isn't really right for init portion
// of the for statement, but that's ok. Correctness
// does not depend on depth info.
loopDepth++
defer func() { loopDepth-- }()
}
// Check all the children.
ir.DoChildren(n, do)
return false
}
// Run the analysis over the whole body.
for _, stmt := range fn.Body {
do(stmt)
}
// Process accumulated info to find slice variables
// that we can allocate on the stack.
for _, s := range fn.Dcl {
if s.Opt == nil {
continue
}
i := s.Opt.(*sliceInfo)
s.Opt = nil
if i.okUses != i.allUses {
// Some use of i.s that don't understand lurks. Give up.
continue
}
// At this point, we've decided that we *can* do
// the optimization.
if i.transition == nil {
// Exclusive for its whole lifetime. That means it
// didn't escape. We can already handle nonescaping
// slices without this pass.
continue
}
if i.appendWeight < 2 {
// This optimization only really helps if there is
// (dynamically) more than one append.
continue
}
// Commit point - at this point we've decided we *should*
// do the optimization.
// Insert a move2heap operation before the exclusive->nonexclusive
// transition.
move := ir.NewMoveToHeapExpr(i.transition.Pos(), i.s)
if i.capUsed {
move.PreserveCapacity = true
}
move.RType = reflectdata.AppendElemRType(i.transition.Pos(), i.appends[0])
move.SetType(i.s.Type())
move.SetTypecheck(1)
as := ir.NewAssignStmt(i.transition.Pos(), i.s, move)
as.SetTypecheck(1)
i.transition.PtrInit().Prepend(as)
// Note: we prepend because we need to put the move2heap
// operation first, before any other init work, as the transition
// might occur in the init work.
// Now that we've inserted a move2heap operation before every
// exclusive -> nonexclusive transition, appends can now use
// stack backing stores.
// (This is the whole point of this pass, to enable stack
// allocation of append backing stores.)
for _, a := range i.appends {
a.SetEsc(ir.EscNone)
if i.capUsed {
a.UseBuf = true
}
}
}
}
// argLeak reports if the idx'th argument to the call n escapes anywhere
// (to the heap, another argument, return value, etc.)
// If unknown returns true.
func argLeak(n *ir.CallExpr, idx int) bool {
if n.Op() != ir.OCALLFUNC {
return true
}
fn := ir.StaticCalleeName(ir.StaticValue(n.Fun))
if fn == nil {
return true
}
fntype := fn.Type()
if recv := fntype.Recv(); recv != nil {
if idx == 0 {
return escape.ParseLeaks(recv.Note).Any()
}
idx--
}
return escape.ParseLeaks(fntype.Params()[idx].Note).Any()
}

View file

@ -156,6 +156,7 @@ func init() {
gp11sb = regInfo{inputs: []regMask{gpspsbg}, outputs: gponly} gp11sb = regInfo{inputs: []regMask{gpspsbg}, outputs: gponly}
gp21 = regInfo{inputs: []regMask{gp, gp}, outputs: gponly} gp21 = regInfo{inputs: []regMask{gp, gp}, outputs: gponly}
gp21sp = regInfo{inputs: []regMask{gpsp, gp}, outputs: gponly} gp21sp = regInfo{inputs: []regMask{gpsp, gp}, outputs: gponly}
gp21sp2 = regInfo{inputs: []regMask{gp, gpsp}, outputs: gponly}
gp21sb = regInfo{inputs: []regMask{gpspsbg, gpsp}, outputs: gponly} gp21sb = regInfo{inputs: []regMask{gpspsbg, gpsp}, outputs: gponly}
gp21shift = regInfo{inputs: []regMask{gp, cx}, outputs: []regMask{gp}} gp21shift = regInfo{inputs: []regMask{gp, cx}, outputs: []regMask{gp}}
gp31shift = regInfo{inputs: []regMask{gp, gp, cx}, outputs: []regMask{gp}} gp31shift = regInfo{inputs: []regMask{gp, gp, cx}, outputs: []regMask{gp}}
@ -361,7 +362,7 @@ func init() {
{name: "ADDQconstmodify", argLength: 2, reg: gpstoreconst, asm: "ADDQ", aux: "SymValAndOff", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, {name: "ADDQconstmodify", argLength: 2, reg: gpstoreconst, asm: "ADDQ", aux: "SymValAndOff", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"},
{name: "ADDLconstmodify", argLength: 2, reg: gpstoreconst, asm: "ADDL", aux: "SymValAndOff", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"}, {name: "ADDLconstmodify", argLength: 2, reg: gpstoreconst, asm: "ADDL", aux: "SymValAndOff", clobberFlags: true, faultOnNilArg0: true, symEffect: "Read,Write"},
{name: "SUBQ", argLength: 2, reg: gp21, asm: "SUBQ", resultInArg0: true, clobberFlags: true}, {name: "SUBQ", argLength: 2, reg: gp21sp2, asm: "SUBQ", resultInArg0: true, clobberFlags: true},
{name: "SUBL", argLength: 2, reg: gp21, asm: "SUBL", resultInArg0: true, clobberFlags: true}, {name: "SUBL", argLength: 2, reg: gp21, asm: "SUBL", resultInArg0: true, clobberFlags: true},
{name: "SUBQconst", argLength: 1, reg: gp11, asm: "SUBQ", aux: "Int32", resultInArg0: true, clobberFlags: true}, {name: "SUBQconst", argLength: 1, reg: gp11, asm: "SUBQ", aux: "Int32", resultInArg0: true, clobberFlags: true},
{name: "SUBLconst", argLength: 1, reg: gp11, asm: "SUBL", aux: "Int32", resultInArg0: true, clobberFlags: true}, {name: "SUBLconst", argLength: 1, reg: gp11, asm: "SUBL", aux: "Int32", resultInArg0: true, clobberFlags: true},

View file

@ -573,6 +573,8 @@
(TBNZ [0] (GreaterThanF cc) yes no) => (FGT cc yes no) (TBNZ [0] (GreaterThanF cc) yes no) => (FGT cc yes no)
(TBNZ [0] (GreaterEqualF cc) yes no) => (FGE cc yes no) (TBNZ [0] (GreaterEqualF cc) yes no) => (FGE cc yes no)
(TB(Z|NZ) [0] (XORconst [1] x) yes no) => (TB(NZ|Z) [0] x yes no)
((EQ|NE|LT|LE|GT|GE) (CMPconst [0] z:(AND x y)) yes no) && z.Uses == 1 => ((EQ|NE|LT|LE|GT|GE) (TST x y) yes no) ((EQ|NE|LT|LE|GT|GE) (CMPconst [0] z:(AND x y)) yes no) && z.Uses == 1 => ((EQ|NE|LT|LE|GT|GE) (TST x y) yes no)
((EQ|NE|LT|LE|GT|GE) (CMPconst [0] x:(ANDconst [c] y)) yes no) && x.Uses == 1 => ((EQ|NE|LT|LE|GT|GE) (TSTconst [c] y) yes no) ((EQ|NE|LT|LE|GT|GE) (CMPconst [0] x:(ANDconst [c] y)) yes no) && x.Uses == 1 => ((EQ|NE|LT|LE|GT|GE) (TSTconst [c] y) yes no)
((EQ|NE|LT|LE|GT|GE) (CMPWconst [0] z:(AND x y)) yes no) && z.Uses == 1 => ((EQ|NE|LT|LE|GT|GE) (TSTW x y) yes no) ((EQ|NE|LT|LE|GT|GE) (CMPWconst [0] z:(AND x y)) yes no) && z.Uses == 1 => ((EQ|NE|LT|LE|GT|GE) (TSTW x y) yes no)
@ -1814,3 +1816,7 @@
(Select0 (Mul64uover x y)) => (MUL x y) (Select0 (Mul64uover x y)) => (MUL x y)
(Select1 (Mul64uover x y)) => (NotEqual (CMPconst (UMULH <typ.UInt64> x y) [0])) (Select1 (Mul64uover x y)) => (NotEqual (CMPconst (UMULH <typ.UInt64> x y) [0]))
// 32 mul 32 -> 64
(MUL r:(MOVWUreg x) s:(MOVWUreg y)) && r.Uses == 1 && s.Uses == 1 => (UMULL x y)
(MUL r:(MOVWreg x) s:(MOVWreg y)) && r.Uses == 1 && s.Uses == 1 => (MULL x y)

View file

@ -743,9 +743,6 @@
(MULV x (MOVVconst [c])) && canMulStrengthReduce(config, c) => {mulStrengthReduce(v, x, c)} (MULV x (MOVVconst [c])) && canMulStrengthReduce(config, c) => {mulStrengthReduce(v, x, c)}
(MULV (NEGV x) (MOVVconst [c])) => (MULV x (MOVVconst [-c]))
(MULV (NEGV x) (NEGV y)) => (MULV x y)
(ADDV x0 x1:(SLLVconst [c] y)) && x1.Uses == 1 && c > 0 && c <= 4 => (ADDshiftLLV x0 y [c]) (ADDV x0 x1:(SLLVconst [c] y)) && x1.Uses == 1 && c > 0 && c <= 4 => (ADDshiftLLV x0 y [c])
// fold constant in ADDshift op // fold constant in ADDshift op

View file

@ -388,6 +388,7 @@ func init() {
argLength: 2, argLength: 2,
reg: regInfo{ reg: regInfo{
inputs: []regMask{gp}, inputs: []regMask{gp},
clobbers: buildReg("F31"),
clobbersArg0: true, clobbersArg0: true,
}, },
faultOnNilArg0: true, faultOnNilArg0: true,

View file

@ -689,36 +689,36 @@
(MOVDnop (MOVDconst [c])) => (MOVDconst [c]) (MOVDnop (MOVDconst [c])) => (MOVDconst [c])
// Avoid unnecessary zero and sign extension when right shifting. // Avoid unnecessary zero and sign extension when right shifting.
(SRAI <t> [x] (MOVWreg y)) && x >= 0 && x <= 31 => (SRAIW <t> [int64(x)] y) (SRAI [x] (MOVWreg y)) && x >= 0 && x <= 31 => (SRAIW [x] y)
(SRLI <t> [x] (MOVWUreg y)) && x >= 0 && x <= 31 => (SRLIW <t> [int64(x)] y) (SRLI [x] (MOVWUreg y)) && x >= 0 && x <= 31 => (SRLIW [x] y)
// Replace right shifts that exceed size of signed type. // Replace right shifts that exceed size of signed type.
(SRAI <t> [x] (MOVBreg y)) && x >= 8 => (SRAI [63] (SLLI <t> [56] y)) (SRAI <t> [x] (MOVBreg y)) && x >= 8 => (SRAI [63] (SLLI <t> [56] y))
(SRAI <t> [x] (MOVHreg y)) && x >= 16 => (SRAI [63] (SLLI <t> [48] y)) (SRAI <t> [x] (MOVHreg y)) && x >= 16 => (SRAI [63] (SLLI <t> [48] y))
(SRAI <t> [x] (MOVWreg y)) && x >= 32 => (SRAIW [31] y) (SRAI [x] (MOVWreg y)) && x >= 32 => (SRAIW [31] y)
// Eliminate right shifts that exceed size of unsigned type. // Eliminate right shifts that exceed size of unsigned type.
(SRLI <t> [x] (MOVBUreg y)) && x >= 8 => (MOVDconst <t> [0]) (SRLI [x] (MOVBUreg y)) && x >= 8 => (MOVDconst [0])
(SRLI <t> [x] (MOVHUreg y)) && x >= 16 => (MOVDconst <t> [0]) (SRLI [x] (MOVHUreg y)) && x >= 16 => (MOVDconst [0])
(SRLI <t> [x] (MOVWUreg y)) && x >= 32 => (MOVDconst <t> [0]) (SRLI [x] (MOVWUreg y)) && x >= 32 => (MOVDconst [0])
// Fold constant into immediate instructions where possible. // Fold constant into immediate instructions where possible.
(ADD (MOVDconst <t> [val]) x) && is32Bit(val) && !t.IsPtr() => (ADDI [val] x) (ADD (MOVDconst <t> [val]) x) && is32Bit(val) && !t.IsPtr() => (ADDI [val] x)
(AND (MOVDconst [val]) x) && is32Bit(val) => (ANDI [val] x) (AND (MOVDconst [val]) x) && is32Bit(val) => (ANDI [val] x)
(OR (MOVDconst [val]) x) && is32Bit(val) => (ORI [val] x) (OR (MOVDconst [val]) x) && is32Bit(val) => (ORI [val] x)
(XOR (MOVDconst [val]) x) && is32Bit(val) => (XORI [val] x) (XOR (MOVDconst [val]) x) && is32Bit(val) => (XORI [val] x)
(ROL x (MOVDconst [val])) => (RORI [int64(int8(-val)&63)] x) (ROL x (MOVDconst [val])) => (RORI [-val&63] x)
(ROLW x (MOVDconst [val])) => (RORIW [int64(int8(-val)&31)] x) (ROLW x (MOVDconst [val])) => (RORIW [-val&31] x)
(ROR x (MOVDconst [val])) => (RORI [int64(val&63)] x) (ROR x (MOVDconst [val])) => (RORI [val&63] x)
(RORW x (MOVDconst [val])) => (RORIW [int64(val&31)] x) (RORW x (MOVDconst [val])) => (RORIW [val&31] x)
(SLL x (MOVDconst [val])) => (SLLI [int64(val&63)] x) (SLL x (MOVDconst [val])) => (SLLI [val&63] x)
(SRL x (MOVDconst [val])) => (SRLI [int64(val&63)] x) (SLLW x (MOVDconst [val])) => (SLLIW [val&31] x)
(SLLW x (MOVDconst [val])) => (SLLIW [int64(val&31)] x) (SRL x (MOVDconst [val])) => (SRLI [val&63] x)
(SRLW x (MOVDconst [val])) => (SRLIW [int64(val&31)] x) (SRLW x (MOVDconst [val])) => (SRLIW [val&31] x)
(SRA x (MOVDconst [val])) => (SRAI [int64(val&63)] x) (SRA x (MOVDconst [val])) => (SRAI [val&63] x)
(SRAW x (MOVDconst [val])) => (SRAIW [int64(val&31)] x) (SRAW x (MOVDconst [val])) => (SRAIW [val&31] x)
(SLT x (MOVDconst [val])) && val >= -2048 && val <= 2047 => (SLTI [val] x) (SLT x (MOVDconst [val])) && is12Bit(val) => (SLTI [val] x)
(SLTU x (MOVDconst [val])) && val >= -2048 && val <= 2047 => (SLTIU [val] x) (SLTU x (MOVDconst [val])) && is12Bit(val) => (SLTIU [val] x)
// Replace negated left rotation with right rotation. // Replace negated left rotation with right rotation.
(ROL x (NEG y)) => (ROR x y) (ROL x (NEG y)) => (ROR x y)
@ -782,7 +782,7 @@
(SRAI [x] (MOVDconst [y])) => (MOVDconst [int64(y) >> uint32(x)]) (SRAI [x] (MOVDconst [y])) => (MOVDconst [int64(y) >> uint32(x)])
// Combine doubling via addition with shift. // Combine doubling via addition with shift.
(SLLI <t> [c] (ADD x x)) && c < t.Size() * 8 - 1 => (SLLI <t> [c+1] x) (SLLI <t> [c] (ADD x x)) && c < t.Size() * 8 - 1 => (SLLI [c+1] x)
(SLLI <t> [c] (ADD x x)) && c >= t.Size() * 8 - 1 => (MOVDconst [0]) (SLLI <t> [c] (ADD x x)) && c >= t.Size() * 8 - 1 => (MOVDconst [0])
// SLTI/SLTIU with constants. // SLTI/SLTIU with constants.
@ -792,7 +792,6 @@
// SLTI/SLTIU with known outcomes. // SLTI/SLTIU with known outcomes.
(SLTI [x] (ANDI [y] _)) && y >= 0 && int64(y) < int64(x) => (MOVDconst [1]) (SLTI [x] (ANDI [y] _)) && y >= 0 && int64(y) < int64(x) => (MOVDconst [1])
(SLTIU [x] (ANDI [y] _)) && y >= 0 && uint64(y) < uint64(x) => (MOVDconst [1]) (SLTIU [x] (ANDI [y] _)) && y >= 0 && uint64(y) < uint64(x) => (MOVDconst [1])
(SLTI [x] (ORI [y] _)) && y >= 0 && int64(y) >= int64(x) => (MOVDconst [0])
(SLTIU [x] (ORI [y] _)) && y >= 0 && uint64(y) >= uint64(x) => (MOVDconst [0]) (SLTIU [x] (ORI [y] _)) && y >= 0 && uint64(y) >= uint64(x) => (MOVDconst [0])
// SLT/SLTU with known outcomes. // SLT/SLTU with known outcomes.

View file

@ -97,8 +97,10 @@
// Helpers for expand calls // Helpers for expand calls
// Some of these are copied from generic.rules // Some of these are copied from generic.rules
(IMake _typ (StructMake val)) => (IMake _typ val) (IMake _typ (StructMake ___)) => imakeOfStructMake(v)
(StructSelect [0] (IData x)) => (IData x) (StructSelect (IData x)) && v.Type.Size() > 0 => (IData x)
(StructSelect (IData x)) && v.Type.Size() == 0 && v.Type.IsStruct() => (StructMake)
(StructSelect (IData x)) && v.Type.Size() == 0 && v.Type.IsArray() => (ArrayMake0)
(StructSelect [i] x:(StructMake ___)) => x.Args[i] (StructSelect [i] x:(StructMake ___)) => x.Args[i]
@ -109,7 +111,7 @@
// More annoying case: (ArraySelect[0] (StructSelect[0] isAPtr)) // More annoying case: (ArraySelect[0] (StructSelect[0] isAPtr))
// There, result of the StructSelect is an Array (not a pointer) and // There, result of the StructSelect is an Array (not a pointer) and
// the pre-rewrite input to the ArraySelect is a struct, not a pointer. // the pre-rewrite input to the ArraySelect is a struct, not a pointer.
(StructSelect [0] x) && x.Type.IsPtrShaped() => x (StructSelect x) && x.Type.IsPtrShaped() => x
(ArraySelect [0] x) && x.Type.IsPtrShaped() => x (ArraySelect [0] x) && x.Type.IsPtrShaped() => x
// These, too. Bits is bits. // These, too. Bits is bits.
@ -119,6 +121,7 @@
(Store _ (StructMake ___) _) => rewriteStructStore(v) (Store _ (StructMake ___) _) => rewriteStructStore(v)
(IMake _typ (ArrayMake1 val)) => (IMake _typ val)
(ArraySelect (ArrayMake1 x)) => x (ArraySelect (ArrayMake1 x)) => x
(ArraySelect [0] (IData x)) => (IData x) (ArraySelect [0] (IData x)) => (IData x)

View file

@ -195,6 +195,11 @@
// Convert x * -1 to -x. // Convert x * -1 to -x.
(Mul(8|16|32|64) (Const(8|16|32|64) [-1]) x) => (Neg(8|16|32|64) x) (Mul(8|16|32|64) (Const(8|16|32|64) [-1]) x) => (Neg(8|16|32|64) x)
// Convert -x * c to x * -c
(Mul(8|16|32|64) (Const(8|16|32|64) <t> [c]) (Neg(8|16|32|64) x)) => (Mul(8|16|32|64) x (Const(8|16|32|64) <t> [-c]))
(Mul(8|16|32|64) (Neg(8|16|32|64) x) (Neg(8|16|32|64) y)) => (Mul(8|16|32|64) x y)
// DeMorgan's Laws // DeMorgan's Laws
(And(8|16|32|64) <t> (Com(8|16|32|64) x) (Com(8|16|32|64) y)) => (Com(8|16|32|64) (Or(8|16|32|64) <t> x y)) (And(8|16|32|64) <t> (Com(8|16|32|64) x) (Com(8|16|32|64) y)) => (Com(8|16|32|64) (Or(8|16|32|64) <t> x y))
(Or(8|16|32|64) <t> (Com(8|16|32|64) x) (Com(8|16|32|64) y)) => (Com(8|16|32|64) (And(8|16|32|64) <t> x y)) (Or(8|16|32|64) <t> (Com(8|16|32|64) x) (Com(8|16|32|64) y)) => (Com(8|16|32|64) (And(8|16|32|64) <t> x y))
@ -337,6 +342,12 @@
(OrB ((Less|Leq)16U (Const16 [c]) x) (Leq16U x (Const16 [d]))) && uint16(c) >= uint16(d+1) && uint16(d+1) > uint16(d) => ((Less|Leq)16U (Const16 <x.Type> [c-d-1]) (Sub16 <x.Type> x (Const16 <x.Type> [d+1]))) (OrB ((Less|Leq)16U (Const16 [c]) x) (Leq16U x (Const16 [d]))) && uint16(c) >= uint16(d+1) && uint16(d+1) > uint16(d) => ((Less|Leq)16U (Const16 <x.Type> [c-d-1]) (Sub16 <x.Type> x (Const16 <x.Type> [d+1])))
(OrB ((Less|Leq)8U (Const8 [c]) x) (Leq8U x (Const8 [d]))) && uint8(c) >= uint8(d+1) && uint8(d+1) > uint8(d) => ((Less|Leq)8U (Const8 <x.Type> [c-d-1]) (Sub8 <x.Type> x (Const8 <x.Type> [d+1]))) (OrB ((Less|Leq)8U (Const8 [c]) x) (Leq8U x (Const8 [d]))) && uint8(c) >= uint8(d+1) && uint8(d+1) > uint8(d) => ((Less|Leq)8U (Const8 <x.Type> [c-d-1]) (Sub8 <x.Type> x (Const8 <x.Type> [d+1])))
// single bit difference: ( x != c && x != d ) -> ( x|(c^d) != c )
(AndB (Neq(64|32|16|8) x cv:(Const(64|32|16|8) [c])) (Neq(64|32|16|8) x (Const(64|32|16|8) [d]))) && c|d == c && oneBit(c^d) => (Neq(64|32|16|8) (Or(64|32|16|8) <x.Type> x (Const(64|32|16|8) <x.Type> [c^d])) cv)
// single bit difference: ( x == c || x == d ) -> ( x|(c^d) == c )
(OrB (Eq(64|32|16|8) x cv:(Const(64|32|16|8) [c])) (Eq(64|32|16|8) x (Const(64|32|16|8) [d]))) && c|d == c && oneBit(c^d) => (Eq(64|32|16|8) (Or(64|32|16|8) <x.Type> x (Const(64|32|16|8) <x.Type> [c^d])) cv)
// NaN check: ( x != x || x (>|>=|<|<=) c ) -> ( !(c (>=|>|<=|<) x) ) // NaN check: ( x != x || x (>|>=|<|<=) c ) -> ( !(c (>=|>|<=|<) x) )
(OrB (Neq64F x x) ((Less|Leq)64F x y:(Const64F [c]))) => (Not ((Leq|Less)64F y x)) (OrB (Neq64F x x) ((Less|Leq)64F x y:(Const64F [c]))) => (Not ((Leq|Less)64F y x))
(OrB (Neq64F x x) ((Less|Leq)64F y:(Const64F [c]) x)) => (Not ((Leq|Less)64F x y)) (OrB (Neq64F x x) ((Less|Leq)64F y:(Const64F [c]) x)) => (Not ((Leq|Less)64F x y))
@ -933,8 +944,10 @@
@x.Block (Load <v.Type> (OffPtr <v.Type.PtrTo()> [t.FieldOff(int(i))] ptr) mem) @x.Block (Load <v.Type> (OffPtr <v.Type.PtrTo()> [t.FieldOff(int(i))] ptr) mem)
// Putting struct{*byte} and similar into direct interfaces. // Putting struct{*byte} and similar into direct interfaces.
(IMake _typ (StructMake val)) => (IMake _typ val) (IMake _typ (StructMake ___)) => imakeOfStructMake(v)
(StructSelect [0] (IData x)) => (IData x) (StructSelect (IData x)) && v.Type.Size() > 0 => (IData x)
(StructSelect (IData x)) && v.Type.Size() == 0 && v.Type.IsStruct() => (StructMake)
(StructSelect (IData x)) && v.Type.Size() == 0 && v.Type.IsArray() => (ArrayMake0)
// un-SSAable values use mem->mem copies // un-SSAable values use mem->mem copies
(Store {t} dst (Load src mem) mem) && !CanSSA(t) => (Store {t} dst (Load src mem) mem) && !CanSSA(t) =>

View file

@ -426,7 +426,14 @@ func (x *expandState) decomposeAsNecessary(pos src.XPos, b *Block, a, m0 *Value,
if a.Op == OpIMake { if a.Op == OpIMake {
data := a.Args[1] data := a.Args[1]
for data.Op == OpStructMake || data.Op == OpArrayMake1 { for data.Op == OpStructMake || data.Op == OpArrayMake1 {
data = data.Args[0] // A struct make might have a few zero-sized fields.
// Use the pointer-y one we know is there.
for _, a := range data.Args {
if a.Type.Size() > 0 {
data = a
break
}
}
} }
return x.decomposeAsNecessary(pos, b, data, mem, rc.next(data.Type)) return x.decomposeAsNecessary(pos, b, data, mem, rc.next(data.Type))
} }

View file

@ -10,7 +10,9 @@ import (
) )
// fuseEarly runs fuse(f, fuseTypePlain|fuseTypeIntInRange|fuseTypeNanCheck). // fuseEarly runs fuse(f, fuseTypePlain|fuseTypeIntInRange|fuseTypeNanCheck).
func fuseEarly(f *Func) { fuse(f, fuseTypePlain|fuseTypeIntInRange|fuseTypeNanCheck) } func fuseEarly(f *Func) {
fuse(f, fuseTypePlain|fuseTypeIntInRange|fuseTypeSingleBitDifference|fuseTypeNanCheck)
}
// fuseLate runs fuse(f, fuseTypePlain|fuseTypeIf|fuseTypeBranchRedirect). // fuseLate runs fuse(f, fuseTypePlain|fuseTypeIf|fuseTypeBranchRedirect).
func fuseLate(f *Func) { fuse(f, fuseTypePlain|fuseTypeIf|fuseTypeBranchRedirect) } func fuseLate(f *Func) { fuse(f, fuseTypePlain|fuseTypeIf|fuseTypeBranchRedirect) }
@ -21,6 +23,7 @@ const (
fuseTypePlain fuseType = 1 << iota fuseTypePlain fuseType = 1 << iota
fuseTypeIf fuseTypeIf
fuseTypeIntInRange fuseTypeIntInRange
fuseTypeSingleBitDifference
fuseTypeNanCheck fuseTypeNanCheck
fuseTypeBranchRedirect fuseTypeBranchRedirect
fuseTypeShortCircuit fuseTypeShortCircuit
@ -41,6 +44,9 @@ func fuse(f *Func, typ fuseType) {
if typ&fuseTypeIntInRange != 0 { if typ&fuseTypeIntInRange != 0 {
changed = fuseIntInRange(b) || changed changed = fuseIntInRange(b) || changed
} }
if typ&fuseTypeSingleBitDifference != 0 {
changed = fuseSingleBitDifference(b) || changed
}
if typ&fuseTypeNanCheck != 0 { if typ&fuseTypeNanCheck != 0 {
changed = fuseNanCheck(b) || changed changed = fuseNanCheck(b) || changed
} }

View file

@ -19,6 +19,14 @@ func fuseNanCheck(b *Block) bool {
return fuseComparisons(b, canOptNanCheck) return fuseComparisons(b, canOptNanCheck)
} }
// fuseSingleBitDifference replaces the short-circuit operators between equality checks with
// constants that only differ by a single bit. For example, it would convert
// `if x == 4 || x == 6 { ... }` into `if (x == 4) | (x == 6) { ... }`. Rewrite rules can
// then optimize these using a bitwise operation, in this case generating `if x|2 == 6 { ... }`.
func fuseSingleBitDifference(b *Block) bool {
return fuseComparisons(b, canOptSingleBitDifference)
}
// fuseComparisons looks for control graphs that match this pattern: // fuseComparisons looks for control graphs that match this pattern:
// //
// p - predecessor // p - predecessor
@ -229,3 +237,40 @@ func canOptNanCheck(x, y *Value, op Op) bool {
} }
return false return false
} }
// canOptSingleBitDifference returns true if x op y matches either:
//
// v == c || v == d
// v != c && v != d
//
// Where c and d are constant values that differ by a single bit.
func canOptSingleBitDifference(x, y *Value, op Op) bool {
if x.Op != y.Op {
return false
}
switch x.Op {
case OpEq64, OpEq32, OpEq16, OpEq8:
if op != OpOrB {
return false
}
case OpNeq64, OpNeq32, OpNeq16, OpNeq8:
if op != OpAndB {
return false
}
default:
return false
}
xi := getConstIntArgIndex(x)
if xi < 0 {
return false
}
yi := getConstIntArgIndex(y)
if yi < 0 {
return false
}
if x.Args[xi^1] != y.Args[yi^1] {
return false
}
return oneBit(x.Args[xi].AuxInt ^ y.Args[yi].AuxInt)
}

View file

@ -11481,7 +11481,7 @@ var opcodeTable = [...]opInfo{
reg: regInfo{ reg: regInfo{
inputs: []inputInfo{ inputs: []inputInfo{
{0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15
{1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15
}, },
outputs: []outputInfo{ outputs: []outputInfo{
{0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15
@ -68770,6 +68770,7 @@ var opcodeTable = [...]opInfo{
inputs: []inputInfo{ inputs: []inputInfo{
{0, 1071644664}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 R23 R24 R25 R26 R27 R28 R29 R31 {0, 1071644664}, // R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 R23 R24 R25 R26 R27 R28 R29 R31
}, },
clobbers: 2305843009213693952, // F31
clobbersArg0: true, clobbersArg0: true,
}, },
}, },

View file

@ -466,57 +466,56 @@ func (ft *factsTable) initLimitForNewValue(v *Value) {
// signedMin records the fact that we know v is at least // signedMin records the fact that we know v is at least
// min in the signed domain. // min in the signed domain.
func (ft *factsTable) signedMin(v *Value, min int64) bool { func (ft *factsTable) signedMin(v *Value, min int64) {
return ft.newLimit(v, limit{min: min, max: math.MaxInt64, umin: 0, umax: math.MaxUint64}) ft.newLimit(v, limit{min: min, max: math.MaxInt64, umin: 0, umax: math.MaxUint64})
} }
// signedMax records the fact that we know v is at most // signedMax records the fact that we know v is at most
// max in the signed domain. // max in the signed domain.
func (ft *factsTable) signedMax(v *Value, max int64) bool { func (ft *factsTable) signedMax(v *Value, max int64) {
return ft.newLimit(v, limit{min: math.MinInt64, max: max, umin: 0, umax: math.MaxUint64}) ft.newLimit(v, limit{min: math.MinInt64, max: max, umin: 0, umax: math.MaxUint64})
} }
func (ft *factsTable) signedMinMax(v *Value, min, max int64) bool { func (ft *factsTable) signedMinMax(v *Value, min, max int64) {
return ft.newLimit(v, limit{min: min, max: max, umin: 0, umax: math.MaxUint64}) ft.newLimit(v, limit{min: min, max: max, umin: 0, umax: math.MaxUint64})
} }
// setNonNegative records the fact that v is known to be non-negative. // setNonNegative records the fact that v is known to be non-negative.
func (ft *factsTable) setNonNegative(v *Value) bool { func (ft *factsTable) setNonNegative(v *Value) {
return ft.signedMin(v, 0) ft.signedMin(v, 0)
} }
// unsignedMin records the fact that we know v is at least // unsignedMin records the fact that we know v is at least
// min in the unsigned domain. // min in the unsigned domain.
func (ft *factsTable) unsignedMin(v *Value, min uint64) bool { func (ft *factsTable) unsignedMin(v *Value, min uint64) {
return ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: min, umax: math.MaxUint64}) ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: min, umax: math.MaxUint64})
} }
// unsignedMax records the fact that we know v is at most // unsignedMax records the fact that we know v is at most
// max in the unsigned domain. // max in the unsigned domain.
func (ft *factsTable) unsignedMax(v *Value, max uint64) bool { func (ft *factsTable) unsignedMax(v *Value, max uint64) {
return ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: 0, umax: max}) ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: 0, umax: max})
} }
func (ft *factsTable) unsignedMinMax(v *Value, min, max uint64) bool { func (ft *factsTable) unsignedMinMax(v *Value, min, max uint64) {
return ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: min, umax: max}) ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: min, umax: max})
} }
func (ft *factsTable) booleanFalse(v *Value) bool { func (ft *factsTable) booleanFalse(v *Value) {
return ft.newLimit(v, limit{min: 0, max: 0, umin: 0, umax: 0}) ft.newLimit(v, limit{min: 0, max: 0, umin: 0, umax: 0})
} }
func (ft *factsTable) booleanTrue(v *Value) bool { func (ft *factsTable) booleanTrue(v *Value) {
return ft.newLimit(v, limit{min: 1, max: 1, umin: 1, umax: 1}) ft.newLimit(v, limit{min: 1, max: 1, umin: 1, umax: 1})
} }
func (ft *factsTable) pointerNil(v *Value) bool { func (ft *factsTable) pointerNil(v *Value) {
return ft.newLimit(v, limit{min: 0, max: 0, umin: 0, umax: 0}) ft.newLimit(v, limit{min: 0, max: 0, umin: 0, umax: 0})
} }
func (ft *factsTable) pointerNonNil(v *Value) bool { func (ft *factsTable) pointerNonNil(v *Value) {
l := noLimit l := noLimit
l.umin = 1 l.umin = 1
return ft.newLimit(v, l) ft.newLimit(v, l)
} }
// newLimit adds new limiting information for v. // newLimit adds new limiting information for v.
// Returns true if the new limit added any new information. func (ft *factsTable) newLimit(v *Value, newLim limit) {
func (ft *factsTable) newLimit(v *Value, newLim limit) bool {
oldLim := ft.limits[v.ID] oldLim := ft.limits[v.ID]
// Merge old and new information. // Merge old and new information.
@ -531,13 +530,12 @@ func (ft *factsTable) newLimit(v *Value, newLim limit) bool {
} }
if lim == oldLim { if lim == oldLim {
return false // nothing new to record return // nothing new to record
} }
if lim.unsat() { if lim.unsat() {
r := !ft.unsat
ft.unsat = true ft.unsat = true
return r return
} }
// Check for recursion. This normally happens because in unsatisfiable // Check for recursion. This normally happens because in unsatisfiable
@ -548,7 +546,7 @@ func (ft *factsTable) newLimit(v *Value, newLim limit) bool {
// the posets will not notice. // the posets will not notice.
if ft.recurseCheck[v.ID] { if ft.recurseCheck[v.ID] {
// This should only happen for unsatisfiable cases. TODO: check // This should only happen for unsatisfiable cases. TODO: check
return false return
} }
ft.recurseCheck[v.ID] = true ft.recurseCheck[v.ID] = true
defer func() { defer func() {
@ -713,8 +711,6 @@ func (ft *factsTable) newLimit(v *Value, newLim limit) bool {
} }
} }
} }
return true
} }
func (ft *factsTable) addOrdering(v, w *Value, d domain, r relation) { func (ft *factsTable) addOrdering(v, w *Value, d domain, r relation) {
@ -1825,7 +1821,7 @@ func initLimit(v *Value) limit {
return lim return lim
} }
// flowLimit updates the known limits of v in ft. Returns true if anything changed. // flowLimit updates the known limits of v in ft.
// flowLimit can use the ranges of input arguments. // flowLimit can use the ranges of input arguments.
// //
// Note: this calculation only happens at the point the value is defined. We do not reevaluate // Note: this calculation only happens at the point the value is defined. We do not reevaluate
@ -1838,10 +1834,10 @@ func initLimit(v *Value) limit {
// block. We could recompute the range of v once we enter the block so // block. We could recompute the range of v once we enter the block so
// we know that it is 0 <= v <= 8, but we don't have a mechanism to do // we know that it is 0 <= v <= 8, but we don't have a mechanism to do
// that right now. // that right now.
func (ft *factsTable) flowLimit(v *Value) bool { func (ft *factsTable) flowLimit(v *Value) {
if !v.Type.IsInteger() { if !v.Type.IsInteger() {
// TODO: boolean? // TODO: boolean?
return false return
} }
// Additional limits based on opcode and argument. // Additional limits based on opcode and argument.
@ -1851,36 +1847,36 @@ func (ft *factsTable) flowLimit(v *Value) bool {
// extensions // extensions
case OpZeroExt8to64, OpZeroExt8to32, OpZeroExt8to16, OpZeroExt16to64, OpZeroExt16to32, OpZeroExt32to64: case OpZeroExt8to64, OpZeroExt8to32, OpZeroExt8to16, OpZeroExt16to64, OpZeroExt16to32, OpZeroExt32to64:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
return ft.unsignedMinMax(v, a.umin, a.umax) ft.unsignedMinMax(v, a.umin, a.umax)
case OpSignExt8to64, OpSignExt8to32, OpSignExt8to16, OpSignExt16to64, OpSignExt16to32, OpSignExt32to64: case OpSignExt8to64, OpSignExt8to32, OpSignExt8to16, OpSignExt16to64, OpSignExt16to32, OpSignExt32to64:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
return ft.signedMinMax(v, a.min, a.max) ft.signedMinMax(v, a.min, a.max)
case OpTrunc64to8, OpTrunc64to16, OpTrunc64to32, OpTrunc32to8, OpTrunc32to16, OpTrunc16to8: case OpTrunc64to8, OpTrunc64to16, OpTrunc64to32, OpTrunc32to8, OpTrunc32to16, OpTrunc16to8:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
if a.umax <= 1<<(uint64(v.Type.Size())*8)-1 { if a.umax <= 1<<(uint64(v.Type.Size())*8)-1 {
return ft.unsignedMinMax(v, a.umin, a.umax) ft.unsignedMinMax(v, a.umin, a.umax)
} }
// math/bits // math/bits
case OpCtz64: case OpCtz64:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
if a.nonzero() { if a.nonzero() {
return ft.unsignedMax(v, uint64(bits.Len64(a.umax)-1)) ft.unsignedMax(v, uint64(bits.Len64(a.umax)-1))
} }
case OpCtz32: case OpCtz32:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
if a.nonzero() { if a.nonzero() {
return ft.unsignedMax(v, uint64(bits.Len32(uint32(a.umax))-1)) ft.unsignedMax(v, uint64(bits.Len32(uint32(a.umax))-1))
} }
case OpCtz16: case OpCtz16:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
if a.nonzero() { if a.nonzero() {
return ft.unsignedMax(v, uint64(bits.Len16(uint16(a.umax))-1)) ft.unsignedMax(v, uint64(bits.Len16(uint16(a.umax))-1))
} }
case OpCtz8: case OpCtz8:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
if a.nonzero() { if a.nonzero() {
return ft.unsignedMax(v, uint64(bits.Len8(uint8(a.umax))-1)) ft.unsignedMax(v, uint64(bits.Len8(uint8(a.umax))-1))
} }
case OpPopCount64, OpPopCount32, OpPopCount16, OpPopCount8: case OpPopCount64, OpPopCount32, OpPopCount16, OpPopCount8:
@ -1889,26 +1885,26 @@ func (ft *factsTable) flowLimit(v *Value) bool {
sharedLeadingMask := ^(uint64(1)<<changingBitsCount - 1) sharedLeadingMask := ^(uint64(1)<<changingBitsCount - 1)
fixedBits := a.umax & sharedLeadingMask fixedBits := a.umax & sharedLeadingMask
min := uint64(bits.OnesCount64(fixedBits)) min := uint64(bits.OnesCount64(fixedBits))
return ft.unsignedMinMax(v, min, min+changingBitsCount) ft.unsignedMinMax(v, min, min+changingBitsCount)
case OpBitLen64: case OpBitLen64:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
return ft.unsignedMinMax(v, ft.unsignedMinMax(v,
uint64(bits.Len64(a.umin)), uint64(bits.Len64(a.umin)),
uint64(bits.Len64(a.umax))) uint64(bits.Len64(a.umax)))
case OpBitLen32: case OpBitLen32:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
return ft.unsignedMinMax(v, ft.unsignedMinMax(v,
uint64(bits.Len32(uint32(a.umin))), uint64(bits.Len32(uint32(a.umin))),
uint64(bits.Len32(uint32(a.umax)))) uint64(bits.Len32(uint32(a.umax))))
case OpBitLen16: case OpBitLen16:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
return ft.unsignedMinMax(v, ft.unsignedMinMax(v,
uint64(bits.Len16(uint16(a.umin))), uint64(bits.Len16(uint16(a.umin))),
uint64(bits.Len16(uint16(a.umax)))) uint64(bits.Len16(uint16(a.umax))))
case OpBitLen8: case OpBitLen8:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
return ft.unsignedMinMax(v, ft.unsignedMinMax(v,
uint64(bits.Len8(uint8(a.umin))), uint64(bits.Len8(uint8(a.umin))),
uint64(bits.Len8(uint8(a.umax)))) uint64(bits.Len8(uint8(a.umax))))
@ -1921,43 +1917,43 @@ func (ft *factsTable) flowLimit(v *Value) bool {
// AND can only make the value smaller. // AND can only make the value smaller.
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID] b := ft.limits[v.Args[1].ID]
return ft.unsignedMax(v, min(a.umax, b.umax)) ft.unsignedMax(v, min(a.umax, b.umax))
case OpOr64, OpOr32, OpOr16, OpOr8: case OpOr64, OpOr32, OpOr16, OpOr8:
// OR can only make the value bigger and can't flip bits proved to be zero in both inputs. // OR can only make the value bigger and can't flip bits proved to be zero in both inputs.
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID] b := ft.limits[v.Args[1].ID]
return ft.unsignedMinMax(v, ft.unsignedMinMax(v,
max(a.umin, b.umin), max(a.umin, b.umin),
1<<bits.Len64(a.umax|b.umax)-1) 1<<bits.Len64(a.umax|b.umax)-1)
case OpXor64, OpXor32, OpXor16, OpXor8: case OpXor64, OpXor32, OpXor16, OpXor8:
// XOR can't flip bits that are proved to be zero in both inputs. // XOR can't flip bits that are proved to be zero in both inputs.
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID] b := ft.limits[v.Args[1].ID]
return ft.unsignedMax(v, 1<<bits.Len64(a.umax|b.umax)-1) ft.unsignedMax(v, 1<<bits.Len64(a.umax|b.umax)-1)
case OpCom64, OpCom32, OpCom16, OpCom8: case OpCom64, OpCom32, OpCom16, OpCom8:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
return ft.newLimit(v, a.com(uint(v.Type.Size())*8)) ft.newLimit(v, a.com(uint(v.Type.Size())*8))
// Arithmetic. // Arithmetic.
case OpAdd64, OpAdd32, OpAdd16, OpAdd8: case OpAdd64, OpAdd32, OpAdd16, OpAdd8:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID] b := ft.limits[v.Args[1].ID]
return ft.newLimit(v, a.add(b, uint(v.Type.Size())*8)) ft.newLimit(v, a.add(b, uint(v.Type.Size())*8))
case OpSub64, OpSub32, OpSub16, OpSub8: case OpSub64, OpSub32, OpSub16, OpSub8:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID] b := ft.limits[v.Args[1].ID]
sub := ft.newLimit(v, a.sub(b, uint(v.Type.Size())*8)) ft.newLimit(v, a.sub(b, uint(v.Type.Size())*8))
mod := ft.detectMod(v) ft.detectMod(v)
inferred := ft.detectSliceLenRelation(v) ft.detectSliceLenRelation(v)
return sub || mod || inferred ft.detectSubRelations(v)
case OpNeg64, OpNeg32, OpNeg16, OpNeg8: case OpNeg64, OpNeg32, OpNeg16, OpNeg8:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
bitsize := uint(v.Type.Size()) * 8 bitsize := uint(v.Type.Size()) * 8
return ft.newLimit(v, a.com(bitsize).add(limit{min: 1, max: 1, umin: 1, umax: 1}, bitsize)) ft.newLimit(v, a.com(bitsize).add(limit{min: 1, max: 1, umin: 1, umax: 1}, bitsize))
case OpMul64, OpMul32, OpMul16, OpMul8: case OpMul64, OpMul32, OpMul16, OpMul8:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID] b := ft.limits[v.Args[1].ID]
return ft.newLimit(v, a.mul(b, uint(v.Type.Size())*8)) ft.newLimit(v, a.mul(b, uint(v.Type.Size())*8))
case OpLsh64x64, OpLsh64x32, OpLsh64x16, OpLsh64x8, case OpLsh64x64, OpLsh64x32, OpLsh64x16, OpLsh64x8,
OpLsh32x64, OpLsh32x32, OpLsh32x16, OpLsh32x8, OpLsh32x64, OpLsh32x32, OpLsh32x16, OpLsh32x8,
OpLsh16x64, OpLsh16x32, OpLsh16x16, OpLsh16x8, OpLsh16x64, OpLsh16x32, OpLsh16x16, OpLsh16x8,
@ -1965,7 +1961,7 @@ func (ft *factsTable) flowLimit(v *Value) bool {
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID] b := ft.limits[v.Args[1].ID]
bitsize := uint(v.Type.Size()) * 8 bitsize := uint(v.Type.Size()) * 8
return ft.newLimit(v, a.mul(b.exp2(bitsize), bitsize)) ft.newLimit(v, a.mul(b.exp2(bitsize), bitsize))
case OpRsh64x64, OpRsh64x32, OpRsh64x16, OpRsh64x8, case OpRsh64x64, OpRsh64x32, OpRsh64x16, OpRsh64x8,
OpRsh32x64, OpRsh32x32, OpRsh32x16, OpRsh32x8, OpRsh32x64, OpRsh32x32, OpRsh32x16, OpRsh32x8,
OpRsh16x64, OpRsh16x32, OpRsh16x16, OpRsh16x8, OpRsh16x64, OpRsh16x32, OpRsh16x16, OpRsh16x8,
@ -1979,7 +1975,7 @@ func (ft *factsTable) flowLimit(v *Value) bool {
// Easier to compute min and max of both than to write sign logic. // Easier to compute min and max of both than to write sign logic.
vmin := min(a.min>>b.min, a.min>>b.max) vmin := min(a.min>>b.min, a.min>>b.max)
vmax := max(a.max>>b.min, a.max>>b.max) vmax := max(a.max>>b.min, a.max>>b.max)
return ft.signedMinMax(v, vmin, vmax) ft.signedMinMax(v, vmin, vmax)
} }
case OpRsh64Ux64, OpRsh64Ux32, OpRsh64Ux16, OpRsh64Ux8, case OpRsh64Ux64, OpRsh64Ux32, OpRsh64Ux16, OpRsh64Ux8,
OpRsh32Ux64, OpRsh32Ux32, OpRsh32Ux16, OpRsh32Ux8, OpRsh32Ux64, OpRsh32Ux32, OpRsh32Ux16, OpRsh32Ux8,
@ -1988,7 +1984,7 @@ func (ft *factsTable) flowLimit(v *Value) bool {
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID] b := ft.limits[v.Args[1].ID]
if b.min >= 0 { if b.min >= 0 {
return ft.unsignedMinMax(v, a.umin>>b.max, a.umax>>b.min) ft.unsignedMinMax(v, a.umin>>b.max, a.umax>>b.min)
} }
case OpDiv64, OpDiv32, OpDiv16, OpDiv8: case OpDiv64, OpDiv32, OpDiv16, OpDiv8:
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
@ -2008,11 +2004,11 @@ func (ft *factsTable) flowLimit(v *Value) bool {
if b.umin > 0 { if b.umin > 0 {
lim = lim.unsignedMax(a.umax / b.umin) lim = lim.unsignedMax(a.umax / b.umin)
} }
return ft.newLimit(v, lim) ft.newLimit(v, lim)
case OpMod64, OpMod32, OpMod16, OpMod8: case OpMod64, OpMod32, OpMod16, OpMod8:
return ft.modLimit(true, v, v.Args[0], v.Args[1]) ft.modLimit(true, v, v.Args[0], v.Args[1])
case OpMod64u, OpMod32u, OpMod16u, OpMod8u: case OpMod64u, OpMod32u, OpMod16u, OpMod8u:
return ft.modLimit(false, v, v.Args[0], v.Args[1]) ft.modLimit(false, v, v.Args[0], v.Args[1])
case OpPhi: case OpPhi:
// Compute the union of all the input phis. // Compute the union of all the input phis.
@ -2032,9 +2028,8 @@ func (ft *factsTable) flowLimit(v *Value) bool {
l.umin = min(l.umin, l2.umin) l.umin = min(l.umin, l2.umin)
l.umax = max(l.umax, l2.umax) l.umax = max(l.umax, l2.umax)
} }
return ft.newLimit(v, l) ft.newLimit(v, l)
} }
return false
} }
// detectSliceLenRelation matches the pattern where // detectSliceLenRelation matches the pattern where
@ -2047,13 +2042,13 @@ func (ft *factsTable) flowLimit(v *Value) bool {
// //
// Note that "index" is not useed for indexing in this pattern, but // Note that "index" is not useed for indexing in this pattern, but
// in the motivating example (chunked slice iteration) it is. // in the motivating example (chunked slice iteration) it is.
func (ft *factsTable) detectSliceLenRelation(v *Value) (inferred bool) { func (ft *factsTable) detectSliceLenRelation(v *Value) {
if v.Op != OpSub64 { if v.Op != OpSub64 {
return false return
} }
if !(v.Args[0].Op == OpSliceLen || v.Args[0].Op == OpSliceCap) { if !(v.Args[0].Op == OpSliceLen || v.Args[0].Op == OpSliceCap) {
return false return
} }
slice := v.Args[0].Args[0] slice := v.Args[0].Args[0]
@ -2093,13 +2088,54 @@ func (ft *factsTable) detectSliceLenRelation(v *Value) (inferred bool) {
if K < 0 { // We hate thinking about overflow if K < 0 { // We hate thinking about overflow
continue continue
} }
inferred = inferred || ft.signedMin(v, K) ft.signedMin(v, K)
}
}
// v must be Sub{64,32,16,8}.
func (ft *factsTable) detectSubRelations(v *Value) {
// v = x-y
x := v.Args[0]
y := v.Args[1]
if x == y {
ft.signedMinMax(v, 0, 0)
return
}
xLim := ft.limits[x.ID]
yLim := ft.limits[y.ID]
// Check if we might wrap around. If so, give up.
width := uint(v.Type.Size()) * 8
if _, ok := safeSub(xLim.min, yLim.max, width); !ok {
return // x-y might underflow
}
if _, ok := safeSub(xLim.max, yLim.min, width); !ok {
return // x-y might overflow
}
// Subtracting a positive number only makes
// things smaller.
if yLim.min >= 0 {
ft.update(v.Block, v, x, signed, lt|eq)
// TODO: is this worth it?
//if yLim.min > 0 {
// ft.update(v.Block, v, x, signed, lt)
//}
}
// Subtracting a number from a bigger one
// can't go below 0.
if ft.orderS.OrderedOrEqual(y, x) {
ft.setNonNegative(v)
// TODO: is this worth it?
//if ft.orderS.Ordered(y, x) {
// ft.signedMin(v, 1)
//}
} }
return inferred
} }
// x%d has been rewritten to x - (x/d)*d. // x%d has been rewritten to x - (x/d)*d.
func (ft *factsTable) detectMod(v *Value) bool { func (ft *factsTable) detectMod(v *Value) {
var opDiv, opDivU, opMul, opConst Op var opDiv, opDivU, opMul, opConst Op
switch v.Op { switch v.Op {
case OpSub64: case OpSub64:
@ -2126,36 +2162,37 @@ func (ft *factsTable) detectMod(v *Value) bool {
mul := v.Args[1] mul := v.Args[1]
if mul.Op != opMul { if mul.Op != opMul {
return false return
} }
div, con := mul.Args[0], mul.Args[1] div, con := mul.Args[0], mul.Args[1]
if div.Op == opConst { if div.Op == opConst {
div, con = con, div div, con = con, div
} }
if con.Op != opConst || (div.Op != opDiv && div.Op != opDivU) || div.Args[0] != v.Args[0] || div.Args[1].Op != opConst || div.Args[1].AuxInt != con.AuxInt { if con.Op != opConst || (div.Op != opDiv && div.Op != opDivU) || div.Args[0] != v.Args[0] || div.Args[1].Op != opConst || div.Args[1].AuxInt != con.AuxInt {
return false return
} }
return ft.modLimit(div.Op == opDiv, v, v.Args[0], con) ft.modLimit(div.Op == opDiv, v, v.Args[0], con)
} }
// modLimit sets v with facts derived from v = p % q. // modLimit sets v with facts derived from v = p % q.
func (ft *factsTable) modLimit(signed bool, v, p, q *Value) bool { func (ft *factsTable) modLimit(signed bool, v, p, q *Value) {
a := ft.limits[p.ID] a := ft.limits[p.ID]
b := ft.limits[q.ID] b := ft.limits[q.ID]
if signed { if signed {
if a.min < 0 && b.min > 0 { if a.min < 0 && b.min > 0 {
return ft.signedMinMax(v, -(b.max - 1), b.max-1) ft.signedMinMax(v, -(b.max - 1), b.max-1)
return
} }
if !(a.nonnegative() && b.nonnegative()) { if !(a.nonnegative() && b.nonnegative()) {
// TODO: we could handle signed limits but I didn't bother. // TODO: we could handle signed limits but I didn't bother.
return false return
} }
if a.min >= 0 && b.min > 0 { if a.min >= 0 && b.min > 0 {
ft.setNonNegative(v) ft.setNonNegative(v)
} }
} }
// Underflow in the arithmetic below is ok, it gives to MaxUint64 which does nothing to the limit. // Underflow in the arithmetic below is ok, it gives to MaxUint64 which does nothing to the limit.
return ft.unsignedMax(v, min(a.umax, b.umax-1)) ft.unsignedMax(v, min(a.umax, b.umax-1))
} }
// getBranch returns the range restrictions added by p // getBranch returns the range restrictions added by p
@ -2466,10 +2503,7 @@ func addLocalFacts(ft *factsTable, b *Block) {
xl := ft.limits[x.ID] xl := ft.limits[x.ID]
y := add.Args[1] y := add.Args[1]
yl := ft.limits[y.ID] yl := ft.limits[y.ID]
if unsignedAddOverflows(xl.umax, yl.umax, add.Type) { if !unsignedAddOverflows(xl.umax, yl.umax, add.Type) {
continue
}
if xl.umax < uminDivisor { if xl.umax < uminDivisor {
ft.update(b, v, y, unsigned, lt|eq) ft.update(b, v, y, unsigned, lt|eq)
} }
@ -2477,6 +2511,7 @@ func addLocalFacts(ft *factsTable, b *Block) {
ft.update(b, v, x, unsigned, lt|eq) ft.update(b, v, x, unsigned, lt|eq)
} }
} }
}
ft.update(b, v, v.Args[0], unsigned, lt|eq) ft.update(b, v, v.Args[0], unsigned, lt|eq)
case OpMod64, OpMod32, OpMod16, OpMod8: case OpMod64, OpMod32, OpMod16, OpMod8:
if !ft.isNonNegative(v.Args[0]) || !ft.isNonNegative(v.Args[1]) { if !ft.isNonNegative(v.Args[0]) || !ft.isNonNegative(v.Args[1]) {
@ -2993,7 +3028,6 @@ func (ft *factsTable) topoSortValuesInBlock(b *Block) {
want := f.NumValues() want := f.NumValues()
scores := ft.reusedTopoSortScoresTable scores := ft.reusedTopoSortScoresTable
if len(scores) < want {
if want <= cap(scores) { if want <= cap(scores) {
scores = scores[:want] scores = scores[:want]
} else { } else {
@ -3003,7 +3037,6 @@ func (ft *factsTable) topoSortValuesInBlock(b *Block) {
scores = f.Cache.allocUintSlice(want) scores = f.Cache.allocUintSlice(want)
ft.reusedTopoSortScoresTable = scores ft.reusedTopoSortScoresTable = scores
} }
}
for _, v := range b.Values { for _, v := range b.Values {
scores[v.ID] = 0 // sentinel scores[v.ID] = 0 // sentinel

View file

@ -596,17 +596,18 @@ func (s *regAllocState) allocValToReg(v *Value, mask regMask, nospill bool, pos
var c *Value var c *Value
if vi.regs != 0 { if vi.regs != 0 {
// Copy from a register that v is already in. // Copy from a register that v is already in.
r2 := pickReg(vi.regs)
var current *Value var current *Value
if !s.allocatable.contains(r2) { if vi.regs&^s.allocatable != 0 {
current = v // v is in a fixed register // v is in a fixed register, prefer that
current = v
} else { } else {
r2 := pickReg(vi.regs)
if s.regs[r2].v != v { if s.regs[r2].v != v {
panic("bad register state") panic("bad register state")
} }
current = s.regs[r2].c current = s.regs[r2].c
}
s.usedSinceBlockStart |= regMask(1) << r2 s.usedSinceBlockStart |= regMask(1) << r2
}
c = s.curBlock.NewValue1(pos, OpCopy, v.Type, current) c = s.curBlock.NewValue1(pos, OpCopy, v.Type, current)
} else if v.rematerializeable() { } else if v.rematerializeable() {
// Rematerialize instead of loading from the spill location. // Rematerialize instead of loading from the spill location.

View file

@ -2772,3 +2772,17 @@ func panicBoundsCCToAux(p PanicBoundsCC) Aux {
func isDictArgSym(sym Sym) bool { func isDictArgSym(sym Sym) bool {
return sym.(*ir.Name).Sym().Name == typecheck.LocalDictName return sym.(*ir.Name).Sym().Name == typecheck.LocalDictName
} }
// When v is (IMake typ (StructMake ...)), convert to
// (IMake typ arg) where arg is the pointer-y argument to
// the StructMake (there must be exactly one).
func imakeOfStructMake(v *Value) *Value {
var arg *Value
for _, a := range v.Args[1].Args {
if a.Type.Size() > 0 {
arg = a
break
}
}
return v.Block.NewValue2(v.Pos, OpIMake, v.Type, v.Args[0], arg)
}

View file

@ -12556,6 +12556,54 @@ func rewriteValueARM64_OpARM64MUL(v *Value) bool {
} }
break break
} }
// match: (MUL r:(MOVWUreg x) s:(MOVWUreg y))
// cond: r.Uses == 1 && s.Uses == 1
// result: (UMULL x y)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
r := v_0
if r.Op != OpARM64MOVWUreg {
continue
}
x := r.Args[0]
s := v_1
if s.Op != OpARM64MOVWUreg {
continue
}
y := s.Args[0]
if !(r.Uses == 1 && s.Uses == 1) {
continue
}
v.reset(OpARM64UMULL)
v.AddArg2(x, y)
return true
}
break
}
// match: (MUL r:(MOVWreg x) s:(MOVWreg y))
// cond: r.Uses == 1 && s.Uses == 1
// result: (MULL x y)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
r := v_0
if r.Op != OpARM64MOVWreg {
continue
}
x := r.Args[0]
s := v_1
if s.Op != OpARM64MOVWreg {
continue
}
y := s.Args[0]
if !(r.Uses == 1 && s.Uses == 1) {
continue
}
v.reset(OpARM64MULL)
v.AddArg2(x, y)
return true
}
break
}
return false return false
} }
func rewriteValueARM64_OpARM64MULW(v *Value) bool { func rewriteValueARM64_OpARM64MULW(v *Value) bool {
@ -25273,6 +25321,37 @@ func rewriteBlockARM64(b *Block) bool {
b.resetWithControl(BlockARM64FGE, cc) b.resetWithControl(BlockARM64FGE, cc)
return true return true
} }
// match: (TBNZ [0] (XORconst [1] x) yes no)
// result: (TBZ [0] x yes no)
for b.Controls[0].Op == OpARM64XORconst {
v_0 := b.Controls[0]
if auxIntToInt64(v_0.AuxInt) != 1 {
break
}
x := v_0.Args[0]
if auxIntToInt64(b.AuxInt) != 0 {
break
}
b.resetWithControl(BlockARM64TBZ, x)
b.AuxInt = int64ToAuxInt(0)
return true
}
case BlockARM64TBZ:
// match: (TBZ [0] (XORconst [1] x) yes no)
// result: (TBNZ [0] x yes no)
for b.Controls[0].Op == OpARM64XORconst {
v_0 := b.Controls[0]
if auxIntToInt64(v_0.AuxInt) != 1 {
break
}
x := v_0.Args[0]
if auxIntToInt64(b.AuxInt) != 0 {
break
}
b.resetWithControl(BlockARM64TBNZ, x)
b.AuxInt = int64ToAuxInt(0)
return true
}
case BlockARM64UGE: case BlockARM64UGE:
// match: (UGE (FlagConstant [fc]) yes no) // match: (UGE (FlagConstant [fc]) yes no)
// cond: fc.uge() // cond: fc.uge()

View file

@ -5866,7 +5866,6 @@ func rewriteValueLOONG64_OpLOONG64MULV(v *Value) bool {
v_0 := v.Args[0] v_0 := v.Args[0]
b := v.Block b := v.Block
config := b.Func.Config config := b.Func.Config
typ := &b.Func.Config.Types
// match: (MULV _ (MOVVconst [0])) // match: (MULV _ (MOVVconst [0]))
// result: (MOVVconst [0]) // result: (MOVVconst [0])
for { for {
@ -5911,44 +5910,6 @@ func rewriteValueLOONG64_OpLOONG64MULV(v *Value) bool {
} }
break break
} }
// match: (MULV (NEGV x) (MOVVconst [c]))
// result: (MULV x (MOVVconst [-c]))
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpLOONG64NEGV {
continue
}
x := v_0.Args[0]
if v_1.Op != OpLOONG64MOVVconst {
continue
}
c := auxIntToInt64(v_1.AuxInt)
v.reset(OpLOONG64MULV)
v0 := b.NewValue0(v.Pos, OpLOONG64MOVVconst, typ.UInt64)
v0.AuxInt = int64ToAuxInt(-c)
v.AddArg2(x, v0)
return true
}
break
}
// match: (MULV (NEGV x) (NEGV y))
// result: (MULV x y)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpLOONG64NEGV {
continue
}
x := v_0.Args[0]
if v_1.Op != OpLOONG64NEGV {
continue
}
y := v_1.Args[0]
v.reset(OpLOONG64MULV)
v.AddArg2(x, y)
return true
}
break
}
// match: (MULV (MOVVconst [c]) (MOVVconst [d])) // match: (MULV (MOVVconst [c]) (MOVVconst [d]))
// result: (MOVVconst [c*d]) // result: (MOVVconst [c*d])
for { for {

View file

@ -7027,7 +7027,7 @@ func rewriteValueRISCV64_OpRISCV64ROL(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (ROL x (MOVDconst [val])) // match: (ROL x (MOVDconst [val]))
// result: (RORI [int64(int8(-val)&63)] x) // result: (RORI [-val&63] x)
for { for {
x := v_0 x := v_0
if v_1.Op != OpRISCV64MOVDconst { if v_1.Op != OpRISCV64MOVDconst {
@ -7035,7 +7035,7 @@ func rewriteValueRISCV64_OpRISCV64ROL(v *Value) bool {
} }
val := auxIntToInt64(v_1.AuxInt) val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64RORI) v.reset(OpRISCV64RORI)
v.AuxInt = int64ToAuxInt(int64(int8(-val) & 63)) v.AuxInt = int64ToAuxInt(-val & 63)
v.AddArg(x) v.AddArg(x)
return true return true
} }
@ -7057,7 +7057,7 @@ func rewriteValueRISCV64_OpRISCV64ROLW(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (ROLW x (MOVDconst [val])) // match: (ROLW x (MOVDconst [val]))
// result: (RORIW [int64(int8(-val)&31)] x) // result: (RORIW [-val&31] x)
for { for {
x := v_0 x := v_0
if v_1.Op != OpRISCV64MOVDconst { if v_1.Op != OpRISCV64MOVDconst {
@ -7065,7 +7065,7 @@ func rewriteValueRISCV64_OpRISCV64ROLW(v *Value) bool {
} }
val := auxIntToInt64(v_1.AuxInt) val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64RORIW) v.reset(OpRISCV64RORIW)
v.AuxInt = int64ToAuxInt(int64(int8(-val) & 31)) v.AuxInt = int64ToAuxInt(-val & 31)
v.AddArg(x) v.AddArg(x)
return true return true
} }
@ -7087,7 +7087,7 @@ func rewriteValueRISCV64_OpRISCV64ROR(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (ROR x (MOVDconst [val])) // match: (ROR x (MOVDconst [val]))
// result: (RORI [int64(val&63)] x) // result: (RORI [val&63] x)
for { for {
x := v_0 x := v_0
if v_1.Op != OpRISCV64MOVDconst { if v_1.Op != OpRISCV64MOVDconst {
@ -7095,7 +7095,7 @@ func rewriteValueRISCV64_OpRISCV64ROR(v *Value) bool {
} }
val := auxIntToInt64(v_1.AuxInt) val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64RORI) v.reset(OpRISCV64RORI)
v.AuxInt = int64ToAuxInt(int64(val & 63)) v.AuxInt = int64ToAuxInt(val & 63)
v.AddArg(x) v.AddArg(x)
return true return true
} }
@ -7105,7 +7105,7 @@ func rewriteValueRISCV64_OpRISCV64RORW(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (RORW x (MOVDconst [val])) // match: (RORW x (MOVDconst [val]))
// result: (RORIW [int64(val&31)] x) // result: (RORIW [val&31] x)
for { for {
x := v_0 x := v_0
if v_1.Op != OpRISCV64MOVDconst { if v_1.Op != OpRISCV64MOVDconst {
@ -7113,7 +7113,7 @@ func rewriteValueRISCV64_OpRISCV64RORW(v *Value) bool {
} }
val := auxIntToInt64(v_1.AuxInt) val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64RORIW) v.reset(OpRISCV64RORIW)
v.AuxInt = int64ToAuxInt(int64(val & 31)) v.AuxInt = int64ToAuxInt(val & 31)
v.AddArg(x) v.AddArg(x)
return true return true
} }
@ -7212,7 +7212,7 @@ func rewriteValueRISCV64_OpRISCV64SLL(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (SLL x (MOVDconst [val])) // match: (SLL x (MOVDconst [val]))
// result: (SLLI [int64(val&63)] x) // result: (SLLI [val&63] x)
for { for {
x := v_0 x := v_0
if v_1.Op != OpRISCV64MOVDconst { if v_1.Op != OpRISCV64MOVDconst {
@ -7220,7 +7220,7 @@ func rewriteValueRISCV64_OpRISCV64SLL(v *Value) bool {
} }
val := auxIntToInt64(v_1.AuxInt) val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64SLLI) v.reset(OpRISCV64SLLI)
v.AuxInt = int64ToAuxInt(int64(val & 63)) v.AuxInt = int64ToAuxInt(val & 63)
v.AddArg(x) v.AddArg(x)
return true return true
} }
@ -7246,7 +7246,7 @@ func rewriteValueRISCV64_OpRISCV64SLLI(v *Value) bool {
} }
// match: (SLLI <t> [c] (ADD x x)) // match: (SLLI <t> [c] (ADD x x))
// cond: c < t.Size() * 8 - 1 // cond: c < t.Size() * 8 - 1
// result: (SLLI <t> [c+1] x) // result: (SLLI [c+1] x)
for { for {
t := v.Type t := v.Type
c := auxIntToInt64(v.AuxInt) c := auxIntToInt64(v.AuxInt)
@ -7258,7 +7258,6 @@ func rewriteValueRISCV64_OpRISCV64SLLI(v *Value) bool {
break break
} }
v.reset(OpRISCV64SLLI) v.reset(OpRISCV64SLLI)
v.Type = t
v.AuxInt = int64ToAuxInt(c + 1) v.AuxInt = int64ToAuxInt(c + 1)
v.AddArg(x) v.AddArg(x)
return true return true
@ -7286,7 +7285,7 @@ func rewriteValueRISCV64_OpRISCV64SLLW(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (SLLW x (MOVDconst [val])) // match: (SLLW x (MOVDconst [val]))
// result: (SLLIW [int64(val&31)] x) // result: (SLLIW [val&31] x)
for { for {
x := v_0 x := v_0
if v_1.Op != OpRISCV64MOVDconst { if v_1.Op != OpRISCV64MOVDconst {
@ -7294,7 +7293,7 @@ func rewriteValueRISCV64_OpRISCV64SLLW(v *Value) bool {
} }
val := auxIntToInt64(v_1.AuxInt) val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64SLLIW) v.reset(OpRISCV64SLLIW)
v.AuxInt = int64ToAuxInt(int64(val & 31)) v.AuxInt = int64ToAuxInt(val & 31)
v.AddArg(x) v.AddArg(x)
return true return true
} }
@ -7304,7 +7303,7 @@ func rewriteValueRISCV64_OpRISCV64SLT(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (SLT x (MOVDconst [val])) // match: (SLT x (MOVDconst [val]))
// cond: val >= -2048 && val <= 2047 // cond: is12Bit(val)
// result: (SLTI [val] x) // result: (SLTI [val] x)
for { for {
x := v_0 x := v_0
@ -7312,7 +7311,7 @@ func rewriteValueRISCV64_OpRISCV64SLT(v *Value) bool {
break break
} }
val := auxIntToInt64(v_1.AuxInt) val := auxIntToInt64(v_1.AuxInt)
if !(val >= -2048 && val <= 2047) { if !(is12Bit(val)) {
break break
} }
v.reset(OpRISCV64SLTI) v.reset(OpRISCV64SLTI)
@ -7363,22 +7362,6 @@ func rewriteValueRISCV64_OpRISCV64SLTI(v *Value) bool {
v.AuxInt = int64ToAuxInt(1) v.AuxInt = int64ToAuxInt(1)
return true return true
} }
// match: (SLTI [x] (ORI [y] _))
// cond: y >= 0 && int64(y) >= int64(x)
// result: (MOVDconst [0])
for {
x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64ORI {
break
}
y := auxIntToInt64(v_0.AuxInt)
if !(y >= 0 && int64(y) >= int64(x)) {
break
}
v.reset(OpRISCV64MOVDconst)
v.AuxInt = int64ToAuxInt(0)
return true
}
return false return false
} }
func rewriteValueRISCV64_OpRISCV64SLTIU(v *Value) bool { func rewriteValueRISCV64_OpRISCV64SLTIU(v *Value) bool {
@ -7433,7 +7416,7 @@ func rewriteValueRISCV64_OpRISCV64SLTU(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (SLTU x (MOVDconst [val])) // match: (SLTU x (MOVDconst [val]))
// cond: val >= -2048 && val <= 2047 // cond: is12Bit(val)
// result: (SLTIU [val] x) // result: (SLTIU [val] x)
for { for {
x := v_0 x := v_0
@ -7441,7 +7424,7 @@ func rewriteValueRISCV64_OpRISCV64SLTU(v *Value) bool {
break break
} }
val := auxIntToInt64(v_1.AuxInt) val := auxIntToInt64(v_1.AuxInt)
if !(val >= -2048 && val <= 2047) { if !(is12Bit(val)) {
break break
} }
v.reset(OpRISCV64SLTIU) v.reset(OpRISCV64SLTIU)
@ -7555,7 +7538,7 @@ func rewriteValueRISCV64_OpRISCV64SRA(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (SRA x (MOVDconst [val])) // match: (SRA x (MOVDconst [val]))
// result: (SRAI [int64(val&63)] x) // result: (SRAI [val&63] x)
for { for {
x := v_0 x := v_0
if v_1.Op != OpRISCV64MOVDconst { if v_1.Op != OpRISCV64MOVDconst {
@ -7563,7 +7546,7 @@ func rewriteValueRISCV64_OpRISCV64SRA(v *Value) bool {
} }
val := auxIntToInt64(v_1.AuxInt) val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64SRAI) v.reset(OpRISCV64SRAI)
v.AuxInt = int64ToAuxInt(int64(val & 63)) v.AuxInt = int64ToAuxInt(val & 63)
v.AddArg(x) v.AddArg(x)
return true return true
} }
@ -7572,11 +7555,10 @@ func rewriteValueRISCV64_OpRISCV64SRA(v *Value) bool {
func rewriteValueRISCV64_OpRISCV64SRAI(v *Value) bool { func rewriteValueRISCV64_OpRISCV64SRAI(v *Value) bool {
v_0 := v.Args[0] v_0 := v.Args[0]
b := v.Block b := v.Block
// match: (SRAI <t> [x] (MOVWreg y)) // match: (SRAI [x] (MOVWreg y))
// cond: x >= 0 && x <= 31 // cond: x >= 0 && x <= 31
// result: (SRAIW <t> [int64(x)] y) // result: (SRAIW [x] y)
for { for {
t := v.Type
x := auxIntToInt64(v.AuxInt) x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64MOVWreg { if v_0.Op != OpRISCV64MOVWreg {
break break
@ -7586,8 +7568,7 @@ func rewriteValueRISCV64_OpRISCV64SRAI(v *Value) bool {
break break
} }
v.reset(OpRISCV64SRAIW) v.reset(OpRISCV64SRAIW)
v.Type = t v.AuxInt = int64ToAuxInt(x)
v.AuxInt = int64ToAuxInt(int64(x))
v.AddArg(y) v.AddArg(y)
return true return true
} }
@ -7633,7 +7614,7 @@ func rewriteValueRISCV64_OpRISCV64SRAI(v *Value) bool {
v.AddArg(v0) v.AddArg(v0)
return true return true
} }
// match: (SRAI <t> [x] (MOVWreg y)) // match: (SRAI [x] (MOVWreg y))
// cond: x >= 32 // cond: x >= 32
// result: (SRAIW [31] y) // result: (SRAIW [31] y)
for { for {
@ -7668,7 +7649,7 @@ func rewriteValueRISCV64_OpRISCV64SRAW(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (SRAW x (MOVDconst [val])) // match: (SRAW x (MOVDconst [val]))
// result: (SRAIW [int64(val&31)] x) // result: (SRAIW [val&31] x)
for { for {
x := v_0 x := v_0
if v_1.Op != OpRISCV64MOVDconst { if v_1.Op != OpRISCV64MOVDconst {
@ -7676,7 +7657,7 @@ func rewriteValueRISCV64_OpRISCV64SRAW(v *Value) bool {
} }
val := auxIntToInt64(v_1.AuxInt) val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64SRAIW) v.reset(OpRISCV64SRAIW)
v.AuxInt = int64ToAuxInt(int64(val & 31)) v.AuxInt = int64ToAuxInt(val & 31)
v.AddArg(x) v.AddArg(x)
return true return true
} }
@ -7686,7 +7667,7 @@ func rewriteValueRISCV64_OpRISCV64SRL(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (SRL x (MOVDconst [val])) // match: (SRL x (MOVDconst [val]))
// result: (SRLI [int64(val&63)] x) // result: (SRLI [val&63] x)
for { for {
x := v_0 x := v_0
if v_1.Op != OpRISCV64MOVDconst { if v_1.Op != OpRISCV64MOVDconst {
@ -7694,7 +7675,7 @@ func rewriteValueRISCV64_OpRISCV64SRL(v *Value) bool {
} }
val := auxIntToInt64(v_1.AuxInt) val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64SRLI) v.reset(OpRISCV64SRLI)
v.AuxInt = int64ToAuxInt(int64(val & 63)) v.AuxInt = int64ToAuxInt(val & 63)
v.AddArg(x) v.AddArg(x)
return true return true
} }
@ -7702,11 +7683,10 @@ func rewriteValueRISCV64_OpRISCV64SRL(v *Value) bool {
} }
func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool { func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool {
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (SRLI <t> [x] (MOVWUreg y)) // match: (SRLI [x] (MOVWUreg y))
// cond: x >= 0 && x <= 31 // cond: x >= 0 && x <= 31
// result: (SRLIW <t> [int64(x)] y) // result: (SRLIW [x] y)
for { for {
t := v.Type
x := auxIntToInt64(v.AuxInt) x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64MOVWUreg { if v_0.Op != OpRISCV64MOVWUreg {
break break
@ -7716,16 +7696,14 @@ func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool {
break break
} }
v.reset(OpRISCV64SRLIW) v.reset(OpRISCV64SRLIW)
v.Type = t v.AuxInt = int64ToAuxInt(x)
v.AuxInt = int64ToAuxInt(int64(x))
v.AddArg(y) v.AddArg(y)
return true return true
} }
// match: (SRLI <t> [x] (MOVBUreg y)) // match: (SRLI [x] (MOVBUreg y))
// cond: x >= 8 // cond: x >= 8
// result: (MOVDconst <t> [0]) // result: (MOVDconst [0])
for { for {
t := v.Type
x := auxIntToInt64(v.AuxInt) x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64MOVBUreg { if v_0.Op != OpRISCV64MOVBUreg {
break break
@ -7734,15 +7712,13 @@ func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool {
break break
} }
v.reset(OpRISCV64MOVDconst) v.reset(OpRISCV64MOVDconst)
v.Type = t
v.AuxInt = int64ToAuxInt(0) v.AuxInt = int64ToAuxInt(0)
return true return true
} }
// match: (SRLI <t> [x] (MOVHUreg y)) // match: (SRLI [x] (MOVHUreg y))
// cond: x >= 16 // cond: x >= 16
// result: (MOVDconst <t> [0]) // result: (MOVDconst [0])
for { for {
t := v.Type
x := auxIntToInt64(v.AuxInt) x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64MOVHUreg { if v_0.Op != OpRISCV64MOVHUreg {
break break
@ -7751,15 +7727,13 @@ func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool {
break break
} }
v.reset(OpRISCV64MOVDconst) v.reset(OpRISCV64MOVDconst)
v.Type = t
v.AuxInt = int64ToAuxInt(0) v.AuxInt = int64ToAuxInt(0)
return true return true
} }
// match: (SRLI <t> [x] (MOVWUreg y)) // match: (SRLI [x] (MOVWUreg y))
// cond: x >= 32 // cond: x >= 32
// result: (MOVDconst <t> [0]) // result: (MOVDconst [0])
for { for {
t := v.Type
x := auxIntToInt64(v.AuxInt) x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64MOVWUreg { if v_0.Op != OpRISCV64MOVWUreg {
break break
@ -7768,7 +7742,6 @@ func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool {
break break
} }
v.reset(OpRISCV64MOVDconst) v.reset(OpRISCV64MOVDconst)
v.Type = t
v.AuxInt = int64ToAuxInt(0) v.AuxInt = int64ToAuxInt(0)
return true return true
} }
@ -7790,7 +7763,7 @@ func rewriteValueRISCV64_OpRISCV64SRLW(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (SRLW x (MOVDconst [val])) // match: (SRLW x (MOVDconst [val]))
// result: (SRLIW [int64(val&31)] x) // result: (SRLIW [val&31] x)
for { for {
x := v_0 x := v_0
if v_1.Op != OpRISCV64MOVDconst { if v_1.Op != OpRISCV64MOVDconst {
@ -7798,7 +7771,7 @@ func rewriteValueRISCV64_OpRISCV64SRLW(v *Value) bool {
} }
val := auxIntToInt64(v_1.AuxInt) val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64SRLIW) v.reset(OpRISCV64SRLIW)
v.AuxInt = int64ToAuxInt(int64(val & 31)) v.AuxInt = int64ToAuxInt(val & 31)
v.AddArg(x) v.AddArg(x)
return true return true
} }

View file

@ -279,11 +279,20 @@ func rewriteValuedec_OpIData(v *Value) bool {
func rewriteValuedec_OpIMake(v *Value) bool { func rewriteValuedec_OpIMake(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (IMake _typ (StructMake val)) // match: (IMake _typ (StructMake ___))
// result: imakeOfStructMake(v)
for {
if v_1.Op != OpStructMake {
break
}
v.copyOf(imakeOfStructMake(v))
return true
}
// match: (IMake _typ (ArrayMake1 val))
// result: (IMake _typ val) // result: (IMake _typ val)
for { for {
_typ := v_0 _typ := v_0
if v_1.Op != OpStructMake || len(v_1.Args) != 1 { if v_1.Op != OpArrayMake1 {
break break
} }
val := v_1.Args[0] val := v_1.Args[0]
@ -839,17 +848,47 @@ func rewriteValuedec_OpStructMake(v *Value) bool {
func rewriteValuedec_OpStructSelect(v *Value) bool { func rewriteValuedec_OpStructSelect(v *Value) bool {
v_0 := v.Args[0] v_0 := v.Args[0]
b := v.Block b := v.Block
// match: (StructSelect [0] (IData x)) // match: (StructSelect (IData x))
// cond: v.Type.Size() > 0
// result: (IData x) // result: (IData x)
for { for {
if auxIntToInt64(v.AuxInt) != 0 || v_0.Op != OpIData { if v_0.Op != OpIData {
break break
} }
x := v_0.Args[0] x := v_0.Args[0]
if !(v.Type.Size() > 0) {
break
}
v.reset(OpIData) v.reset(OpIData)
v.AddArg(x) v.AddArg(x)
return true return true
} }
// match: (StructSelect (IData x))
// cond: v.Type.Size() == 0 && v.Type.IsStruct()
// result: (StructMake)
for {
if v_0.Op != OpIData {
break
}
if !(v.Type.Size() == 0 && v.Type.IsStruct()) {
break
}
v.reset(OpStructMake)
return true
}
// match: (StructSelect (IData x))
// cond: v.Type.Size() == 0 && v.Type.IsArray()
// result: (ArrayMake0)
for {
if v_0.Op != OpIData {
break
}
if !(v.Type.Size() == 0 && v.Type.IsArray()) {
break
}
v.reset(OpArrayMake0)
return true
}
// match: (StructSelect [i] x:(StructMake ___)) // match: (StructSelect [i] x:(StructMake ___))
// result: x.Args[i] // result: x.Args[i]
for { for {
@ -861,13 +900,10 @@ func rewriteValuedec_OpStructSelect(v *Value) bool {
v.copyOf(x.Args[i]) v.copyOf(x.Args[i])
return true return true
} }
// match: (StructSelect [0] x) // match: (StructSelect x)
// cond: x.Type.IsPtrShaped() // cond: x.Type.IsPtrShaped()
// result: x // result: x
for { for {
if auxIntToInt64(v.AuxInt) != 0 {
break
}
x := v_0 x := v_0
if !(x.Type.IsPtrShaped()) { if !(x.Type.IsPtrShaped()) {
break break

View file

@ -5332,6 +5332,182 @@ func rewriteValuegeneric_OpAndB(v *Value) bool {
} }
break break
} }
// match: (AndB (Neq64 x cv:(Const64 [c])) (Neq64 x (Const64 [d])))
// cond: c|d == c && oneBit(c^d)
// result: (Neq64 (Or64 <x.Type> x (Const64 <x.Type> [c^d])) cv)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpNeq64 {
continue
}
_ = v_0.Args[1]
v_0_0 := v_0.Args[0]
v_0_1 := v_0.Args[1]
for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
x := v_0_0
cv := v_0_1
if cv.Op != OpConst64 {
continue
}
c := auxIntToInt64(cv.AuxInt)
if v_1.Op != OpNeq64 {
continue
}
_ = v_1.Args[1]
v_1_0 := v_1.Args[0]
v_1_1 := v_1.Args[1]
for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
if x != v_1_0 || v_1_1.Op != OpConst64 {
continue
}
d := auxIntToInt64(v_1_1.AuxInt)
if !(c|d == c && oneBit(c^d)) {
continue
}
v.reset(OpNeq64)
v0 := b.NewValue0(v.Pos, OpOr64, x.Type)
v1 := b.NewValue0(v.Pos, OpConst64, x.Type)
v1.AuxInt = int64ToAuxInt(c ^ d)
v0.AddArg2(x, v1)
v.AddArg2(v0, cv)
return true
}
}
}
break
}
// match: (AndB (Neq32 x cv:(Const32 [c])) (Neq32 x (Const32 [d])))
// cond: c|d == c && oneBit(c^d)
// result: (Neq32 (Or32 <x.Type> x (Const32 <x.Type> [c^d])) cv)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpNeq32 {
continue
}
_ = v_0.Args[1]
v_0_0 := v_0.Args[0]
v_0_1 := v_0.Args[1]
for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
x := v_0_0
cv := v_0_1
if cv.Op != OpConst32 {
continue
}
c := auxIntToInt32(cv.AuxInt)
if v_1.Op != OpNeq32 {
continue
}
_ = v_1.Args[1]
v_1_0 := v_1.Args[0]
v_1_1 := v_1.Args[1]
for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
if x != v_1_0 || v_1_1.Op != OpConst32 {
continue
}
d := auxIntToInt32(v_1_1.AuxInt)
if !(c|d == c && oneBit(c^d)) {
continue
}
v.reset(OpNeq32)
v0 := b.NewValue0(v.Pos, OpOr32, x.Type)
v1 := b.NewValue0(v.Pos, OpConst32, x.Type)
v1.AuxInt = int32ToAuxInt(c ^ d)
v0.AddArg2(x, v1)
v.AddArg2(v0, cv)
return true
}
}
}
break
}
// match: (AndB (Neq16 x cv:(Const16 [c])) (Neq16 x (Const16 [d])))
// cond: c|d == c && oneBit(c^d)
// result: (Neq16 (Or16 <x.Type> x (Const16 <x.Type> [c^d])) cv)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpNeq16 {
continue
}
_ = v_0.Args[1]
v_0_0 := v_0.Args[0]
v_0_1 := v_0.Args[1]
for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
x := v_0_0
cv := v_0_1
if cv.Op != OpConst16 {
continue
}
c := auxIntToInt16(cv.AuxInt)
if v_1.Op != OpNeq16 {
continue
}
_ = v_1.Args[1]
v_1_0 := v_1.Args[0]
v_1_1 := v_1.Args[1]
for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
if x != v_1_0 || v_1_1.Op != OpConst16 {
continue
}
d := auxIntToInt16(v_1_1.AuxInt)
if !(c|d == c && oneBit(c^d)) {
continue
}
v.reset(OpNeq16)
v0 := b.NewValue0(v.Pos, OpOr16, x.Type)
v1 := b.NewValue0(v.Pos, OpConst16, x.Type)
v1.AuxInt = int16ToAuxInt(c ^ d)
v0.AddArg2(x, v1)
v.AddArg2(v0, cv)
return true
}
}
}
break
}
// match: (AndB (Neq8 x cv:(Const8 [c])) (Neq8 x (Const8 [d])))
// cond: c|d == c && oneBit(c^d)
// result: (Neq8 (Or8 <x.Type> x (Const8 <x.Type> [c^d])) cv)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpNeq8 {
continue
}
_ = v_0.Args[1]
v_0_0 := v_0.Args[0]
v_0_1 := v_0.Args[1]
for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
x := v_0_0
cv := v_0_1
if cv.Op != OpConst8 {
continue
}
c := auxIntToInt8(cv.AuxInt)
if v_1.Op != OpNeq8 {
continue
}
_ = v_1.Args[1]
v_1_0 := v_1.Args[0]
v_1_1 := v_1.Args[1]
for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
if x != v_1_0 || v_1_1.Op != OpConst8 {
continue
}
d := auxIntToInt8(v_1_1.AuxInt)
if !(c|d == c && oneBit(c^d)) {
continue
}
v.reset(OpNeq8)
v0 := b.NewValue0(v.Pos, OpOr8, x.Type)
v1 := b.NewValue0(v.Pos, OpConst8, x.Type)
v1.AuxInt = int8ToAuxInt(c ^ d)
v0.AddArg2(x, v1)
v.AddArg2(v0, cv)
return true
}
}
}
break
}
return false return false
} }
func rewriteValuegeneric_OpArraySelect(v *Value) bool { func rewriteValuegeneric_OpArraySelect(v *Value) bool {
@ -8809,16 +8985,13 @@ func rewriteValuegeneric_OpFloor(v *Value) bool {
func rewriteValuegeneric_OpIMake(v *Value) bool { func rewriteValuegeneric_OpIMake(v *Value) bool {
v_1 := v.Args[1] v_1 := v.Args[1]
v_0 := v.Args[0] v_0 := v.Args[0]
// match: (IMake _typ (StructMake val)) // match: (IMake _typ (StructMake ___))
// result: (IMake _typ val) // result: imakeOfStructMake(v)
for { for {
_typ := v_0 if v_1.Op != OpStructMake {
if v_1.Op != OpStructMake || len(v_1.Args) != 1 {
break break
} }
val := v_1.Args[0] v.copyOf(imakeOfStructMake(v))
v.reset(OpIMake)
v.AddArg2(_typ, val)
return true return true
} }
// match: (IMake _typ (ArrayMake1 val)) // match: (IMake _typ (ArrayMake1 val))
@ -16610,6 +16783,45 @@ func rewriteValuegeneric_OpMul16(v *Value) bool {
} }
break break
} }
// match: (Mul16 (Const16 <t> [c]) (Neg16 x))
// result: (Mul16 x (Const16 <t> [-c]))
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpConst16 {
continue
}
t := v_0.Type
c := auxIntToInt16(v_0.AuxInt)
if v_1.Op != OpNeg16 {
continue
}
x := v_1.Args[0]
v.reset(OpMul16)
v0 := b.NewValue0(v.Pos, OpConst16, t)
v0.AuxInt = int16ToAuxInt(-c)
v.AddArg2(x, v0)
return true
}
break
}
// match: (Mul16 (Neg16 x) (Neg16 y))
// result: (Mul16 x y)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpNeg16 {
continue
}
x := v_0.Args[0]
if v_1.Op != OpNeg16 {
continue
}
y := v_1.Args[0]
v.reset(OpMul16)
v.AddArg2(x, y)
return true
}
break
}
// match: (Mul16 (Const16 <t> [c]) (Add16 <t> (Const16 <t> [d]) x)) // match: (Mul16 (Const16 <t> [c]) (Add16 <t> (Const16 <t> [d]) x))
// cond: !isPowerOfTwo(c) // cond: !isPowerOfTwo(c)
// result: (Add16 (Const16 <t> [c*d]) (Mul16 <t> (Const16 <t> [c]) x)) // result: (Add16 (Const16 <t> [c*d]) (Mul16 <t> (Const16 <t> [c]) x))
@ -16821,6 +17033,45 @@ func rewriteValuegeneric_OpMul32(v *Value) bool {
} }
break break
} }
// match: (Mul32 (Const32 <t> [c]) (Neg32 x))
// result: (Mul32 x (Const32 <t> [-c]))
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpConst32 {
continue
}
t := v_0.Type
c := auxIntToInt32(v_0.AuxInt)
if v_1.Op != OpNeg32 {
continue
}
x := v_1.Args[0]
v.reset(OpMul32)
v0 := b.NewValue0(v.Pos, OpConst32, t)
v0.AuxInt = int32ToAuxInt(-c)
v.AddArg2(x, v0)
return true
}
break
}
// match: (Mul32 (Neg32 x) (Neg32 y))
// result: (Mul32 x y)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpNeg32 {
continue
}
x := v_0.Args[0]
if v_1.Op != OpNeg32 {
continue
}
y := v_1.Args[0]
v.reset(OpMul32)
v.AddArg2(x, y)
return true
}
break
}
// match: (Mul32 (Const32 <t> [c]) (Add32 <t> (Const32 <t> [d]) x)) // match: (Mul32 (Const32 <t> [c]) (Add32 <t> (Const32 <t> [d]) x))
// cond: !isPowerOfTwo(c) // cond: !isPowerOfTwo(c)
// result: (Add32 (Const32 <t> [c*d]) (Mul32 <t> (Const32 <t> [c]) x)) // result: (Add32 (Const32 <t> [c*d]) (Mul32 <t> (Const32 <t> [c]) x))
@ -17193,6 +17444,45 @@ func rewriteValuegeneric_OpMul64(v *Value) bool {
} }
break break
} }
// match: (Mul64 (Const64 <t> [c]) (Neg64 x))
// result: (Mul64 x (Const64 <t> [-c]))
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpConst64 {
continue
}
t := v_0.Type
c := auxIntToInt64(v_0.AuxInt)
if v_1.Op != OpNeg64 {
continue
}
x := v_1.Args[0]
v.reset(OpMul64)
v0 := b.NewValue0(v.Pos, OpConst64, t)
v0.AuxInt = int64ToAuxInt(-c)
v.AddArg2(x, v0)
return true
}
break
}
// match: (Mul64 (Neg64 x) (Neg64 y))
// result: (Mul64 x y)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpNeg64 {
continue
}
x := v_0.Args[0]
if v_1.Op != OpNeg64 {
continue
}
y := v_1.Args[0]
v.reset(OpMul64)
v.AddArg2(x, y)
return true
}
break
}
// match: (Mul64 (Const64 <t> [c]) (Add64 <t> (Const64 <t> [d]) x)) // match: (Mul64 (Const64 <t> [c]) (Add64 <t> (Const64 <t> [d]) x))
// cond: !isPowerOfTwo(c) // cond: !isPowerOfTwo(c)
// result: (Add64 (Const64 <t> [c*d]) (Mul64 <t> (Const64 <t> [c]) x)) // result: (Add64 (Const64 <t> [c*d]) (Mul64 <t> (Const64 <t> [c]) x))
@ -17565,6 +17855,45 @@ func rewriteValuegeneric_OpMul8(v *Value) bool {
} }
break break
} }
// match: (Mul8 (Const8 <t> [c]) (Neg8 x))
// result: (Mul8 x (Const8 <t> [-c]))
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpConst8 {
continue
}
t := v_0.Type
c := auxIntToInt8(v_0.AuxInt)
if v_1.Op != OpNeg8 {
continue
}
x := v_1.Args[0]
v.reset(OpMul8)
v0 := b.NewValue0(v.Pos, OpConst8, t)
v0.AuxInt = int8ToAuxInt(-c)
v.AddArg2(x, v0)
return true
}
break
}
// match: (Mul8 (Neg8 x) (Neg8 y))
// result: (Mul8 x y)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpNeg8 {
continue
}
x := v_0.Args[0]
if v_1.Op != OpNeg8 {
continue
}
y := v_1.Args[0]
v.reset(OpMul8)
v.AddArg2(x, y)
return true
}
break
}
// match: (Mul8 (Const8 <t> [c]) (Add8 <t> (Const8 <t> [d]) x)) // match: (Mul8 (Const8 <t> [c]) (Add8 <t> (Const8 <t> [d]) x))
// cond: !isPowerOfTwo(c) // cond: !isPowerOfTwo(c)
// result: (Add8 (Const8 <t> [c*d]) (Mul8 <t> (Const8 <t> [c]) x)) // result: (Add8 (Const8 <t> [c*d]) (Mul8 <t> (Const8 <t> [c]) x))
@ -23242,6 +23571,182 @@ func rewriteValuegeneric_OpOrB(v *Value) bool {
} }
break break
} }
// match: (OrB (Eq64 x cv:(Const64 [c])) (Eq64 x (Const64 [d])))
// cond: c|d == c && oneBit(c^d)
// result: (Eq64 (Or64 <x.Type> x (Const64 <x.Type> [c^d])) cv)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpEq64 {
continue
}
_ = v_0.Args[1]
v_0_0 := v_0.Args[0]
v_0_1 := v_0.Args[1]
for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
x := v_0_0
cv := v_0_1
if cv.Op != OpConst64 {
continue
}
c := auxIntToInt64(cv.AuxInt)
if v_1.Op != OpEq64 {
continue
}
_ = v_1.Args[1]
v_1_0 := v_1.Args[0]
v_1_1 := v_1.Args[1]
for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
if x != v_1_0 || v_1_1.Op != OpConst64 {
continue
}
d := auxIntToInt64(v_1_1.AuxInt)
if !(c|d == c && oneBit(c^d)) {
continue
}
v.reset(OpEq64)
v0 := b.NewValue0(v.Pos, OpOr64, x.Type)
v1 := b.NewValue0(v.Pos, OpConst64, x.Type)
v1.AuxInt = int64ToAuxInt(c ^ d)
v0.AddArg2(x, v1)
v.AddArg2(v0, cv)
return true
}
}
}
break
}
// match: (OrB (Eq32 x cv:(Const32 [c])) (Eq32 x (Const32 [d])))
// cond: c|d == c && oneBit(c^d)
// result: (Eq32 (Or32 <x.Type> x (Const32 <x.Type> [c^d])) cv)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpEq32 {
continue
}
_ = v_0.Args[1]
v_0_0 := v_0.Args[0]
v_0_1 := v_0.Args[1]
for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
x := v_0_0
cv := v_0_1
if cv.Op != OpConst32 {
continue
}
c := auxIntToInt32(cv.AuxInt)
if v_1.Op != OpEq32 {
continue
}
_ = v_1.Args[1]
v_1_0 := v_1.Args[0]
v_1_1 := v_1.Args[1]
for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
if x != v_1_0 || v_1_1.Op != OpConst32 {
continue
}
d := auxIntToInt32(v_1_1.AuxInt)
if !(c|d == c && oneBit(c^d)) {
continue
}
v.reset(OpEq32)
v0 := b.NewValue0(v.Pos, OpOr32, x.Type)
v1 := b.NewValue0(v.Pos, OpConst32, x.Type)
v1.AuxInt = int32ToAuxInt(c ^ d)
v0.AddArg2(x, v1)
v.AddArg2(v0, cv)
return true
}
}
}
break
}
// match: (OrB (Eq16 x cv:(Const16 [c])) (Eq16 x (Const16 [d])))
// cond: c|d == c && oneBit(c^d)
// result: (Eq16 (Or16 <x.Type> x (Const16 <x.Type> [c^d])) cv)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpEq16 {
continue
}
_ = v_0.Args[1]
v_0_0 := v_0.Args[0]
v_0_1 := v_0.Args[1]
for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
x := v_0_0
cv := v_0_1
if cv.Op != OpConst16 {
continue
}
c := auxIntToInt16(cv.AuxInt)
if v_1.Op != OpEq16 {
continue
}
_ = v_1.Args[1]
v_1_0 := v_1.Args[0]
v_1_1 := v_1.Args[1]
for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
if x != v_1_0 || v_1_1.Op != OpConst16 {
continue
}
d := auxIntToInt16(v_1_1.AuxInt)
if !(c|d == c && oneBit(c^d)) {
continue
}
v.reset(OpEq16)
v0 := b.NewValue0(v.Pos, OpOr16, x.Type)
v1 := b.NewValue0(v.Pos, OpConst16, x.Type)
v1.AuxInt = int16ToAuxInt(c ^ d)
v0.AddArg2(x, v1)
v.AddArg2(v0, cv)
return true
}
}
}
break
}
// match: (OrB (Eq8 x cv:(Const8 [c])) (Eq8 x (Const8 [d])))
// cond: c|d == c && oneBit(c^d)
// result: (Eq8 (Or8 <x.Type> x (Const8 <x.Type> [c^d])) cv)
for {
for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
if v_0.Op != OpEq8 {
continue
}
_ = v_0.Args[1]
v_0_0 := v_0.Args[0]
v_0_1 := v_0.Args[1]
for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
x := v_0_0
cv := v_0_1
if cv.Op != OpConst8 {
continue
}
c := auxIntToInt8(cv.AuxInt)
if v_1.Op != OpEq8 {
continue
}
_ = v_1.Args[1]
v_1_0 := v_1.Args[0]
v_1_1 := v_1.Args[1]
for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
if x != v_1_0 || v_1_1.Op != OpConst8 {
continue
}
d := auxIntToInt8(v_1_1.AuxInt)
if !(c|d == c && oneBit(c^d)) {
continue
}
v.reset(OpEq8)
v0 := b.NewValue0(v.Pos, OpOr8, x.Type)
v1 := b.NewValue0(v.Pos, OpConst8, x.Type)
v1.AuxInt = int8ToAuxInt(c ^ d)
v0.AddArg2(x, v1)
v.AddArg2(v0, cv)
return true
}
}
}
break
}
// match: (OrB (Neq64F x x) (Less64F x y:(Const64F [c]))) // match: (OrB (Neq64F x x) (Less64F x y:(Const64F [c])))
// result: (Not (Leq64F y x)) // result: (Not (Leq64F y x))
for { for {
@ -31601,17 +32106,47 @@ func rewriteValuegeneric_OpStructSelect(v *Value) bool {
v0.AddArg2(v1, mem) v0.AddArg2(v1, mem)
return true return true
} }
// match: (StructSelect [0] (IData x)) // match: (StructSelect (IData x))
// cond: v.Type.Size() > 0
// result: (IData x) // result: (IData x)
for { for {
if auxIntToInt64(v.AuxInt) != 0 || v_0.Op != OpIData { if v_0.Op != OpIData {
break break
} }
x := v_0.Args[0] x := v_0.Args[0]
if !(v.Type.Size() > 0) {
break
}
v.reset(OpIData) v.reset(OpIData)
v.AddArg(x) v.AddArg(x)
return true return true
} }
// match: (StructSelect (IData x))
// cond: v.Type.Size() == 0 && v.Type.IsStruct()
// result: (StructMake)
for {
if v_0.Op != OpIData {
break
}
if !(v.Type.Size() == 0 && v.Type.IsStruct()) {
break
}
v.reset(OpStructMake)
return true
}
// match: (StructSelect (IData x))
// cond: v.Type.Size() == 0 && v.Type.IsArray()
// result: (ArrayMake0)
for {
if v_0.Op != OpIData {
break
}
if !(v.Type.Size() == 0 && v.Type.IsArray()) {
break
}
v.reset(OpArrayMake0)
return true
}
return false return false
} }
func rewriteValuegeneric_OpSub16(v *Value) bool { func rewriteValuegeneric_OpSub16(v *Value) bool {

View file

@ -124,6 +124,11 @@ func InitConfig() {
ir.Syms.GCWriteBarrier[7] = typecheck.LookupRuntimeFunc("gcWriteBarrier8") ir.Syms.GCWriteBarrier[7] = typecheck.LookupRuntimeFunc("gcWriteBarrier8")
ir.Syms.Goschedguarded = typecheck.LookupRuntimeFunc("goschedguarded") ir.Syms.Goschedguarded = typecheck.LookupRuntimeFunc("goschedguarded")
ir.Syms.Growslice = typecheck.LookupRuntimeFunc("growslice") ir.Syms.Growslice = typecheck.LookupRuntimeFunc("growslice")
ir.Syms.GrowsliceBuf = typecheck.LookupRuntimeFunc("growsliceBuf")
ir.Syms.MoveSlice = typecheck.LookupRuntimeFunc("moveSlice")
ir.Syms.MoveSliceNoScan = typecheck.LookupRuntimeFunc("moveSliceNoScan")
ir.Syms.MoveSliceNoCap = typecheck.LookupRuntimeFunc("moveSliceNoCap")
ir.Syms.MoveSliceNoCapNoScan = typecheck.LookupRuntimeFunc("moveSliceNoCapNoScan")
ir.Syms.InterfaceSwitch = typecheck.LookupRuntimeFunc("interfaceSwitch") ir.Syms.InterfaceSwitch = typecheck.LookupRuntimeFunc("interfaceSwitch")
for i := 1; i < len(ir.Syms.MallocGCSmallNoScan); i++ { for i := 1; i < len(ir.Syms.MallocGCSmallNoScan); i++ {
ir.Syms.MallocGCSmallNoScan[i] = typecheck.LookupRuntimeFunc(fmt.Sprintf("mallocgcSmallNoScanSC%d", i)) ir.Syms.MallocGCSmallNoScan[i] = typecheck.LookupRuntimeFunc(fmt.Sprintf("mallocgcSmallNoScanSC%d", i))
@ -1096,6 +1101,23 @@ type state struct {
// Block starting position, indexed by block id. // Block starting position, indexed by block id.
blockStarts []src.XPos blockStarts []src.XPos
// Information for stack allocation. Indexed by the first argument
// to an append call. Normally a slice-typed variable, but not always.
backingStores map[ir.Node]*backingStoreInfo
}
type backingStoreInfo struct {
// Size of backing store array (in elements)
K int64
// Stack-allocated backing store variable.
store *ir.Name
// Dynamic boolean variable marking the fact that we used this backing store.
used *ir.Name
// Have we used this variable statically yet? This is just a hint
// to avoid checking the dynamic variable if the answer is obvious.
// (usedStatic == true implies used == true)
usedStatic bool
} }
type funcLine struct { type funcLine struct {
@ -3683,6 +3705,9 @@ func (s *state) exprCheckPtr(n ir.Node, checkPtrOK bool) *ssa.Value {
case ir.OAPPEND: case ir.OAPPEND:
return s.append(n.(*ir.CallExpr), false) return s.append(n.(*ir.CallExpr), false)
case ir.OMOVE2HEAP:
return s.move2heap(n.(*ir.MoveToHeapExpr))
case ir.OMIN, ir.OMAX: case ir.OMIN, ir.OMAX:
return s.minMax(n.(*ir.CallExpr)) return s.minMax(n.(*ir.CallExpr))
@ -3744,6 +3769,68 @@ func (s *state) resultAddrOfCall(c *ssa.Value, which int64, t *types.Type) *ssa.
return addr return addr
} }
// Get backing store information for an append call.
func (s *state) getBackingStoreInfoForAppend(n *ir.CallExpr) *backingStoreInfo {
if n.Esc() != ir.EscNone {
return nil
}
return s.getBackingStoreInfo(n.Args[0])
}
func (s *state) getBackingStoreInfo(n ir.Node) *backingStoreInfo {
t := n.Type()
et := t.Elem()
maxStackSize := int64(base.Debug.VariableMakeThreshold)
if et.Size() == 0 || et.Size() > maxStackSize {
return nil
}
if base.Flag.N != 0 {
return nil
}
if !base.VariableMakeHash.MatchPos(n.Pos(), nil) {
return nil
}
i := s.backingStores[n]
if i != nil {
return i
}
// Build type of backing store.
K := maxStackSize / et.Size() // rounds down
KT := types.NewArray(et, K)
KT.SetNoalg(true)
types.CalcArraySize(KT)
// Align more than naturally for the type KT. See issue 73199.
align := types.NewArray(types.Types[types.TUINTPTR], 0)
types.CalcArraySize(align)
storeTyp := types.NewStruct([]*types.Field{
{Sym: types.BlankSym, Type: align},
{Sym: types.BlankSym, Type: KT},
})
storeTyp.SetNoalg(true)
types.CalcStructSize(storeTyp)
// Make backing store variable.
backingStore := typecheck.TempAt(n.Pos(), s.curfn, storeTyp)
backingStore.SetAddrtaken(true)
// Make "used" boolean.
used := typecheck.TempAt(n.Pos(), s.curfn, types.Types[types.TBOOL])
if s.curBlock == s.f.Entry {
s.vars[used] = s.constBool(false)
} else {
// initialize this variable at end of entry block
s.defvars[s.f.Entry.ID][used] = s.constBool(false)
}
// Initialize an info structure.
if s.backingStores == nil {
s.backingStores = map[ir.Node]*backingStoreInfo{}
}
i = &backingStoreInfo{K: K, store: backingStore, used: used, usedStatic: false}
s.backingStores[n] = i
return i
}
// append converts an OAPPEND node to SSA. // append converts an OAPPEND node to SSA.
// If inplace is false, it converts the OAPPEND expression n to an ssa.Value, // If inplace is false, it converts the OAPPEND expression n to an ssa.Value,
// adds it to s, and returns the Value. // adds it to s, and returns the Value.
@ -3834,9 +3921,29 @@ func (s *state) append(n *ir.CallExpr, inplace bool) *ssa.Value {
// A stack-allocated backing store could be used at every // A stack-allocated backing store could be used at every
// append that qualifies, but we limit it in some cases to // append that qualifies, but we limit it in some cases to
// avoid wasted code and stack space. // avoid wasted code and stack space.
// TODO: handle ... append case. //
maxStackSize := int64(base.Debug.VariableMakeThreshold) // Note that we have two different strategies.
if !inplace && n.Esc() == ir.EscNone && et.Size() > 0 && et.Size() <= maxStackSize && base.Flag.N == 0 && base.VariableMakeHash.MatchPos(n.Pos(), nil) && !s.appendTargets[sn] { // 1. The standard strategy is just to allocate the full
// backing store at the first append.
// 2. An alternate strategy is used when
// a. The backing store eventually escapes via move2heap
// and b. The capacity is used somehow
// In this case, we don't want to just allocate
// the full buffer at the first append, because when
// we move2heap the buffer to the heap when it escapes,
// we might end up wasting memory because we can't
// change the capacity.
// So in this case we use growsliceBuf to reuse the buffer
// and walk one step up the size class ladder each time.
//
// TODO: handle ... append case? Currently we handle only
// a fixed number of appended elements.
var info *backingStoreInfo
if !inplace {
info = s.getBackingStoreInfoForAppend(n)
}
if !inplace && info != nil && !n.UseBuf && !info.usedStatic {
// if l <= K { // if l <= K {
// if !used { // if !used {
// if oldLen == 0 { // if oldLen == 0 {
@ -3860,43 +3967,19 @@ func (s *state) append(n *ir.CallExpr, inplace bool) *ssa.Value {
// It is ok to do it more often, but it is probably helpful only for // It is ok to do it more often, but it is probably helpful only for
// the first instance. TODO: this could use more tuning. Using ir.Node // the first instance. TODO: this could use more tuning. Using ir.Node
// as the key works for *ir.Name instances but probably nothing else. // as the key works for *ir.Name instances but probably nothing else.
if s.appendTargets == nil { info.usedStatic = true
s.appendTargets = map[ir.Node]bool{} // TODO: unset usedStatic somehow?
}
s.appendTargets[sn] = true
K := maxStackSize / et.Size() // rounds down
KT := types.NewArray(et, K)
KT.SetNoalg(true)
types.CalcArraySize(KT)
// Align more than naturally for the type KT. See issue 73199.
align := types.NewArray(types.Types[types.TUINTPTR], 0)
types.CalcArraySize(align)
storeTyp := types.NewStruct([]*types.Field{
{Sym: types.BlankSym, Type: align},
{Sym: types.BlankSym, Type: KT},
})
storeTyp.SetNoalg(true)
types.CalcStructSize(storeTyp)
usedTestBlock := s.f.NewBlock(ssa.BlockPlain) usedTestBlock := s.f.NewBlock(ssa.BlockPlain)
oldLenTestBlock := s.f.NewBlock(ssa.BlockPlain) oldLenTestBlock := s.f.NewBlock(ssa.BlockPlain)
bodyBlock := s.f.NewBlock(ssa.BlockPlain) bodyBlock := s.f.NewBlock(ssa.BlockPlain)
growSlice := s.f.NewBlock(ssa.BlockPlain) growSlice := s.f.NewBlock(ssa.BlockPlain)
// Make "used" boolean.
tBool := types.Types[types.TBOOL]
used := typecheck.TempAt(n.Pos(), s.curfn, tBool)
s.defvars[s.f.Entry.ID][used] = s.constBool(false) // initialize this variable at fn entry
// Make backing store variable.
tInt := types.Types[types.TINT] tInt := types.Types[types.TINT]
backingStore := typecheck.TempAt(n.Pos(), s.curfn, storeTyp) tBool := types.Types[types.TBOOL]
backingStore.SetAddrtaken(true)
// if l <= K // if l <= K
s.startBlock(grow) s.startBlock(grow)
kTest := s.newValue2(s.ssaOp(ir.OLE, tInt), tBool, l, s.constInt(tInt, K)) kTest := s.newValue2(s.ssaOp(ir.OLE, tInt), tBool, l, s.constInt(tInt, info.K))
b := s.endBlock() b := s.endBlock()
b.Kind = ssa.BlockIf b.Kind = ssa.BlockIf
b.SetControl(kTest) b.SetControl(kTest)
@ -3906,7 +3989,7 @@ func (s *state) append(n *ir.CallExpr, inplace bool) *ssa.Value {
// if !used // if !used
s.startBlock(usedTestBlock) s.startBlock(usedTestBlock)
usedTest := s.newValue1(ssa.OpNot, tBool, s.expr(used)) usedTest := s.newValue1(ssa.OpNot, tBool, s.expr(info.used))
b = s.endBlock() b = s.endBlock()
b.Kind = ssa.BlockIf b.Kind = ssa.BlockIf
b.SetControl(usedTest) b.SetControl(usedTest)
@ -3927,18 +4010,18 @@ func (s *state) append(n *ir.CallExpr, inplace bool) *ssa.Value {
// var store struct { _ [0]uintptr; arr [K]T } // var store struct { _ [0]uintptr; arr [K]T }
s.startBlock(bodyBlock) s.startBlock(bodyBlock)
if et.HasPointers() { if et.HasPointers() {
s.vars[memVar] = s.newValue1A(ssa.OpVarDef, types.TypeMem, backingStore, s.mem()) s.vars[memVar] = s.newValue1A(ssa.OpVarDef, types.TypeMem, info.store, s.mem())
} }
addr := s.addr(backingStore) addr := s.addr(info.store)
s.zero(storeTyp, addr) s.zero(info.store.Type(), addr)
// s = store.arr[:l:K] // s = store.arr[:l:K]
s.vars[ptrVar] = addr s.vars[ptrVar] = addr
s.vars[lenVar] = l // nargs would also be ok because of the oldLen==0 test. s.vars[lenVar] = l // nargs would also be ok because of the oldLen==0 test.
s.vars[capVar] = s.constInt(tInt, K) s.vars[capVar] = s.constInt(tInt, info.K)
// used = true // used = true
s.assign(used, s.constBool(true), false, 0) s.assign(info.used, s.constBool(true), false, 0)
b = s.endBlock() b = s.endBlock()
b.AddEdgeTo(assign) b.AddEdgeTo(assign)
@ -3949,7 +4032,25 @@ func (s *state) append(n *ir.CallExpr, inplace bool) *ssa.Value {
// Call growslice // Call growslice
s.startBlock(grow) s.startBlock(grow)
taddr := s.expr(n.Fun) taddr := s.expr(n.Fun)
r := s.rtcall(ir.Syms.Growslice, true, []*types.Type{n.Type()}, p, l, c, nargs, taddr) var r []*ssa.Value
if info != nil && n.UseBuf {
// Use stack-allocated buffer as backing store, if we can.
if et.HasPointers() && !info.usedStatic {
// Initialize in the function header. Not the best place,
// but it makes sure we don't scan this area before it is
// initialized.
mem := s.defvars[s.f.Entry.ID][memVar]
mem = s.f.Entry.NewValue1A(n.Pos(), ssa.OpVarDef, types.TypeMem, info.store, mem)
addr := s.f.Entry.NewValue2A(n.Pos(), ssa.OpLocalAddr, types.NewPtr(info.store.Type()), info.store, s.sp, mem)
mem = s.f.Entry.NewValue2I(n.Pos(), ssa.OpZero, types.TypeMem, info.store.Type().Size(), addr, mem)
mem.Aux = info.store.Type()
s.defvars[s.f.Entry.ID][memVar] = mem
info.usedStatic = true
}
r = s.rtcall(ir.Syms.GrowsliceBuf, true, []*types.Type{n.Type()}, p, l, c, nargs, taddr, s.addr(info.store), s.constInt(types.Types[types.TINT], info.K))
} else {
r = s.rtcall(ir.Syms.Growslice, true, []*types.Type{n.Type()}, p, l, c, nargs, taddr)
}
// Decompose output slice // Decompose output slice
p = s.newValue1(ssa.OpSlicePtr, pt, r[0]) p = s.newValue1(ssa.OpSlicePtr, pt, r[0])
@ -4036,6 +4137,95 @@ func (s *state) append(n *ir.CallExpr, inplace bool) *ssa.Value {
return s.newValue3(ssa.OpSliceMake, n.Type(), p, l, c) return s.newValue3(ssa.OpSliceMake, n.Type(), p, l, c)
} }
func (s *state) move2heap(n *ir.MoveToHeapExpr) *ssa.Value {
// s := n.Slice
// if s.ptr points to current stack frame {
// s2 := make([]T, s.len, s.cap)
// copy(s2[:cap], s[:cap])
// s = s2
// }
// return s
slice := s.expr(n.Slice)
et := slice.Type.Elem()
pt := types.NewPtr(et)
info := s.getBackingStoreInfo(n)
if info == nil {
// Backing store will never be stack allocated, so
// move2heap is a no-op.
return slice
}
// Decomposse input slice.
p := s.newValue1(ssa.OpSlicePtr, pt, slice)
l := s.newValue1(ssa.OpSliceLen, types.Types[types.TINT], slice)
c := s.newValue1(ssa.OpSliceCap, types.Types[types.TINT], slice)
moveBlock := s.f.NewBlock(ssa.BlockPlain)
mergeBlock := s.f.NewBlock(ssa.BlockPlain)
s.vars[ptrVar] = p
s.vars[lenVar] = l
s.vars[capVar] = c
// Decide if we need to move the slice backing store.
// It needs to be moved if it is currently on the stack.
sub := ssa.OpSub64
less := ssa.OpLess64U
if s.config.PtrSize == 4 {
sub = ssa.OpSub32
less = ssa.OpLess32U
}
callerSP := s.newValue1(ssa.OpGetCallerSP, types.Types[types.TUINTPTR], s.mem())
frameSize := s.newValue2(sub, types.Types[types.TUINTPTR], callerSP, s.sp)
pInt := s.newValue2(ssa.OpConvert, types.Types[types.TUINTPTR], p, s.mem())
off := s.newValue2(sub, types.Types[types.TUINTPTR], pInt, s.sp)
cond := s.newValue2(less, types.Types[types.TBOOL], off, frameSize)
b := s.endBlock()
b.Kind = ssa.BlockIf
b.Likely = ssa.BranchUnlikely // fast path is to not have to call into runtime
b.SetControl(cond)
b.AddEdgeTo(moveBlock)
b.AddEdgeTo(mergeBlock)
// Move the slice to heap
s.startBlock(moveBlock)
var newSlice *ssa.Value
if et.HasPointers() {
typ := s.expr(n.RType)
if n.PreserveCapacity {
newSlice = s.rtcall(ir.Syms.MoveSlice, true, []*types.Type{slice.Type}, typ, p, l, c)[0]
} else {
newSlice = s.rtcall(ir.Syms.MoveSliceNoCap, true, []*types.Type{slice.Type}, typ, p, l)[0]
}
} else {
elemSize := s.constInt(types.Types[types.TUINTPTR], et.Size())
if n.PreserveCapacity {
newSlice = s.rtcall(ir.Syms.MoveSliceNoScan, true, []*types.Type{slice.Type}, elemSize, p, l, c)[0]
} else {
newSlice = s.rtcall(ir.Syms.MoveSliceNoCapNoScan, true, []*types.Type{slice.Type}, elemSize, p, l)[0]
}
}
// Decompose output slice
s.vars[ptrVar] = s.newValue1(ssa.OpSlicePtr, pt, newSlice)
s.vars[lenVar] = s.newValue1(ssa.OpSliceLen, types.Types[types.TINT], newSlice)
s.vars[capVar] = s.newValue1(ssa.OpSliceCap, types.Types[types.TINT], newSlice)
b = s.endBlock()
b.AddEdgeTo(mergeBlock)
// Merge fast path (no moving) and slow path (moved)
s.startBlock(mergeBlock)
p = s.variable(ptrVar, pt) // generates phi for ptr
l = s.variable(lenVar, types.Types[types.TINT]) // generates phi for len
c = s.variable(capVar, types.Types[types.TINT]) // generates phi for cap
delete(s.vars, ptrVar)
delete(s.vars, lenVar)
delete(s.vars, capVar)
return s.newValue3(ssa.OpSliceMake, slice.Type, p, l, c)
}
// minMax converts an OMIN/OMAX builtin call into SSA. // minMax converts an OMIN/OMAX builtin call into SSA.
func (s *state) minMax(n *ir.CallExpr) *ssa.Value { func (s *state) minMax(n *ir.CallExpr) *ssa.Value {
// The OMIN/OMAX builtin is variadic, but its semantics are // The OMIN/OMAX builtin is variadic, but its semantics are

View file

@ -444,6 +444,19 @@ func testBitwiseRshU_ssa(a uint32, b, c uint32) uint32 {
return a >> b >> c return a >> b >> c
} }
//go:noinline
func orLt_ssa(x int) bool {
y := x - x
return (x | 2) < y
}
// test riscv64 SLTI rules
func testSetIfLessThan(t *testing.T) {
if want, got := true, orLt_ssa(-7); got != want {
t.Errorf("orLt_ssa(-7) = %t want %t", got, want)
}
}
//go:noinline //go:noinline
func testShiftCX_ssa() int { func testShiftCX_ssa() int {
v1 := uint8(3) v1 := uint8(3)
@ -977,6 +990,7 @@ func TestArithmetic(t *testing.T) {
testRegallocCVSpill(t) testRegallocCVSpill(t)
testSubqToNegq(t) testSubqToNegq(t)
testBitwiseLogic(t) testBitwiseLogic(t)
testSetIfLessThan(t)
testOcom(t) testOcom(t)
testLrot(t) testLrot(t)
testShiftCX(t) testShiftCX(t)

View file

@ -195,6 +195,7 @@ func makeslice(typ *byte, len int, cap int) unsafe.Pointer
func makeslice64(typ *byte, len int64, cap int64) unsafe.Pointer func makeslice64(typ *byte, len int64, cap int64) unsafe.Pointer
func makeslicecopy(typ *byte, tolen int, fromlen int, from unsafe.Pointer) unsafe.Pointer func makeslicecopy(typ *byte, tolen int, fromlen int, from unsafe.Pointer) unsafe.Pointer
func growslice(oldPtr *any, newLen, oldCap, num int, et *byte) (ary []any) func growslice(oldPtr *any, newLen, oldCap, num int, et *byte) (ary []any)
func growsliceBuf(oldPtr *any, newLen, oldCap, num int, et *byte, buf *any, bufLen int) (ary []any)
func unsafeslicecheckptr(typ *byte, ptr unsafe.Pointer, len int64) func unsafeslicecheckptr(typ *byte, ptr unsafe.Pointer, len int64)
func panicunsafeslicelen() func panicunsafeslicelen()
func panicunsafeslicenilptr() func panicunsafeslicenilptr()
@ -202,6 +203,11 @@ func unsafestringcheckptr(ptr unsafe.Pointer, len int64)
func panicunsafestringlen() func panicunsafestringlen()
func panicunsafestringnilptr() func panicunsafestringnilptr()
func moveSlice(typ *byte, old *byte, len, cap int) (*byte, int, int)
func moveSliceNoScan(elemSize uintptr, old *byte, len, cap int) (*byte, int, int)
func moveSliceNoCap(typ *byte, old *byte, len int) (*byte, int, int)
func moveSliceNoCapNoScan(elemSize uintptr, old *byte, len int) (*byte, int, int)
func memmove(to *any, frm *any, length uintptr) func memmove(to *any, frm *any, length uintptr)
func memclrNoHeapPointers(ptr unsafe.Pointer, n uintptr) func memclrNoHeapPointers(ptr unsafe.Pointer, n uintptr)
func memclrHasPointers(ptr unsafe.Pointer, n uintptr) func memclrHasPointers(ptr unsafe.Pointer, n uintptr)

View file

@ -160,80 +160,85 @@ var runtimeDecls = [...]struct {
{"makeslice64", funcTag, 124}, {"makeslice64", funcTag, 124},
{"makeslicecopy", funcTag, 125}, {"makeslicecopy", funcTag, 125},
{"growslice", funcTag, 127}, {"growslice", funcTag, 127},
{"unsafeslicecheckptr", funcTag, 128}, {"growsliceBuf", funcTag, 128},
{"unsafeslicecheckptr", funcTag, 129},
{"panicunsafeslicelen", funcTag, 9}, {"panicunsafeslicelen", funcTag, 9},
{"panicunsafeslicenilptr", funcTag, 9}, {"panicunsafeslicenilptr", funcTag, 9},
{"unsafestringcheckptr", funcTag, 129}, {"unsafestringcheckptr", funcTag, 130},
{"panicunsafestringlen", funcTag, 9}, {"panicunsafestringlen", funcTag, 9},
{"panicunsafestringnilptr", funcTag, 9}, {"panicunsafestringnilptr", funcTag, 9},
{"memmove", funcTag, 130}, {"moveSlice", funcTag, 131},
{"memclrNoHeapPointers", funcTag, 131}, {"moveSliceNoScan", funcTag, 132},
{"memclrHasPointers", funcTag, 131}, {"moveSliceNoCap", funcTag, 133},
{"memequal", funcTag, 132}, {"moveSliceNoCapNoScan", funcTag, 134},
{"memequal0", funcTag, 133}, {"memmove", funcTag, 135},
{"memequal8", funcTag, 133}, {"memclrNoHeapPointers", funcTag, 136},
{"memequal16", funcTag, 133}, {"memclrHasPointers", funcTag, 136},
{"memequal32", funcTag, 133}, {"memequal", funcTag, 137},
{"memequal64", funcTag, 133}, {"memequal0", funcTag, 138},
{"memequal128", funcTag, 133}, {"memequal8", funcTag, 138},
{"f32equal", funcTag, 134}, {"memequal16", funcTag, 138},
{"f64equal", funcTag, 134}, {"memequal32", funcTag, 138},
{"c64equal", funcTag, 134}, {"memequal64", funcTag, 138},
{"c128equal", funcTag, 134}, {"memequal128", funcTag, 138},
{"strequal", funcTag, 134}, {"f32equal", funcTag, 139},
{"interequal", funcTag, 134}, {"f64equal", funcTag, 139},
{"nilinterequal", funcTag, 134}, {"c64equal", funcTag, 139},
{"memhash", funcTag, 135}, {"c128equal", funcTag, 139},
{"memhash0", funcTag, 136}, {"strequal", funcTag, 139},
{"memhash8", funcTag, 136}, {"interequal", funcTag, 139},
{"memhash16", funcTag, 136}, {"nilinterequal", funcTag, 139},
{"memhash32", funcTag, 136}, {"memhash", funcTag, 140},
{"memhash64", funcTag, 136}, {"memhash0", funcTag, 141},
{"memhash128", funcTag, 136}, {"memhash8", funcTag, 141},
{"f32hash", funcTag, 137}, {"memhash16", funcTag, 141},
{"f64hash", funcTag, 137}, {"memhash32", funcTag, 141},
{"c64hash", funcTag, 137}, {"memhash64", funcTag, 141},
{"c128hash", funcTag, 137}, {"memhash128", funcTag, 141},
{"strhash", funcTag, 137}, {"f32hash", funcTag, 142},
{"interhash", funcTag, 137}, {"f64hash", funcTag, 142},
{"nilinterhash", funcTag, 137}, {"c64hash", funcTag, 142},
{"int64div", funcTag, 138}, {"c128hash", funcTag, 142},
{"uint64div", funcTag, 139}, {"strhash", funcTag, 142},
{"int64mod", funcTag, 138}, {"interhash", funcTag, 142},
{"uint64mod", funcTag, 139}, {"nilinterhash", funcTag, 142},
{"float64toint64", funcTag, 140}, {"int64div", funcTag, 143},
{"float64touint64", funcTag, 141}, {"uint64div", funcTag, 144},
{"float64touint32", funcTag, 142}, {"int64mod", funcTag, 143},
{"int64tofloat64", funcTag, 143}, {"uint64mod", funcTag, 144},
{"int64tofloat32", funcTag, 144}, {"float64toint64", funcTag, 145},
{"uint64tofloat64", funcTag, 145}, {"float64touint64", funcTag, 146},
{"uint64tofloat32", funcTag, 146}, {"float64touint32", funcTag, 147},
{"uint32tofloat64", funcTag, 147}, {"int64tofloat64", funcTag, 148},
{"complex128div", funcTag, 148}, {"int64tofloat32", funcTag, 149},
{"uint64tofloat64", funcTag, 150},
{"uint64tofloat32", funcTag, 151},
{"uint32tofloat64", funcTag, 152},
{"complex128div", funcTag, 153},
{"racefuncenter", funcTag, 33}, {"racefuncenter", funcTag, 33},
{"racefuncexit", funcTag, 9}, {"racefuncexit", funcTag, 9},
{"raceread", funcTag, 33}, {"raceread", funcTag, 33},
{"racewrite", funcTag, 33}, {"racewrite", funcTag, 33},
{"racereadrange", funcTag, 149}, {"racereadrange", funcTag, 154},
{"racewriterange", funcTag, 149}, {"racewriterange", funcTag, 154},
{"msanread", funcTag, 149}, {"msanread", funcTag, 154},
{"msanwrite", funcTag, 149}, {"msanwrite", funcTag, 154},
{"msanmove", funcTag, 150}, {"msanmove", funcTag, 155},
{"asanread", funcTag, 149}, {"asanread", funcTag, 154},
{"asanwrite", funcTag, 149}, {"asanwrite", funcTag, 154},
{"checkptrAlignment", funcTag, 151}, {"checkptrAlignment", funcTag, 156},
{"checkptrArithmetic", funcTag, 153}, {"checkptrArithmetic", funcTag, 158},
{"libfuzzerTraceCmp1", funcTag, 154}, {"libfuzzerTraceCmp1", funcTag, 159},
{"libfuzzerTraceCmp2", funcTag, 155}, {"libfuzzerTraceCmp2", funcTag, 160},
{"libfuzzerTraceCmp4", funcTag, 156}, {"libfuzzerTraceCmp4", funcTag, 161},
{"libfuzzerTraceCmp8", funcTag, 157}, {"libfuzzerTraceCmp8", funcTag, 162},
{"libfuzzerTraceConstCmp1", funcTag, 154}, {"libfuzzerTraceConstCmp1", funcTag, 159},
{"libfuzzerTraceConstCmp2", funcTag, 155}, {"libfuzzerTraceConstCmp2", funcTag, 160},
{"libfuzzerTraceConstCmp4", funcTag, 156}, {"libfuzzerTraceConstCmp4", funcTag, 161},
{"libfuzzerTraceConstCmp8", funcTag, 157}, {"libfuzzerTraceConstCmp8", funcTag, 162},
{"libfuzzerHookStrCmp", funcTag, 158}, {"libfuzzerHookStrCmp", funcTag, 163},
{"libfuzzerHookEqualFold", funcTag, 158}, {"libfuzzerHookEqualFold", funcTag, 163},
{"addCovMeta", funcTag, 160}, {"addCovMeta", funcTag, 165},
{"x86HasAVX", varTag, 6}, {"x86HasAVX", varTag, 6},
{"x86HasFMA", varTag, 6}, {"x86HasFMA", varTag, 6},
{"x86HasPOPCNT", varTag, 6}, {"x86HasPOPCNT", varTag, 6},
@ -244,11 +249,11 @@ var runtimeDecls = [...]struct {
{"loong64HasLAM_BH", varTag, 6}, {"loong64HasLAM_BH", varTag, 6},
{"loong64HasLSX", varTag, 6}, {"loong64HasLSX", varTag, 6},
{"riscv64HasZbb", varTag, 6}, {"riscv64HasZbb", varTag, 6},
{"asanregisterglobals", funcTag, 131}, {"asanregisterglobals", funcTag, 136},
} }
func runtimeTypes() []*types.Type { func runtimeTypes() []*types.Type {
var typs [161]*types.Type var typs [166]*types.Type
typs[0] = types.ByteType typs[0] = types.ByteType
typs[1] = types.NewPtr(typs[0]) typs[1] = types.NewPtr(typs[0])
typs[2] = types.Types[types.TANY] typs[2] = types.Types[types.TANY]
@ -377,39 +382,44 @@ func runtimeTypes() []*types.Type {
typs[125] = newSig(params(typs[1], typs[13], typs[13], typs[7]), params(typs[7])) typs[125] = newSig(params(typs[1], typs[13], typs[13], typs[7]), params(typs[7]))
typs[126] = types.NewSlice(typs[2]) typs[126] = types.NewSlice(typs[2])
typs[127] = newSig(params(typs[3], typs[13], typs[13], typs[13], typs[1]), params(typs[126])) typs[127] = newSig(params(typs[3], typs[13], typs[13], typs[13], typs[1]), params(typs[126]))
typs[128] = newSig(params(typs[1], typs[7], typs[22]), nil) typs[128] = newSig(params(typs[3], typs[13], typs[13], typs[13], typs[1], typs[3], typs[13]), params(typs[126]))
typs[129] = newSig(params(typs[7], typs[22]), nil) typs[129] = newSig(params(typs[1], typs[7], typs[22]), nil)
typs[130] = newSig(params(typs[3], typs[3], typs[5]), nil) typs[130] = newSig(params(typs[7], typs[22]), nil)
typs[131] = newSig(params(typs[7], typs[5]), nil) typs[131] = newSig(params(typs[1], typs[1], typs[13], typs[13]), params(typs[1], typs[13], typs[13]))
typs[132] = newSig(params(typs[3], typs[3], typs[5]), params(typs[6])) typs[132] = newSig(params(typs[5], typs[1], typs[13], typs[13]), params(typs[1], typs[13], typs[13]))
typs[133] = newSig(params(typs[3], typs[3]), params(typs[6])) typs[133] = newSig(params(typs[1], typs[1], typs[13]), params(typs[1], typs[13], typs[13]))
typs[134] = newSig(params(typs[7], typs[7]), params(typs[6])) typs[134] = newSig(params(typs[5], typs[1], typs[13]), params(typs[1], typs[13], typs[13]))
typs[135] = newSig(params(typs[3], typs[5], typs[5]), params(typs[5])) typs[135] = newSig(params(typs[3], typs[3], typs[5]), nil)
typs[136] = newSig(params(typs[7], typs[5]), params(typs[5])) typs[136] = newSig(params(typs[7], typs[5]), nil)
typs[137] = newSig(params(typs[3], typs[5]), params(typs[5])) typs[137] = newSig(params(typs[3], typs[3], typs[5]), params(typs[6]))
typs[138] = newSig(params(typs[22], typs[22]), params(typs[22])) typs[138] = newSig(params(typs[3], typs[3]), params(typs[6]))
typs[139] = newSig(params(typs[24], typs[24]), params(typs[24])) typs[139] = newSig(params(typs[7], typs[7]), params(typs[6]))
typs[140] = newSig(params(typs[18]), params(typs[22])) typs[140] = newSig(params(typs[3], typs[5], typs[5]), params(typs[5]))
typs[141] = newSig(params(typs[18]), params(typs[24])) typs[141] = newSig(params(typs[7], typs[5]), params(typs[5]))
typs[142] = newSig(params(typs[18]), params(typs[67])) typs[142] = newSig(params(typs[3], typs[5]), params(typs[5]))
typs[143] = newSig(params(typs[22]), params(typs[18])) typs[143] = newSig(params(typs[22], typs[22]), params(typs[22]))
typs[144] = newSig(params(typs[22]), params(typs[20])) typs[144] = newSig(params(typs[24], typs[24]), params(typs[24]))
typs[145] = newSig(params(typs[24]), params(typs[18])) typs[145] = newSig(params(typs[18]), params(typs[22]))
typs[146] = newSig(params(typs[24]), params(typs[20])) typs[146] = newSig(params(typs[18]), params(typs[24]))
typs[147] = newSig(params(typs[67]), params(typs[18])) typs[147] = newSig(params(typs[18]), params(typs[67]))
typs[148] = newSig(params(typs[26], typs[26]), params(typs[26])) typs[148] = newSig(params(typs[22]), params(typs[18]))
typs[149] = newSig(params(typs[5], typs[5]), nil) typs[149] = newSig(params(typs[22]), params(typs[20]))
typs[150] = newSig(params(typs[5], typs[5], typs[5]), nil) typs[150] = newSig(params(typs[24]), params(typs[18]))
typs[151] = newSig(params(typs[7], typs[1], typs[5]), nil) typs[151] = newSig(params(typs[24]), params(typs[20]))
typs[152] = types.NewSlice(typs[7]) typs[152] = newSig(params(typs[67]), params(typs[18]))
typs[153] = newSig(params(typs[7], typs[152]), nil) typs[153] = newSig(params(typs[26], typs[26]), params(typs[26]))
typs[154] = newSig(params(typs[71], typs[71], typs[15]), nil) typs[154] = newSig(params(typs[5], typs[5]), nil)
typs[155] = newSig(params(typs[65], typs[65], typs[15]), nil) typs[155] = newSig(params(typs[5], typs[5], typs[5]), nil)
typs[156] = newSig(params(typs[67], typs[67], typs[15]), nil) typs[156] = newSig(params(typs[7], typs[1], typs[5]), nil)
typs[157] = newSig(params(typs[24], typs[24], typs[15]), nil) typs[157] = types.NewSlice(typs[7])
typs[158] = newSig(params(typs[30], typs[30], typs[15]), nil) typs[158] = newSig(params(typs[7], typs[157]), nil)
typs[159] = types.NewArray(typs[0], 16) typs[159] = newSig(params(typs[71], typs[71], typs[15]), nil)
typs[160] = newSig(params(typs[7], typs[67], typs[159], typs[30], typs[13], typs[71], typs[71]), params(typs[67])) typs[160] = newSig(params(typs[65], typs[65], typs[15]), nil)
typs[161] = newSig(params(typs[67], typs[67], typs[15]), nil)
typs[162] = newSig(params(typs[24], typs[24], typs[15]), nil)
typs[163] = newSig(params(typs[30], typs[30], typs[15]), nil)
typs[164] = types.NewArray(typs[0], 16)
typs[165] = newSig(params(typs[7], typs[67], typs[164], typs[30], typs[13], typs[71], typs[71]), params(typs[67]))
return typs[:] return typs[:]
} }

View file

@ -1842,26 +1842,7 @@ func IsReflexive(t *Type) bool {
// Can this type be stored directly in an interface word? // Can this type be stored directly in an interface word?
// Yes, if the representation is a single pointer. // Yes, if the representation is a single pointer.
func IsDirectIface(t *Type) bool { func IsDirectIface(t *Type) bool {
switch t.Kind() { return t.Size() == int64(PtrSize) && PtrDataSize(t) == int64(PtrSize)
case TPTR:
// Pointers to notinheap types must be stored indirectly. See issue 42076.
return !t.Elem().NotInHeap()
case TCHAN,
TMAP,
TFUNC,
TUNSAFEPTR:
return true
case TARRAY:
// Array of 1 direct iface type can be direct.
return t.NumElem() == 1 && IsDirectIface(t.Elem())
case TSTRUCT:
// Struct with 1 field of direct iface type can be direct.
return t.NumFields() == 1 && IsDirectIface(t.Field(0).Type)
}
return false
} }
// IsInterfaceMethod reports whether (field) m is // IsInterfaceMethod reports whether (field) m is

View file

@ -175,7 +175,8 @@ type Checker struct {
methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods
untyped map[syntax.Expr]exprInfo // map of expressions without final type untyped map[syntax.Expr]exprInfo // map of expressions without final type
delayed []action // stack of delayed action segments; segments are processed in FIFO order delayed []action // stack of delayed action segments; segments are processed in FIFO order
objPath []Object // path of object dependencies during type inference (for cycle reporting) objPath []Object // path of object dependencies during type-checking (for cycle reporting)
objPathIdx map[Object]int // map of object to object path index during type-checking (for cycle reporting)
cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking
// environment within which the current object is type-checked (valid only // environment within which the current object is type-checked (valid only
@ -248,19 +249,22 @@ func (check *Checker) later(f func()) *action {
return &check.delayed[i] return &check.delayed[i]
} }
// push pushes obj onto the object path and returns its index in the path. // push pushes obj onto the object path and records its index in the path index map.
func (check *Checker) push(obj Object) int { func (check *Checker) push(obj Object) {
if check.objPathIdx == nil {
check.objPathIdx = make(map[Object]int)
}
check.objPathIdx[obj] = len(check.objPath)
check.objPath = append(check.objPath, obj) check.objPath = append(check.objPath, obj)
return len(check.objPath) - 1
} }
// pop pops and returns the topmost object from the object path. // pop pops an object from the object path and removes it from the path index map.
func (check *Checker) pop() Object { func (check *Checker) pop() {
i := len(check.objPath) - 1 i := len(check.objPath) - 1
obj := check.objPath[i] obj := check.objPath[i]
check.objPath[i] = nil check.objPath[i] = nil // help the garbage collector
check.objPath = check.objPath[:i] check.objPath = check.objPath[:i]
return obj delete(check.objPathIdx, obj)
} }
type cleaner interface { type cleaner interface {
@ -319,6 +323,7 @@ func (check *Checker) initFiles(files []*syntax.File) {
check.untyped = nil check.untyped = nil
check.delayed = nil check.delayed = nil
check.objPath = nil check.objPath = nil
check.objPathIdx = nil
check.cleaners = nil check.cleaners = nil
// We must initialize usedVars and usedPkgNames both here and in NewChecker, // We must initialize usedVars and usedPkgNames both here and in NewChecker,

View file

@ -54,7 +54,6 @@ func (check *Checker) directCycle(tname *TypeName, pathIdx map[*TypeName]int) {
// tname is marked grey - we have a cycle on the path beginning at start. // tname is marked grey - we have a cycle on the path beginning at start.
// Mark tname as invalid. // Mark tname as invalid.
tname.setType(Typ[Invalid]) tname.setType(Typ[Invalid])
tname.setColor(black)
// collect type names on cycle // collect type names on cycle
var cycle []Object var cycle []Object

View file

@ -62,114 +62,77 @@ func (check *Checker) objDecl(obj Object, def *TypeName) {
if check.indent == 0 { if check.indent == 0 {
fmt.Println() // empty line between top-level objects for readability fmt.Println() // empty line between top-level objects for readability
} }
check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath)) check.trace(obj.Pos(), "-- checking %s (objPath = %s)", obj, pathString(check.objPath))
check.indent++ check.indent++
defer func() { defer func() {
check.indent-- check.indent--
check.trace(obj.Pos(), "=> %s (%s)", obj, obj.color()) check.trace(obj.Pos(), "=> %s", obj)
}() }()
} }
// Checking the declaration of obj means inferring its type // Checking the declaration of an object means determining its type
// (and possibly its value, for constants). // (and also its value for constants). An object (and thus its type)
// An object's type (and thus the object) may be in one of // may be in 1 of 3 states:
// three states which are expressed by colors:
// //
// - an object whose type is not yet known is painted white (initial color) // - not in Checker.objPathIdx and type == nil : type is not yet known (white)
// - an object whose type is in the process of being inferred is painted grey // - in Checker.objPathIdx : type is pending (grey)
// - an object whose type is fully inferred is painted black // - not in Checker.objPathIdx and type != nil : type is known (black)
// //
// During type inference, an object's color changes from white to grey // During type-checking, an object changes from white to grey to black.
// to black (pre-declared objects are painted black from the start). // Predeclared objects start as black (their type is known without checking).
// A black object (i.e., its type) can only depend on (refer to) other black
// ones. White and grey objects may depend on white and black objects.
// A dependency on a grey object indicates a cycle which may or may not be
// valid.
// //
// When objects turn grey, they are pushed on the object path (a stack); // A black object may only depend on (refer to) to other black objects. White
// they are popped again when they turn black. Thus, if a grey object (a // and grey objects may depend on white or black objects. A dependency on a
// cycle) is encountered, it is on the object path, and all the objects // grey object indicates a (possibly invalid) cycle.
// it depends on are the remaining objects on that path. Color encoding //
// is such that the color value of a grey object indicates the index of // When an object is marked grey, it is pushed onto the object path (a stack)
// that object in the object path. // and its index in the path is recorded in the path index map. It is popped
// and removed from the map when its type is determined (and marked black).
// During type-checking, white objects may be assigned a type without // If this object is grey, we have a (possibly invalid) cycle. This is signaled
// traversing through objDecl; e.g., when initializing constants and // by a non-nil type for the object, except for constants and variables whose
// variables. Update the colors of those objects here (rather than // type may be non-nil (known), or nil if it depends on a not-yet known
// everywhere where we set the type) to satisfy the color invariants. // initialization value.
if obj.color() == white && obj.Type() != nil { //
obj.setColor(black) // In the former case, set the type to Typ[Invalid] because we have an
return // initialization cycle. The cycle error will be reported later, when
} // determining initialization order.
//
switch obj.color() { // TODO(gri) Report cycle here and simplify initialization order code.
case white: if _, ok := check.objPathIdx[obj]; ok {
assert(obj.Type() == nil)
// All color values other than white and black are considered grey.
// Because black and white are < grey, all values >= grey are grey.
// Use those values to encode the object's index into the object path.
obj.setColor(grey + color(check.push(obj)))
defer func() {
check.pop().setColor(black)
}()
case black:
assert(obj.Type() != nil)
return
default:
// Color values other than white or black are considered grey.
fallthrough
case grey:
// We have a (possibly invalid) cycle.
// In the existing code, this is marked by a non-nil type
// for the object except for constants and variables whose
// type may be non-nil (known), or nil if it depends on the
// not-yet known initialization value.
// In the former case, set the type to Typ[Invalid] because
// we have an initialization cycle. The cycle error will be
// reported later, when determining initialization order.
// TODO(gri) Report cycle here and simplify initialization
// order code.
switch obj := obj.(type) { switch obj := obj.(type) {
case *Const: case *Const, *Var:
if !check.validCycle(obj) || obj.typ == nil { if !check.validCycle(obj) || obj.Type() == nil {
obj.typ = Typ[Invalid] obj.setType(Typ[Invalid])
} }
case *Var:
if !check.validCycle(obj) || obj.typ == nil {
obj.typ = Typ[Invalid]
}
case *TypeName: case *TypeName:
if !check.validCycle(obj) { if !check.validCycle(obj) {
// break cycle obj.setType(Typ[Invalid])
// (without this, calling underlying()
// below may lead to an endless loop
// if we have a cycle for a defined
// (*Named) type)
obj.typ = Typ[Invalid]
} }
case *Func: case *Func:
if !check.validCycle(obj) { if !check.validCycle(obj) {
// Don't set obj.typ to Typ[Invalid] here // Don't set type to Typ[Invalid]; plenty of code asserts that
// because plenty of code type-asserts that // functions have a *Signature type. Instead, leave the type
// functions have a *Signature type. Grey // as an empty signature, which makes it impossible to
// functions have their type set to an empty
// signature which makes it impossible to
// initialize a variable with the function. // initialize a variable with the function.
} }
default: default:
panic("unreachable") panic("unreachable")
} }
assert(obj.Type() != nil) assert(obj.Type() != nil)
return return
} }
if obj.Type() != nil { // black, meaning it's already type-checked
return
}
// white, meaning it must be type-checked
check.push(obj)
defer check.pop()
d := check.objMap[obj] d := check.objMap[obj]
if d == nil { if d == nil {
check.dump("%v: %s should have been declared", obj.Pos(), obj) check.dump("%v: %s should have been declared", obj.Pos(), obj)
@ -221,8 +184,8 @@ func (check *Checker) validCycle(obj Object) (valid bool) {
} }
// Count cycle objects. // Count cycle objects.
assert(obj.color() >= grey) start, found := check.objPathIdx[obj]
start := obj.color() - grey // index of obj in objPath assert(found)
cycle := check.objPath[start:] cycle := check.objPath[start:]
tparCycle := false // if set, the cycle is through a type parameter list tparCycle := false // if set, the cycle is through a type parameter list
nval := 0 // number of (constant or variable) values in the cycle nval := 0 // number of (constant or variable) values in the cycle
@ -532,11 +495,16 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeN
check.collectTypeParams(&alias.tparams, tdecl.TParamList) check.collectTypeParams(&alias.tparams, tdecl.TParamList)
} }
rhs = check.definedType(tdecl.Type, obj) rhs = check.declaredType(tdecl.Type, obj)
assert(rhs != nil) assert(rhs != nil)
alias.fromRHS = rhs alias.fromRHS = rhs
unalias(alias) // populate alias.actual
// spec: In an alias declaration the given type cannot be a type parameter declared in the same declaration."
// (see also go.dev/issue/75884, go.dev/issue/#75885)
if tpar, ok := rhs.(*TypeParam); ok && alias.tparams != nil && slices.Index(alias.tparams.list(), tpar) >= 0 {
check.error(tdecl.Type, MisplacedTypeParam, "cannot use type parameter declared in alias declaration as RHS")
alias.fromRHS = Typ[Invalid]
}
} else { } else {
if !versionErr && tparam0 != nil { if !versionErr && tparam0 != nil {
check.error(tdecl, UnsupportedFeature, "generic type alias requires GODEBUG=gotypesalias=1 or unset") check.error(tdecl, UnsupportedFeature, "generic type alias requires GODEBUG=gotypesalias=1 or unset")
@ -576,7 +544,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeN
check.collectTypeParams(&named.tparams, tdecl.TParamList) check.collectTypeParams(&named.tparams, tdecl.TParamList)
} }
rhs = check.definedType(tdecl.Type, obj) rhs = check.declaredType(tdecl.Type, obj)
assert(rhs != nil) assert(rhs != nil)
named.fromRHS = rhs named.fromRHS = rhs
@ -764,17 +732,8 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
sig := new(Signature) sig := new(Signature)
obj.typ = sig // guard against cycles obj.typ = sig // guard against cycles
// Avoid cycle error when referring to method while type-checking the signature.
// This avoids a nuisance in the best case (non-parameterized receiver type) and
// since the method is not a type, we get an error. If we have a parameterized
// receiver type, instantiating the receiver type leads to the instantiation of
// its methods, and we don't want a cycle error in that case.
// TODO(gri) review if this is correct and/or whether we still need this?
saved := obj.color_
obj.color_ = black
fdecl := decl.fdecl fdecl := decl.fdecl
check.funcType(sig, fdecl.Recv, fdecl.TParamList, fdecl.Type) check.funcType(sig, fdecl.Recv, fdecl.TParamList, fdecl.Type)
obj.color_ = saved
// Set the scope's extent to the complete "func (...) { ... }" // Set the scope's extent to the complete "func (...) { ... }"
// so that Scope.Innermost works correctly. // so that Scope.Innermost works correctly.
@ -921,10 +880,9 @@ func (check *Checker) declStmt(list []syntax.Decl) {
// the innermost containing block." // the innermost containing block."
scopePos := s.Name.Pos() scopePos := s.Name.Pos()
check.declare(check.scope, s.Name, obj, scopePos) check.declare(check.scope, s.Name, obj, scopePos)
// mark and unmark type before calling typeDecl; its type is still nil (see Checker.objDecl) check.push(obj) // mark as grey
obj.setColor(grey + color(check.push(obj)))
check.typeDecl(obj, s, nil) check.typeDecl(obj, s, nil)
check.pop().setColor(black) check.pop()
default: default:
check.errorf(s, InvalidSyntaxTree, "unknown syntax.Decl node %T", s) check.errorf(s, InvalidSyntaxTree, "unknown syntax.Decl node %T", s)

View file

@ -42,18 +42,12 @@ type Object interface {
// 0 for all other objects (including objects in file scopes). // 0 for all other objects (including objects in file scopes).
order() uint32 order() uint32
// color returns the object's color.
color() color
// setType sets the type of the object. // setType sets the type of the object.
setType(Type) setType(Type)
// setOrder sets the order number of the object. It must be > 0. // setOrder sets the order number of the object. It must be > 0.
setOrder(uint32) setOrder(uint32)
// setColor sets the object's color. It must not be white.
setColor(color color)
// setParent sets the parent scope of the object. // setParent sets the parent scope of the object.
setParent(*Scope) setParent(*Scope)
@ -102,41 +96,9 @@ type object struct {
name string name string
typ Type typ Type
order_ uint32 order_ uint32
color_ color
scopePos_ syntax.Pos scopePos_ syntax.Pos
} }
// color encodes the color of an object (see Checker.objDecl for details).
type color uint32
// An object may be painted in one of three colors.
// Color values other than white or black are considered grey.
const (
white color = iota
black
grey // must be > white and black
)
func (c color) String() string {
switch c {
case white:
return "white"
case black:
return "black"
default:
return "grey"
}
}
// colorFor returns the (initial) color for an object depending on
// whether its type t is known or not.
func colorFor(t Type) color {
if t != nil {
return black
}
return white
}
// Parent returns the scope in which the object is declared. // Parent returns the scope in which the object is declared.
// The result is nil for methods and struct fields. // The result is nil for methods and struct fields.
func (obj *object) Parent() *Scope { return obj.parent } func (obj *object) Parent() *Scope { return obj.parent }
@ -164,13 +126,11 @@ func (obj *object) Id() string { return Id(obj.pkg, obj.name) }
func (obj *object) String() string { panic("abstract") } func (obj *object) String() string { panic("abstract") }
func (obj *object) order() uint32 { return obj.order_ } func (obj *object) order() uint32 { return obj.order_ }
func (obj *object) color() color { return obj.color_ }
func (obj *object) scopePos() syntax.Pos { return obj.scopePos_ } func (obj *object) scopePos() syntax.Pos { return obj.scopePos_ }
func (obj *object) setParent(parent *Scope) { obj.parent = parent } func (obj *object) setParent(parent *Scope) { obj.parent = parent }
func (obj *object) setType(typ Type) { obj.typ = typ } func (obj *object) setType(typ Type) { obj.typ = typ }
func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order } func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order }
func (obj *object) setColor(color color) { assert(color != white); obj.color_ = color }
func (obj *object) setScopePos(pos syntax.Pos) { obj.scopePos_ = pos } func (obj *object) setScopePos(pos syntax.Pos) { obj.scopePos_ = pos }
func (obj *object) sameId(pkg *Package, name string, foldCase bool) bool { func (obj *object) sameId(pkg *Package, name string, foldCase bool) bool {
@ -247,7 +207,7 @@ type PkgName struct {
// NewPkgName returns a new PkgName object representing an imported package. // NewPkgName returns a new PkgName object representing an imported package.
// The remaining arguments set the attributes found with all Objects. // The remaining arguments set the attributes found with all Objects.
func NewPkgName(pos syntax.Pos, pkg *Package, name string, imported *Package) *PkgName { func NewPkgName(pos syntax.Pos, pkg *Package, name string, imported *Package) *PkgName {
return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, black, nopos}, imported} return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, nopos}, imported}
} }
// Imported returns the package that was imported. // Imported returns the package that was imported.
@ -263,7 +223,7 @@ type Const struct {
// NewConst returns a new constant with value val. // NewConst returns a new constant with value val.
// The remaining arguments set the attributes found with all Objects. // The remaining arguments set the attributes found with all Objects.
func NewConst(pos syntax.Pos, pkg *Package, name string, typ Type, val constant.Value) *Const { func NewConst(pos syntax.Pos, pkg *Package, name string, typ Type, val constant.Value) *Const {
return &Const{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, val} return &Const{object{nil, pos, pkg, name, typ, 0, nopos}, val}
} }
// Val returns the constant's value. // Val returns the constant's value.
@ -288,7 +248,7 @@ type TypeName struct {
// argument for NewNamed, which will set the TypeName's type as a side- // argument for NewNamed, which will set the TypeName's type as a side-
// effect. // effect.
func NewTypeName(pos syntax.Pos, pkg *Package, name string, typ Type) *TypeName { func NewTypeName(pos syntax.Pos, pkg *Package, name string, typ Type) *TypeName {
return &TypeName{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}} return &TypeName{object{nil, pos, pkg, name, typ, 0, nopos}}
} }
// NewTypeNameLazy returns a new defined type like NewTypeName, but it // NewTypeNameLazy returns a new defined type like NewTypeName, but it
@ -402,7 +362,7 @@ func NewField(pos syntax.Pos, pkg *Package, name string, typ Type, embedded bool
// newVar returns a new variable. // newVar returns a new variable.
// The arguments set the attributes found with all Objects. // The arguments set the attributes found with all Objects.
func newVar(kind VarKind, pos syntax.Pos, pkg *Package, name string, typ Type) *Var { func newVar(kind VarKind, pos syntax.Pos, pkg *Package, name string, typ Type) *Var {
return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, kind: kind} return &Var{object: object{nil, pos, pkg, name, typ, 0, nopos}, kind: kind}
} }
// Anonymous reports whether the variable is an embedded field. // Anonymous reports whether the variable is an embedded field.
@ -452,7 +412,7 @@ func NewFunc(pos syntax.Pos, pkg *Package, name string, sig *Signature) *Func {
// as this would violate object.{Type,color} invariants. // as this would violate object.{Type,color} invariants.
// TODO(adonovan): propose to disallow NewFunc with nil *Signature. // TODO(adonovan): propose to disallow NewFunc with nil *Signature.
} }
return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, false, nil} return &Func{object{nil, pos, pkg, name, typ, 0, nopos}, false, nil}
} }
// Signature returns the signature (type) of the function or method. // Signature returns the signature (type) of the function or method.
@ -534,7 +494,7 @@ type Label struct {
// NewLabel returns a new label. // NewLabel returns a new label.
func NewLabel(pos syntax.Pos, pkg *Package, name string) *Label { func NewLabel(pos syntax.Pos, pkg *Package, name string) *Label {
return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid], color_: black}, false} return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid]}, false}
} }
// A Builtin represents a built-in function. // A Builtin represents a built-in function.
@ -545,7 +505,7 @@ type Builtin struct {
} }
func newBuiltin(id builtinId) *Builtin { func newBuiltin(id builtinId) *Builtin {
return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid], color_: black}, id} return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid]}, id}
} }
// Nil represents the predeclared value nil. // Nil represents the predeclared value nil.

View file

@ -217,10 +217,8 @@ func (*lazyObject) Exported() bool { panic("unreachable") }
func (*lazyObject) Id() string { panic("unreachable") } func (*lazyObject) Id() string { panic("unreachable") }
func (*lazyObject) String() string { panic("unreachable") } func (*lazyObject) String() string { panic("unreachable") }
func (*lazyObject) order() uint32 { panic("unreachable") } func (*lazyObject) order() uint32 { panic("unreachable") }
func (*lazyObject) color() color { panic("unreachable") }
func (*lazyObject) setType(Type) { panic("unreachable") } func (*lazyObject) setType(Type) { panic("unreachable") }
func (*lazyObject) setOrder(uint32) { panic("unreachable") } func (*lazyObject) setOrder(uint32) { panic("unreachable") }
func (*lazyObject) setColor(color color) { panic("unreachable") }
func (*lazyObject) setParent(*Scope) { panic("unreachable") } func (*lazyObject) setParent(*Scope) { panic("unreachable") }
func (*lazyObject) sameId(*Package, string, bool) bool { panic("unreachable") } func (*lazyObject) sameId(*Package, string, bool) bool { panic("unreachable") }
func (*lazyObject) scopePos() syntax.Pos { panic("unreachable") } func (*lazyObject) scopePos() syntax.Pos { panic("unreachable") }

View file

@ -36,14 +36,14 @@ func TestSizeof(t *testing.T) {
{term{}, 12, 24}, {term{}, 12, 24},
// Objects // Objects
{PkgName{}, 60, 96}, {PkgName{}, 56, 96},
{Const{}, 64, 104}, {Const{}, 60, 104},
{TypeName{}, 56, 88}, {TypeName{}, 52, 88},
{Var{}, 64, 104}, {Var{}, 60, 104},
{Func{}, 64, 104}, {Func{}, 60, 104},
{Label{}, 60, 96}, {Label{}, 56, 96},
{Builtin{}, 60, 96}, {Builtin{}, 56, 96},
{Nil{}, 56, 88}, {Nil{}, 52, 88},
// Misc // Misc
{Scope{}, 60, 104}, {Scope{}, 60, 104},

View file

@ -16,7 +16,7 @@ import (
// ident type-checks identifier e and initializes x with the value or type of e. // ident type-checks identifier e and initializes x with the value or type of e.
// If an error occurred, x.mode is set to invalid. // If an error occurred, x.mode is set to invalid.
// For the meaning of def, see Checker.definedType, below. // For the meaning of def, see Checker.declaredType, below.
// If wantType is set, the identifier e is expected to denote a type. // If wantType is set, the identifier e is expected to denote a type.
func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType bool) { func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType bool) {
x.mode = invalid x.mode = invalid
@ -149,14 +149,14 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
// typ type-checks the type expression e and returns its type, or Typ[Invalid]. // typ type-checks the type expression e and returns its type, or Typ[Invalid].
// The type must not be an (uninstantiated) generic type. // The type must not be an (uninstantiated) generic type.
func (check *Checker) typ(e syntax.Expr) Type { func (check *Checker) typ(e syntax.Expr) Type {
return check.definedType(e, nil) return check.declaredType(e, nil)
} }
// varType type-checks the type expression e and returns its type, or Typ[Invalid]. // varType type-checks the type expression e and returns its type, or Typ[Invalid].
// The type must not be an (uninstantiated) generic type and it must not be a // The type must not be an (uninstantiated) generic type and it must not be a
// constraint interface. // constraint interface.
func (check *Checker) varType(e syntax.Expr) Type { func (check *Checker) varType(e syntax.Expr) Type {
typ := check.definedType(e, nil) typ := check.declaredType(e, nil)
check.validVarType(e, typ) check.validVarType(e, typ)
return typ return typ
} }
@ -187,11 +187,11 @@ func (check *Checker) validVarType(e syntax.Expr, typ Type) {
}).describef(e, "check var type %s", typ) }).describef(e, "check var type %s", typ)
} }
// definedType is like typ but also accepts a type name def. // declaredType is like typ but also accepts a type name def.
// If def != nil, e is the type specification for the type named def, declared // If def != nil, e is the type specification for the [Alias] or [Named] type
// in a type declaration, and def.typ.underlying will be set to the type of e // named def, and def.typ.fromRHS will be set to the [Type] of e immediately
// before any components of e are type-checked. // after its creation.
func (check *Checker) definedType(e syntax.Expr, def *TypeName) Type { func (check *Checker) declaredType(e syntax.Expr, def *TypeName) Type {
typ := check.typInternal(e, def) typ := check.typInternal(e, def)
assert(isTyped(typ)) assert(isTyped(typ))
if isGeneric(typ) { if isGeneric(typ) {
@ -230,7 +230,7 @@ func goTypeName(typ Type) string {
} }
// typInternal drives type checking of types. // typInternal drives type checking of types.
// Must only be called by definedType or genericType. // Must only be called by declaredType or genericType.
func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) {
if check.conf.Trace { if check.conf.Trace {
check.trace(e0.Pos(), "-- type %s", e0) check.trace(e0.Pos(), "-- type %s", e0)
@ -296,7 +296,7 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) {
case *syntax.ParenExpr: case *syntax.ParenExpr:
// Generic types must be instantiated before they can be used in any form. // Generic types must be instantiated before they can be used in any form.
// Consequently, generic types cannot be parenthesized. // Consequently, generic types cannot be parenthesized.
return check.definedType(e.X, def) return check.declaredType(e.X, def)
case *syntax.ArrayType: case *syntax.ArrayType:
typ := new(Array) typ := new(Array)

View file

@ -98,7 +98,6 @@ func defPredeclaredTypes() {
// interface. // interface.
{ {
universeAnyNoAlias = NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet}) universeAnyNoAlias = NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet})
universeAnyNoAlias.setColor(black)
// ensure that the any TypeName reports a consistent Parent, after // ensure that the any TypeName reports a consistent Parent, after
// hijacking Universe.Lookup with gotypesalias=0. // hijacking Universe.Lookup with gotypesalias=0.
universeAnyNoAlias.setParent(Universe) universeAnyNoAlias.setParent(Universe)
@ -107,7 +106,6 @@ func defPredeclaredTypes() {
// into the Universe, but we lean toward the future and insert the Alias // into the Universe, but we lean toward the future and insert the Alias
// representation. // representation.
universeAnyAlias = NewTypeName(nopos, nil, "any", nil) universeAnyAlias = NewTypeName(nopos, nil, "any", nil)
universeAnyAlias.setColor(black)
_ = NewAlias(universeAnyAlias, universeAnyNoAlias.Type().Underlying()) // Link TypeName and Alias _ = NewAlias(universeAnyAlias, universeAnyNoAlias.Type().Underlying()) // Link TypeName and Alias
def(universeAnyAlias) def(universeAnyAlias)
} }
@ -115,7 +113,6 @@ func defPredeclaredTypes() {
// type error interface{ Error() string } // type error interface{ Error() string }
{ {
obj := NewTypeName(nopos, nil, "error", nil) obj := NewTypeName(nopos, nil, "error", nil)
obj.setColor(black)
typ := (*Checker)(nil).newNamed(obj, nil, nil) typ := (*Checker)(nil).newNamed(obj, nil, nil)
// error.Error() string // error.Error() string
@ -136,7 +133,6 @@ func defPredeclaredTypes() {
// type comparable interface{} // marked as comparable // type comparable interface{} // marked as comparable
{ {
obj := NewTypeName(nopos, nil, "comparable", nil) obj := NewTypeName(nopos, nil, "comparable", nil)
obj.setColor(black)
typ := (*Checker)(nil).newNamed(obj, nil, nil) typ := (*Checker)(nil).newNamed(obj, nil, nil)
// interface{} // marked as comparable // interface{} // marked as comparable
@ -165,7 +161,7 @@ func defPredeclaredConsts() {
} }
func defPredeclaredNil() { func defPredeclaredNil() {
def(&Nil{object{name: "nil", typ: Typ[UntypedNil], color_: black}}) def(&Nil{object{name: "nil", typ: Typ[UntypedNil]}})
} }
// A builtinId is the id of a builtin function. // A builtinId is the id of a builtin function.
@ -289,7 +285,7 @@ func init() {
// a scope. Objects with exported names are inserted in the unsafe package // a scope. Objects with exported names are inserted in the unsafe package
// scope; other objects are inserted in the universe scope. // scope; other objects are inserted in the universe scope.
func def(obj Object) { func def(obj Object) {
assert(obj.color() == black) assert(obj.Type() != nil)
name := obj.Name() name := obj.Name()
if strings.Contains(name, " ") { if strings.Contains(name, " ") {
return // nothing to do return // nothing to do

View file

@ -351,6 +351,11 @@ func walkExpr1(n ir.Node, init *ir.Nodes) ir.Node {
case ir.OMETHVALUE: case ir.OMETHVALUE:
return walkMethodValue(n.(*ir.SelectorExpr), init) return walkMethodValue(n.(*ir.SelectorExpr), init)
case ir.OMOVE2HEAP:
n := n.(*ir.MoveToHeapExpr)
n.Slice = walkExpr(n.Slice, init)
return n
} }
// No return! Each case must return (or panic), // No return! Each case must return (or panic),

View file

@ -6,12 +6,12 @@ require (
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5
golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938 golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938
golang.org/x/build v0.0.0-20250806225920-b7c66c047964 golang.org/x/build v0.0.0-20250806225920-b7c66c047964
golang.org/x/mod v0.29.0 golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526
golang.org/x/sync v0.17.0 golang.org/x/sync v0.18.0
golang.org/x/sys v0.37.0 golang.org/x/sys v0.38.0
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54
golang.org/x/term v0.34.0 golang.org/x/term v0.34.0
golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5 golang.org/x/tools v0.39.1-0.20251114194111-59ff18ce4883
) )
require ( require (

View file

@ -10,19 +10,19 @@ golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938 h1:VJ182b/ajNehMFRltVfCh
golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/build v0.0.0-20250806225920-b7c66c047964 h1:yRs1K51GKq7hsIO+YHJ8LsslrvwFceNPIv0tYjpcBd0= golang.org/x/build v0.0.0-20250806225920-b7c66c047964 h1:yRs1K51GKq7hsIO+YHJ8LsslrvwFceNPIv0tYjpcBd0=
golang.org/x/build v0.0.0-20250806225920-b7c66c047964/go.mod h1:i9Vx7+aOQUpYJRxSO+OpRStVBCVL/9ccI51xblWm5WY= golang.org/x/build v0.0.0-20250806225920-b7c66c047964/go.mod h1:i9Vx7+aOQUpYJRxSO+OpRStVBCVL/9ccI51xblWm5WY=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526 h1:LPpBM4CGUFMC47OqgAr2YIUxEUjH1Ur+D3KR/1LiuuQ=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU= golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5 h1:cz7f45KGWAtyIrz6bm45Gc+lw8beIxBSW3EQh4Bwbg4= golang.org/x/tools v0.39.1-0.20251114194111-59ff18ce4883 h1:aeO0AW8d+a+5+hNQx9f4J5egD89zftrY2x42KGQjLzI=
golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools v0.39.1-0.20251114194111-59ff18ce4883/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ=

View file

@ -51,6 +51,9 @@ func stripExperiment(version string) string {
if i := strings.Index(version, " X:"); i >= 0 { if i := strings.Index(version, " X:"); i >= 0 {
return version[:i] return version[:i]
} }
if i := strings.Index(version, "-X:"); i >= 0 {
return version[:i]
}
return version return version
} }

View file

@ -44,7 +44,7 @@ func newScriptEngine() *script.Engine {
return script.OnceCondition(summary, func() (bool, error) { return f(), nil }) return script.OnceCondition(summary, func() (bool, error) { return f(), nil })
} }
add("bzr", lazyBool("the 'bzr' executable exists and provides the standard CLI", hasWorkingBzr)) add("bzr", lazyBool("the 'bzr' executable exists and provides the standard CLI", hasWorkingBzr))
add("git-min-vers", script.PrefixCondition("<suffix> indicates a minimum git version", hasAtLeastGitVersion)) add("git-sha256", script.OnceCondition("the local 'git' version is recent enough to support sha256 object/commit hashes", gitSupportsSHA256))
interrupt := func(cmd *exec.Cmd) error { return cmd.Process.Signal(os.Interrupt) } interrupt := func(cmd *exec.Cmd) error { return cmd.Process.Signal(os.Interrupt) }
gracePeriod := 30 * time.Second // arbitrary gracePeriod := 30 * time.Second // arbitrary
@ -412,10 +412,14 @@ func gitVersion() (string, error) {
return "v" + string(matches[1]), nil return "v" + string(matches[1]), nil
} }
func hasAtLeastGitVersion(s *script.State, minVers string) (bool, error) { func hasAtLeastGitVersion(minVers string) (bool, error) {
gitVers, gitVersErr := gitVersion() gitVers, gitVersErr := gitVersion()
if gitVersErr != nil { if gitVersErr != nil {
return false, gitVersErr return false, gitVersErr
} }
return semver.Compare(minVers, gitVers) <= 0, nil return semver.Compare(minVers, gitVers) <= 0, nil
} }
func gitSupportsSHA256() (bool, error) {
return hasAtLeastGitVersion("v2.29")
}

View file

@ -44,7 +44,7 @@ func scriptConditions(t *testing.T) map[string]script.Cond {
add("case-sensitive", script.OnceCondition("$WORK filesystem is case-sensitive", isCaseSensitive)) add("case-sensitive", script.OnceCondition("$WORK filesystem is case-sensitive", isCaseSensitive))
add("cc", script.PrefixCondition("go env CC = <suffix> (ignoring the go/env file)", ccIs)) add("cc", script.PrefixCondition("go env CC = <suffix> (ignoring the go/env file)", ccIs))
add("git", lazyBool("the 'git' executable exists and provides the standard CLI", hasWorkingGit)) add("git", lazyBool("the 'git' executable exists and provides the standard CLI", hasWorkingGit))
add("git-min-vers", script.PrefixCondition("<suffix> indicates a minimum git version", hasAtLeastGitVersion)) add("git-sha256", script.OnceCondition("the local 'git' version is recent enough to support sha256 object/commit hashes", gitSupportsSHA256))
add("net", script.PrefixCondition("can connect to external network host <suffix>", hasNet)) add("net", script.PrefixCondition("can connect to external network host <suffix>", hasNet))
add("trimpath", script.OnceCondition("test binary was built with -trimpath", isTrimpath)) add("trimpath", script.OnceCondition("test binary was built with -trimpath", isTrimpath))
@ -171,7 +171,7 @@ func gitVersion() (string, error) {
return "v" + string(matches[1]), nil return "v" + string(matches[1]), nil
} }
func hasAtLeastGitVersion(s *script.State, minVers string) (bool, error) { func hasAtLeastGitVersion(minVers string) (bool, error) {
gitVers, gitVersErr := gitVersion() gitVers, gitVersErr := gitVersion()
if gitVersErr != nil { if gitVersErr != nil {
return false, gitVersErr return false, gitVersErr
@ -179,6 +179,10 @@ func hasAtLeastGitVersion(s *script.State, minVers string) (bool, error) {
return semver.Compare(minVers, gitVers) <= 0, nil return semver.Compare(minVers, gitVers) <= 0, nil
} }
func gitSupportsSHA256() (bool, error) {
return hasAtLeastGitVersion("v2.29")
}
func hasWorkingBzr() bool { func hasWorkingBzr() bool {
bzr, err := exec.LookPath("bzr") bzr, err := exec.LookPath("bzr")
if err != nil { if err != nil {

View file

@ -399,8 +399,8 @@ The available conditions are:
GOOS/GOARCH supports -fuzz with instrumentation GOOS/GOARCH supports -fuzz with instrumentation
[git] [git]
the 'git' executable exists and provides the standard CLI the 'git' executable exists and provides the standard CLI
[git-min-vers:*] [git-sha256]
<suffix> indicates a minimum git version the local 'git' version is recent enough to support sha256 object/commit hashes
[go-builder] [go-builder]
GO_BUILDER_NAME is non-empty GO_BUILDER_NAME is non-empty
[link] [link]

View file

@ -1,6 +1,6 @@
[short] skip [short] skip
[!git] skip [!git] skip
[!git-min-vers:v2.29] skip [!git-sha256] skip
env GOPRIVATE=vcs-test.golang.org env GOPRIVATE=vcs-test.golang.org

View file

@ -1,6 +1,6 @@
[short] skip [short] skip
[!git] skip [!git] skip
[!git-min-vers:v2.29] skip [!git-sha256] skip
env GOPRIVATE=vcs-test.golang.org env GOPRIVATE=vcs-test.golang.org

View file

@ -1,6 +1,6 @@
[short] skip [short] skip
[!git] skip [!git] skip
[!git-min-vers:v2.29] skip [!git-sha256] skip
# This is a git sha256-mode copy of mod_download_git_bareRepository # This is a git sha256-mode copy of mod_download_git_bareRepository

View file

@ -2,14 +2,14 @@
# 'GOPROXY=direct go get golang.org/x/tools/gopls@master' did not correctly # 'GOPROXY=direct go get golang.org/x/tools/gopls@master' did not correctly
# resolve the pseudo-version for its dependency on golang.org/x/tools. # resolve the pseudo-version for its dependency on golang.org/x/tools.
[!net:cloud.google.com] skip [short] skip
[!git] skip [!git] skip
env GO111MODULE=on env GO111MODULE=on
env GOPROXY=direct env GOPROXY=direct
env GOSUMDB=off env GOSUMDB=off
go list -m cloud.google.com/go@main go list -m vcs-test.golang.org/git/tagtests.git@master
! stdout 'v0.0.0-' ! stdout 'v0.0.0-'
-- go.mod -- -- go.mod --

View file

@ -1,4 +1,4 @@
[!git-min-vers:v2.29] skip [!git-sha256] skip
handle git handle git

View file

@ -1155,6 +1155,7 @@ type Func interface {
type Link struct { type Link struct {
Headtype objabi.HeadType Headtype objabi.HeadType
Arch *LinkArch Arch *LinkArch
CompressInstructions bool // use compressed instructions where possible (if supported by architecture)
Debugasm int Debugasm int
Debugvlog bool Debugvlog bool
Debugpcln string Debugpcln string

View file

@ -589,6 +589,10 @@ const (
AORN AORN
AANDN AANDN
// 2.2.1.12
AMULWVW
AMULWVWU
// 2.2.7. Atomic Memory Access Instructions // 2.2.7. Atomic Memory Access Instructions
AAMSWAPB AAMSWAPB
AAMSWAPH AAMSWAPH

View file

@ -131,6 +131,8 @@ var Anames = []string{
"ALSLV", "ALSLV",
"ORN", "ORN",
"ANDN", "ANDN",
"MULWVW",
"MULWVWU",
"AMSWAPB", "AMSWAPB",
"AMSWAPH", "AMSWAPH",
"AMSWAPW", "AMSWAPW",

View file

@ -1503,6 +1503,8 @@ func buildop(ctxt *obj.Link) {
opset(AREMU, r0) opset(AREMU, r0)
opset(ADIV, r0) opset(ADIV, r0)
opset(ADIVU, r0) opset(ADIVU, r0)
opset(AMULWVW, r0)
opset(AMULWVWU, r0)
case AMULV: case AMULV:
opset(AMULVU, r0) opset(AMULVU, r0)
@ -3230,6 +3232,10 @@ func (c *ctxt0) oprrr(a obj.As) uint32 {
return 0x3c << 15 // mulh.d return 0x3c << 15 // mulh.d
case AMULHVU: case AMULHVU:
return 0x3d << 15 // mulhu.d return 0x3d << 15 // mulhu.d
case AMULWVW:
return 0x3e << 15 // mulw.d.w
case AMULWVWU:
return 0x3f << 15 // mulw.d.wu
case ADIV: case ADIV:
return 0x40 << 15 // div.w return 0x40 << 15 // div.w
case ADIVU: case ADIVU:

View file

@ -11,8 +11,8 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp"
"runtime" "runtime"
"strings"
"testing" "testing"
) )
@ -48,10 +48,10 @@ func genLargeBranch(buf *bytes.Buffer) {
fmt.Fprintln(buf, "TEXT f(SB),0,$0-0") fmt.Fprintln(buf, "TEXT f(SB),0,$0-0")
fmt.Fprintln(buf, "BEQ X0, X0, label") fmt.Fprintln(buf, "BEQ X0, X0, label")
for i := 0; i < 1<<19; i++ { for i := 0; i < 1<<19; i++ {
fmt.Fprintln(buf, "ADD $0, X0, X0") fmt.Fprintln(buf, "ADD $0, X5, X0")
} }
fmt.Fprintln(buf, "label:") fmt.Fprintln(buf, "label:")
fmt.Fprintln(buf, "ADD $0, X0, X0") fmt.Fprintln(buf, "ADD $0, X5, X0")
} }
// TestLargeCall generates a large function (>1MB of text) with a call to // TestLargeCall generates a large function (>1MB of text) with a call to
@ -112,11 +112,11 @@ func genLargeCall(buf *bytes.Buffer) {
fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-0") fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-0")
fmt.Fprintln(buf, "CALL ·y(SB)") fmt.Fprintln(buf, "CALL ·y(SB)")
for i := 0; i < 1<<19; i++ { for i := 0; i < 1<<19; i++ {
fmt.Fprintln(buf, "ADD $0, X0, X0") fmt.Fprintln(buf, "ADD $0, X5, X0")
} }
fmt.Fprintln(buf, "RET") fmt.Fprintln(buf, "RET")
fmt.Fprintln(buf, "TEXT ·y(SB),0,$0-0") fmt.Fprintln(buf, "TEXT ·y(SB),0,$0-0")
fmt.Fprintln(buf, "ADD $0, X0, X0") fmt.Fprintln(buf, "ADD $0, X5, X0")
fmt.Fprintln(buf, "RET") fmt.Fprintln(buf, "RET")
} }
@ -301,9 +301,9 @@ TEXT _stub(SB),$0-0
// FENCE // FENCE
// NOP // NOP
// FENCE // FENCE
// RET // RET (CJALR or JALR)
want := "0f 00 f0 0f 13 00 00 00 0f 00 f0 0f 67 80 00 00" want := regexp.MustCompile("0x0000 0f 00 f0 0f 13 00 00 00 0f 00 f0 0f (82 80|67 80 00 00) ")
if !strings.Contains(string(out), want) { if !want.Match(out) {
t.Errorf("PCALIGN test failed - got %s\nwant %s", out, want) t.Errorf("PCALIGN test failed - got %s\nwant %s", out, want)
} }
} }

View file

@ -326,6 +326,9 @@ const (
NEED_GOT_PCREL_ITYPE_RELOC NEED_GOT_PCREL_ITYPE_RELOC
) )
const NEED_RELOC = NEED_JAL_RELOC | NEED_CALL_RELOC | NEED_PCREL_ITYPE_RELOC |
NEED_PCREL_STYPE_RELOC | NEED_GOT_PCREL_ITYPE_RELOC
// RISC-V mnemonics, as defined in the "opcodes" and "opcodes-pseudo" files // RISC-V mnemonics, as defined in the "opcodes" and "opcodes-pseudo" files
// at https://github.com/riscv/riscv-opcodes. // at https://github.com/riscv/riscv-opcodes.
// //

View file

@ -0,0 +1,297 @@
// Copyright 2025 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 riscv implements the riscv64 assembler.
# Register naming
The integer registers are named X0 through to X31, however X4 must be accessed
through its RISC-V ABI name, TP, and X27, which holds a pointer to the Go
routine structure, must be referred to as g. Additionally, when building in
shared mode, X3 is unavailable and must be accessed via its RISC-V ABI name,
GP.
The floating-point registers are named F0 through to F31.
The vector registers are named V0 through to V31.
Both integer and floating-point registers can be referred to by their RISC-V
ABI names, e.g., A0 or FT0, with the exception that X27 cannot be referred to
by its RISC-V ABI name, S11. It must be referred to as g.
Some of the integer registers are used by the Go runtime and assembler - X26 is
the closure pointer, X27 points to the Go routine structure and X31 is a
temporary register used by the Go assembler. Use of X31 should be avoided in
hand written assembly code as its value could be altered by the instruction
sequences emitted by the assembler.
# Instruction naming
Many RISC-V instructions contain one or more suffixes in their names. In the
[RISC-V ISA Manual] these suffixes are separated from themselves and the
name of the instruction mnemonic with a dot ('.'). In the Go assembler, the
separators are omitted and the suffixes are written in upper case.
Example:
FMVWX <=> fmv.w.x
# Rounding modes
The Go toolchain does not set the FCSR register and requires the desired
rounding mode to be explicitly encoded within floating-point instructions.
The syntax the Go assembler uses to specify the rounding modes differs
from the syntax in the RISC-V specifications. In the [RISC-V ISA Manual]
the rounding mode is given as an extra operand at the end of an
assembly language instruction. In the Go assembler, the rounding modes are
converted to uppercase and follow the instruction mnemonic from which they
are separated with a dot ('.').
Example:
FCVTLUS.RNE F0, X5 <=> fcvt.lu.s x5, f0, rne
RTZ is assumed if the rounding mode is omitted.
# RISC-V extensions
By default the Go compiler targets the [rva20u64] profile. This profile mandates
all the general RISC-V instructions, allowing Go to use integer, multiplication,
division, floating-point and atomic instructions without having to
perform compile time or runtime checks to verify that their use is appropriate
for the target hardware. All widely available riscv64 devices support at least
[rva20u64]. The Go toolchain can be instructed to target later RISC-V profiles,
including, [rva22u64] and [rva23u64], via the GORISCV64 environment variable.
Instructions that are provided by newer profiles cannot typically be used in
handwritten assembly code without compile time guards (or runtime checks)
that ensure they are hardware supported.
The file asm_riscv64.h defines macros for each RISC-V extension that is enabled
by setting the GORISCV64 environment variable to a value other than [rva20u64].
For example, if GORISCV64=rva22u64 the macros hasZba, hasZbb and hasZbs will be
defined. If GORISCV64=rva23u64 hasV will be defined in addition to hasZba,
hasZbb and hasZbs. These macros can be used to determine whether it's safe
to use an instruction in hand-written assembly.
It is not always necessary to include asm_riscv64.h and use #ifdefs in your
code to safely take advantage of instructions present in the [rva22u64]
profile. In some cases the assembler can generate [rva20u64] compatible code
even when an [rva22u64] instruction is used in an assembly source file. When
GORISCV64=rva20u64 the assembler will synthesize certain [rva22u64]
instructions, e.g., ANDN, using multiple [rva20u64] instructions. Instructions
such as ANDN can then be freely used in assembly code without checking to see
whether the instruction is supported by the target profile. When building a
source file containing the ANDN instruction with GORISCV64=rva22u64 the
assembler will emit the Zbb ANDN instruction directly. When building the same
source file with GORISCV64=rva20u64 the assembler will emit multiple [rva20u64]
instructions to synthesize ANDN.
The assembler will also use [rva22u64] instructions to implement the zero and
sign extension instructions, e.g., MOVB and MOVHU, when GORISCV64=rva22u64 or
greater.
The instructions not implemented in the default profile ([rva20u64]) that can
be safely used in assembly code without compile time checks are:
- ANDN
- MAX
- MAXU
- MIN
- MINU
- MOVB
- MOVH
- MOVHU
- MOVWU
- ORN
- ROL
- ROLW
- ROR
- RORI
- RORIW
- RORW
- XNOR
# Operand ordering
The ordering used for instruction operands in the Go assembler differs from the
ordering defined in the [RISC-V ISA Manual].
1. R-Type instructions
R-Type instructions are written in the reverse order to that given in the
[RISC-V ISA Manual], with the register order being rs2, rs1, rd.
Examples:
ADD X10, X11, X12 <=> add x12, x11, x10
FADDD F10, F11, F12 <=> fadd.d f12, f11, f10
2. I-Type arithmetic instructions
I-Type arithmetic instructions (not loads, fences, ebreak, ecall) use the same
ordering as the R-Type instructions, typically, imm12, rs1, rd.
Examples:
ADDI $1, X11, X12 <=> add x12, x11, 1
SLTI $1, X11, X12 <=> slti x12, x11, 1
3. Loads and Stores
Load instructions are written with the source operand (whether it be a register
or a memory address), first followed by the destination operand.
Examples:
MOV 16(X2), X10 <=> ld x10, 16(x2)
MOV X10, (X2) <=> sd x10, 0(x2)
4. Branch instructions
The branch instructions use the same operand ordering as is given in the
[RISC-V ISA Manual], e.g., rs1, rs2, label.
Example:
BLT X12, X23, loop1 <=> blt x12, x23, loop1
BLT X12, X23, label will jump to label if X12 < X23. Note this is not the
same ordering as is used for the SLT instructions.
5. FMA instructions
The Go assembler uses a different ordering for the RISC-V FMA operands to
the ordering given in the [RISC-V ISA Manual]. The operands are rotated one
place to the left, so that the destination operand comes last.
Example:
FMADDS F1, F2, F3, F4 <=> fmadd.s f4, f1, f2, f3
6. AMO instructions
The ordering used for the AMO operations is rs2, rs1, rd, i.e., the operands
as specified in the [RISC-V ISA Manual] are rotated one place to the left.
Example:
AMOSWAPW X5, (X6), X7 <=> amoswap.w x7, x5, (x6)
7. Vector instructions
The VSETVLI instruction uses the same symbolic names as the [RISC-V ISA Manual]
to represent the components of vtype, with the exception
that they are written in upper case. The ordering of the operands in the Go
assembler differs from the [RISC-V ISA Manual] in that the operands are
rotated one place to the left so that the destination register, the register
that holds the new vl, is the last operand.
Example:
VSETVLI X10, E8, M1, TU, MU, X12 <=> vsetvli x12, x10, e8, m1, tu, mu
Vector load and store instructions follow the pattern set by scalar loads and
stores, i.e., the source is always the first operand and the destination the
last. However, the ordering of the operands of these instructions is
complicated by the optional mask register and, in some cases, the use of an
additional stride or index register. In the Go assembler the index and stride
registers appear as the second operand in indexed or strided loads and stores,
while the mask register, if present, is always the penultimate operand.
Examples:
VLE8V (X10), V3 <=> vle8.v v3, (x10)
VSE8V V3, (X10) <=> vse8.v v3, (x10)
VLE8V (X10), V0, V3 <=> vle8.v v3, (x10), v0.t
VSE8V V3, V0, (X10) <=> vse8.v v3, (x10), v0.t
VLSE8V (X10), X11, V3 <=> vlse8.v v3, (x10), x11
VSSE8V V3, X11, (X10) <=> vsse8.v v3, (x10), x11
VLSE8V (X10), X11, V0, V3 <=> vlse8.v v3, (x10), x11, v0.t
VSSE8V V3, X11, V0, (X10) <=> vsse8.v v3, (x10), x11, v0.t
VLUXEI8V (X10), V2, V3 <=> vluxei8.v v3, (x10), v2
VSUXEI8V V3, V2, (X10) <=> vsuxei8.v v3, (x10), v2
VLUXEI8V (X10), V2, V0, V3 <=> vluxei8.v v3, (x10), v2, v0.t
VSUXEI8V V3, V2, V0, (X10) <=> vsuxei8.v v3, (x10), v2, v0.t
VL1RE8V (X10), V3 <=> vl1re8.v v3, (x10)
VS1RV V3, (X11) <=> vs1r.v v3, (x11)
The ordering of operands for two and three argument vector arithmetic instructions is
reversed in the Go assembler.
Examples:
VMVVV V2, V3 <=> vmv.v.v v3, v2
VADDVV V1, V2, V3 <=> vadd.vv v3, v2, v1
VADDVX X10, V2, V3 <=> vadd.vx v3, v2, x10
VMADCVI $15, V2, V3 <=> vmadc.vi v3, v2, 15
The mask register, when specified, is always the penultimate operand in a vector
arithmetic instruction, appearing before the destination register.
Examples:
VANDVV V1, V2, V0, V3 <=> vand.vv v3, v2, v1, v0.t
# Ternary instructions
The Go assembler allows the second operand to be omitted from most ternary
instructions if it matches the third (destination) operand.
Examples:
ADD X10, X12, X12 <=> ADD X10, X12
ANDI $3, X12, X12 <=> ANDI $3, X12
The use of this abbreviated syntax is encouraged.
# Ordering of atomic instructions
It is not possible to specify the ordering bits in the FENCE, LR, SC or AMO
instructions. The FENCE instruction is always emitted as a full fence, the
acquire and release bits are always set for the AMO instructions, the acquire
bit is always set for the LR instructions while the release bit is set for
the SC instructions.
# Immediate operands
In many cases, where an R-Type instruction has a corresponding I-Type
instruction, the R-Type mnemonic can be used in place of the I-Type mnemonic.
The assembler assumes that the immediate form of the instruction was intended
when the first operand is given as an immediate value rather than a register.
Example:
AND $3, X12, X13 <=> ANDI $3, X12, X13
# Integer constant materialization
The MOV instruction can be used to set a register to the value of any 64 bit
constant literal. The way this is achieved by the assembler varies depending
on the value of the constant. Where possible the assembler will synthesize the
constant using one or more RISC-V arithmetic instructions. If it is unable
to easily materialize the constant it will load the 64 bit literal from memory.
A 32 bit constant literal can be specified as an argument to ADDI, ANDI, ORI and
XORI. If the specified literal does not fit into 12 bits the assembler will
generate extra instructions to synthesize it.
Integer constants provided as operands to all other instructions must fit into
the number of bits allowed by the instructions' encodings for immediate values.
Otherwise, an error will be generated.
# Floating point constant materialization
The MOVF and MOVD instructions can be used to set a register to the value
of any 32 bit or 64 bit floating point constant literal, respectively. Unless
the constant literal is 0.0, MOVF and MOVD will be encoded as FLW and FLD
instructions that load the constant from a location within the program's
binary.
[RISC-V ISA Manual]: https://github.com/riscv/riscv-isa-manual
[rva20u64]: https://github.com/riscv/riscv-profiles/blob/main/src/profiles.adoc#51-rva20u64-profile
[rva22u64]: https://github.com/riscv/riscv-profiles/blob/main/src/profiles.adoc#rva22u64-profile
[rva23u64]: https://github.com/riscv/riscv-profiles/blob/main/src/rva23-profile.adoc#rva23u64-profile
*/
package riscv

View file

@ -414,10 +414,10 @@ func containsCall(sym *obj.LSym) bool {
// setPCs sets the Pc field in all instructions reachable from p. // setPCs sets the Pc field in all instructions reachable from p.
// It uses pc as the initial value and returns the next available pc. // It uses pc as the initial value and returns the next available pc.
func setPCs(p *obj.Prog, pc int64) int64 { func setPCs(p *obj.Prog, pc int64, compress bool) int64 {
for ; p != nil; p = p.Link { for ; p != nil; p = p.Link {
p.Pc = pc p.Pc = pc
for _, ins := range instructionsForProg(p) { for _, ins := range instructionsForProg(p, compress) {
pc += int64(ins.length()) pc += int64(ins.length())
} }
@ -671,7 +671,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
// a fixed point will be reached). No attempt to handle functions > 2GiB. // a fixed point will be reached). No attempt to handle functions > 2GiB.
for { for {
big, rescan := false, false big, rescan := false, false
maxPC := setPCs(cursym.Func().Text, 0) maxPC := setPCs(cursym.Func().Text, 0, ctxt.CompressInstructions)
if maxPC+maxTrampSize > (1 << 20) { if maxPC+maxTrampSize > (1 << 20) {
big = true big = true
} }
@ -801,7 +801,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
// Validate all instructions - this provides nice error messages. // Validate all instructions - this provides nice error messages.
for p := cursym.Func().Text; p != nil; p = p.Link { for p := cursym.Func().Text; p != nil; p = p.Link {
for _, ins := range instructionsForProg(p) { for _, ins := range instructionsForProg(p, ctxt.CompressInstructions) {
ins.validate(ctxt) ins.validate(ctxt)
} }
} }
@ -1141,6 +1141,14 @@ func wantImmU(ctxt *obj.Link, ins *instruction, imm int64, nbits uint) {
} }
} }
func isScaledImmI(imm int64, nbits uint, scale int64) bool {
return immFits(imm, nbits, true) == nil && imm%scale == 0
}
func isScaledImmU(imm int64, nbits uint, scale int64) bool {
return immFits(imm, nbits, false) == nil && imm%scale == 0
}
func wantScaledImm(ctxt *obj.Link, ins *instruction, imm int64, nbits uint, scale int64, signed bool) { func wantScaledImm(ctxt *obj.Link, ins *instruction, imm int64, nbits uint, scale int64, signed bool) {
if err := immFits(imm, nbits, signed); err != nil { if err := immFits(imm, nbits, signed); err != nil {
ctxt.Diag("%v: %v", ins, err) ctxt.Diag("%v: %v", ins, err)
@ -1180,6 +1188,10 @@ func wantIntReg(ctxt *obj.Link, ins *instruction, pos string, r uint32) {
wantReg(ctxt, ins, pos, "integer", r, REG_X0, REG_X31) wantReg(ctxt, ins, pos, "integer", r, REG_X0, REG_X31)
} }
func isIntPrimeReg(r uint32) bool {
return r >= REG_X8 && r <= REG_X15
}
// wantIntPrimeReg checks that r is an integer register that can be used // wantIntPrimeReg checks that r is an integer register that can be used
// in a prime register field of a compressed instruction. // in a prime register field of a compressed instruction.
func wantIntPrimeReg(ctxt *obj.Link, ins *instruction, pos string, r uint32) { func wantIntPrimeReg(ctxt *obj.Link, ins *instruction, pos string, r uint32) {
@ -1191,6 +1203,10 @@ func wantFloatReg(ctxt *obj.Link, ins *instruction, pos string, r uint32) {
wantReg(ctxt, ins, pos, "float", r, REG_F0, REG_F31) wantReg(ctxt, ins, pos, "float", r, REG_F0, REG_F31)
} }
func isFloatPrimeReg(r uint32) bool {
return r >= REG_F8 && r <= REG_F15
}
// wantFloatPrimeReg checks that r is an floating-point register that can // wantFloatPrimeReg checks that r is an floating-point register that can
// be used in a prime register field of a compressed instruction. // be used in a prime register field of a compressed instruction.
func wantFloatPrimeReg(ctxt *obj.Link, ins *instruction, pos string, r uint32) { func wantFloatPrimeReg(ctxt *obj.Link, ins *instruction, pos string, r uint32) {
@ -3515,6 +3531,147 @@ func (ins *instruction) usesRegTmp() bool {
return ins.rd == REG_TMP || ins.rs1 == REG_TMP || ins.rs2 == REG_TMP return ins.rd == REG_TMP || ins.rs1 == REG_TMP || ins.rs2 == REG_TMP
} }
func (ins *instruction) compress() {
switch ins.as {
case ALW:
if ins.rd != REG_X0 && ins.rs1 == REG_SP && isScaledImmU(ins.imm, 8, 4) {
ins.as, ins.rs1, ins.rs2 = ACLWSP, obj.REG_NONE, ins.rs1
} else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && isScaledImmU(ins.imm, 7, 4) {
ins.as = ACLW
}
case ALD:
if ins.rs1 == REG_SP && ins.rd != REG_X0 && isScaledImmU(ins.imm, 9, 8) {
ins.as, ins.rs1, ins.rs2 = ACLDSP, obj.REG_NONE, ins.rs1
} else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && isScaledImmU(ins.imm, 8, 8) {
ins.as = ACLD
}
case AFLD:
if ins.rs1 == REG_SP && isScaledImmU(ins.imm, 9, 8) {
ins.as, ins.rs1, ins.rs2 = ACFLDSP, obj.REG_NONE, ins.rs1
} else if isFloatPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && isScaledImmU(ins.imm, 8, 8) {
ins.as = ACFLD
}
case ASW:
if ins.rd == REG_SP && isScaledImmU(ins.imm, 8, 4) {
ins.as, ins.rs1, ins.rs2 = ACSWSP, obj.REG_NONE, ins.rs1
} else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && isScaledImmU(ins.imm, 7, 4) {
ins.as, ins.rd, ins.rs1, ins.rs2 = ACSW, obj.REG_NONE, ins.rd, ins.rs1
}
case ASD:
if ins.rd == REG_SP && isScaledImmU(ins.imm, 9, 8) {
ins.as, ins.rs1, ins.rs2 = ACSDSP, obj.REG_NONE, ins.rs1
} else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && isScaledImmU(ins.imm, 8, 8) {
ins.as, ins.rd, ins.rs1, ins.rs2 = ACSD, obj.REG_NONE, ins.rd, ins.rs1
}
case AFSD:
if ins.rd == REG_SP && isScaledImmU(ins.imm, 9, 8) {
ins.as, ins.rs1, ins.rs2 = ACFSDSP, obj.REG_NONE, ins.rs1
} else if isIntPrimeReg(ins.rd) && isFloatPrimeReg(ins.rs1) && isScaledImmU(ins.imm, 8, 8) {
ins.as, ins.rd, ins.rs1, ins.rs2 = ACFSD, obj.REG_NONE, ins.rd, ins.rs1
}
case AADDI:
if ins.rd == REG_SP && ins.rs1 == REG_SP && ins.imm != 0 && isScaledImmI(ins.imm, 10, 16) {
ins.as = ACADDI16SP
} else if ins.rd != REG_X0 && ins.rd == ins.rs1 && ins.imm != 0 && immIFits(ins.imm, 6) == nil {
ins.as = ACADDI
} else if isIntPrimeReg(ins.rd) && ins.rs1 == REG_SP && ins.imm != 0 && isScaledImmU(ins.imm, 10, 4) {
ins.as = ACADDI4SPN
} else if ins.rd != REG_X0 && ins.rs1 == REG_X0 && immIFits(ins.imm, 6) == nil {
ins.as, ins.rs1 = ACLI, obj.REG_NONE
} else if ins.rd != REG_X0 && ins.rs1 != REG_X0 && ins.imm == 0 {
ins.as, ins.rs1, ins.rs2 = ACMV, obj.REG_NONE, ins.rs1
} else if ins.rd == REG_X0 && ins.rs1 == REG_X0 && ins.imm == 0 {
ins.as, ins.rs1 = ACNOP, ins.rd
}
case AADDIW:
if ins.rd == ins.rs1 && immIFits(ins.imm, 6) == nil {
ins.as = ACADDIW
}
case ALUI:
if ins.rd != REG_X0 && ins.rd != REG_SP && ins.imm != 0 && immIFits(ins.imm, 6) == nil {
ins.as = ACLUI
}
case ASLLI:
if ins.rd != REG_X0 && ins.rd == ins.rs1 && ins.imm != 0 {
ins.as = ACSLLI
}
case ASRLI:
if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && ins.imm != 0 {
ins.as = ACSRLI
}
case ASRAI:
if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && ins.imm != 0 {
ins.as = ACSRAI
}
case AANDI:
if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && immIFits(ins.imm, 6) == nil {
ins.as = ACANDI
}
case AADD:
if ins.rd != REG_X0 && ins.rd == ins.rs1 && ins.rs2 != REG_X0 {
ins.as = ACADD
} else if ins.rd != REG_X0 && ins.rd == ins.rs2 && ins.rs1 != REG_X0 {
ins.as, ins.rs1, ins.rs2 = ACADD, ins.rs2, ins.rs1
} else if ins.rd != REG_X0 && ins.rs1 == REG_X0 && ins.rs2 != REG_X0 {
ins.as = ACMV
}
case AADDW:
if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && isIntPrimeReg(ins.rs2) {
ins.as = ACADDW
} else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && ins.rd == ins.rs2 {
ins.as, ins.rs1, ins.rs2 = ACADDW, ins.rs2, ins.rs1
}
case ASUB:
if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && isIntPrimeReg(ins.rs2) {
ins.as = ACSUB
}
case ASUBW:
if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && isIntPrimeReg(ins.rs2) {
ins.as = ACSUBW
}
case AAND:
if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && isIntPrimeReg(ins.rs2) {
ins.as = ACAND
} else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && ins.rd == ins.rs2 {
ins.as, ins.rs1, ins.rs2 = ACAND, ins.rs2, ins.rs1
}
case AOR:
if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && isIntPrimeReg(ins.rs2) {
ins.as = ACOR
} else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && ins.rd == ins.rs2 {
ins.as, ins.rs1, ins.rs2 = ACOR, ins.rs2, ins.rs1
}
case AXOR:
if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && isIntPrimeReg(ins.rs2) {
ins.as = ACXOR
} else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && ins.rd == ins.rs2 {
ins.as, ins.rs1, ins.rs2 = ACXOR, ins.rs2, ins.rs1
}
case AEBREAK:
ins.as, ins.rd, ins.rs1 = ACEBREAK, obj.REG_NONE, obj.REG_NONE
}
}
// instructionForProg returns the default *obj.Prog to instruction mapping. // instructionForProg returns the default *obj.Prog to instruction mapping.
func instructionForProg(p *obj.Prog) *instruction { func instructionForProg(p *obj.Prog) *instruction {
ins := &instruction{ ins := &instruction{
@ -4057,7 +4214,7 @@ func instructionsForMinMax(p *obj.Prog, ins *instruction) []*instruction {
} }
// instructionsForProg returns the machine instructions for an *obj.Prog. // instructionsForProg returns the machine instructions for an *obj.Prog.
func instructionsForProg(p *obj.Prog) []*instruction { func instructionsForProg(p *obj.Prog, compress bool) []*instruction {
ins := instructionForProg(p) ins := instructionForProg(p)
inss := []*instruction{ins} inss := []*instruction{ins}
@ -4710,6 +4867,15 @@ func instructionsForProg(p *obj.Prog) []*instruction {
ins.rs1, ins.rs2 = obj.REG_NONE, REG_V0 ins.rs1, ins.rs2 = obj.REG_NONE, REG_V0
} }
// Only compress instructions when there is no relocation, since
// relocation relies on knowledge about the exact instructions that
// are in use.
if compress && p.Mark&NEED_RELOC == 0 {
for _, ins := range inss {
ins.compress()
}
}
for _, ins := range inss { for _, ins := range inss {
ins.p = p ins.p = p
} }
@ -4799,15 +4965,22 @@ func assemble(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
v := pcAlignPadLength(p.Pc, alignedValue) v := pcAlignPadLength(p.Pc, alignedValue)
offset := p.Pc offset := p.Pc
for ; v >= 4; v -= 4 { for ; v >= 4; v -= 4 {
// NOP // NOP (ADDI $0, X0, X0)
cursym.WriteBytes(ctxt, offset, []byte{0x13, 0, 0, 0}) cursym.WriteBytes(ctxt, offset, []byte{0x13, 0x00, 0x00, 0x00})
offset += 4 offset += 4
} }
if v == 2 {
// CNOP
cursym.WriteBytes(ctxt, offset, []byte{0x01, 0x00})
offset += 2
} else if v != 0 {
ctxt.Diag("bad PCALIGN pad length")
}
continue continue
} }
offset := p.Pc offset := p.Pc
for _, ins := range instructionsForProg(p) { for _, ins := range instructionsForProg(p, ctxt.CompressInstructions) {
if ic, err := ins.encode(); err == nil { if ic, err := ins.encode(); err == nil {
cursym.WriteInt(ctxt, offset, ins.length(), int64(ic)) cursym.WriteInt(ctxt, offset, ins.length(), int64(ic))
offset += int64(ins.length()) offset += int64(ins.length())

View file

@ -423,9 +423,13 @@ func rewriteToUseGot(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
q.From.Reg = reg q.From.Reg = reg
} }
} }
if p.GetFrom3() != nil && p.GetFrom3().Name == obj.NAME_EXTERN { from3 := p.GetFrom3()
for i := range p.RestArgs {
a := &p.RestArgs[i].Addr
if a != from3 && a.Name == obj.NAME_EXTERN && !a.Sym.Local() {
ctxt.Diag("don't know how to handle %v with -dynlink", p) ctxt.Diag("don't know how to handle %v with -dynlink", p)
} }
}
var source *obj.Addr var source *obj.Addr
// MOVx sym, Ry becomes $MOV sym@GOT, R15; MOVx (R15), Ry // MOVx sym, Ry becomes $MOV sym@GOT, R15; MOVx (R15), Ry
// MOVx Ry, sym becomes $MOV sym@GOT, R15; MOVx Ry, (R15) // MOVx Ry, sym becomes $MOV sym@GOT, R15; MOVx Ry, (R15)
@ -434,9 +438,17 @@ func rewriteToUseGot(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
if p.To.Name == obj.NAME_EXTERN && !p.To.Sym.Local() { if p.To.Name == obj.NAME_EXTERN && !p.To.Sym.Local() {
ctxt.Diag("cannot handle NAME_EXTERN on both sides in %v with -dynlink", p) ctxt.Diag("cannot handle NAME_EXTERN on both sides in %v with -dynlink", p)
} }
if from3 != nil && from3.Name == obj.NAME_EXTERN && !from3.Sym.Local() {
ctxt.Diag("cannot handle NAME_EXTERN on multiple operands in %v with -dynlink", p)
}
source = &p.From source = &p.From
} else if p.To.Name == obj.NAME_EXTERN && !p.To.Sym.Local() { } else if p.To.Name == obj.NAME_EXTERN && !p.To.Sym.Local() {
if from3 != nil && from3.Name == obj.NAME_EXTERN && !from3.Sym.Local() {
ctxt.Diag("cannot handle NAME_EXTERN on multiple operands in %v with -dynlink", p)
}
source = &p.To source = &p.To
} else if from3 != nil && from3.Name == obj.NAME_EXTERN && !from3.Sym.Local() {
source = from3
} else { } else {
return return
} }
@ -501,9 +513,7 @@ func rewriteToUseGot(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
p2.As = p.As p2.As = p.As
p2.From = p.From p2.From = p.From
p2.To = p.To p2.To = p.To
if from3 := p.GetFrom3(); from3 != nil { p2.RestArgs = p.RestArgs
p2.AddRestSource(*from3)
}
if p.From.Name == obj.NAME_EXTERN { if p.From.Name == obj.NAME_EXTERN {
p2.From.Reg = reg p2.From.Reg = reg
p2.From.Name = obj.NAME_NONE p2.From.Name = obj.NAME_NONE
@ -512,6 +522,11 @@ func rewriteToUseGot(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
p2.To.Reg = reg p2.To.Reg = reg
p2.To.Name = obj.NAME_NONE p2.To.Name = obj.NAME_NONE
p2.To.Sym = nil p2.To.Sym = nil
} else if p.GetFrom3() != nil && p.GetFrom3().Name == obj.NAME_EXTERN {
from3 = p2.GetFrom3()
from3.Reg = reg
from3.Name = obj.NAME_NONE
from3.Sym = nil
} else { } else {
return return
} }

View file

@ -236,7 +236,7 @@ var ArchRISCV64 = &Arch{
ByteOrder: binary.LittleEndian, ByteOrder: binary.LittleEndian,
PtrSize: 8, PtrSize: 8,
RegSize: 8, RegSize: 8,
MinLC: 4, MinLC: 2,
Alignment: 8, // riscv unaligned loads work, but are really slow (trap + simulated by OS) Alignment: 8, // riscv unaligned loads work, but are really slow (trap + simulated by OS)
CanMergeLoads: false, CanMergeLoads: false,
HasLR: true, HasLR: true,

View file

@ -2507,19 +2507,19 @@ func dwarfcompress(ctxt *Link) {
var prevSect *sym.Section var prevSect *sym.Section
for _, si := range dwarfp { for _, si := range dwarfp {
for _, s := range si.syms { for _, s := range si.syms {
ldr.SetSymValue(s, int64(pos))
sect := ldr.SymSect(s) sect := ldr.SymSect(s)
if sect != prevSect { if sect != prevSect {
if ctxt.IsWindows() {
pos = uint64(Rnd(int64(pos), PEFILEALIGN))
}
sect.Vaddr = pos sect.Vaddr = pos
prevSect = sect prevSect = sect
} }
ldr.SetSymValue(s, int64(pos))
if ldr.SubSym(s) != 0 { if ldr.SubSym(s) != 0 {
log.Fatalf("%s: unexpected sub-symbols", ldr.SymName(s)) log.Fatalf("%s: unexpected sub-symbols", ldr.SymName(s))
} }
pos += uint64(ldr.SymSize(s)) pos += uint64(ldr.SymSize(s))
if ctxt.IsWindows() {
pos = uint64(Rnd(int64(pos), PEFILEALIGN))
}
} }
} }
Segdwarf.Length = pos - Segdwarf.Vaddr Segdwarf.Length = pos - Segdwarf.Vaddr

View file

@ -387,7 +387,7 @@ func TestRISCVTrampolines(t *testing.T) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
fmt.Fprintf(buf, "TEXT a(SB),$0-0\n") fmt.Fprintf(buf, "TEXT a(SB),$0-0\n")
for i := 0; i < 1<<17; i++ { for i := 0; i < 1<<17; i++ {
fmt.Fprintf(buf, "\tADD $0, X0, X0\n") fmt.Fprintf(buf, "\tADD $0, X5, X0\n")
} }
fmt.Fprintf(buf, "\tCALL b(SB)\n") fmt.Fprintf(buf, "\tCALL b(SB)\n")
fmt.Fprintf(buf, "\tRET\n") fmt.Fprintf(buf, "\tRET\n")
@ -398,7 +398,7 @@ func TestRISCVTrampolines(t *testing.T) {
fmt.Fprintf(buf, "\tRET\n") fmt.Fprintf(buf, "\tRET\n")
fmt.Fprintf(buf, "TEXT ·d(SB),0,$0-0\n") fmt.Fprintf(buf, "TEXT ·d(SB),0,$0-0\n")
for i := 0; i < 1<<17; i++ { for i := 0; i < 1<<17; i++ {
fmt.Fprintf(buf, "\tADD $0, X0, X0\n") fmt.Fprintf(buf, "\tADD $0, X5, X0\n")
} }
fmt.Fprintf(buf, "\tCALL a(SB)\n") fmt.Fprintf(buf, "\tCALL a(SB)\n")
fmt.Fprintf(buf, "\tCALL c(SB)\n") fmt.Fprintf(buf, "\tCALL c(SB)\n")

View file

@ -188,7 +188,11 @@ func Main(arch *sys.Arch, theArch Arch) {
buildVersion := buildcfg.Version buildVersion := buildcfg.Version
if goexperiment := buildcfg.Experiment.String(); goexperiment != "" { if goexperiment := buildcfg.Experiment.String(); goexperiment != "" {
buildVersion += " X:" + goexperiment sep := " "
if !strings.Contains(buildVersion, "-") { // See go.dev/issue/75953.
sep = "-"
}
buildVersion += sep + "X:" + goexperiment
} }
addstrdata1(ctxt, "runtime.buildVersion="+buildVersion) addstrdata1(ctxt, "runtime.buildVersion="+buildVersion)

View file

@ -2464,10 +2464,11 @@ var blockedLinknames = map[string][]string{
// Experimental features // Experimental features
"runtime.goroutineLeakGC": {"runtime/pprof"}, "runtime.goroutineLeakGC": {"runtime/pprof"},
"runtime.goroutineleakcount": {"runtime/pprof"}, "runtime.goroutineleakcount": {"runtime/pprof"},
"runtime.freegc": {}, // disallow all packages
// Others // Others
"net.newWindowsFile": {"net"}, // pushed from os "net.newWindowsFile": {"net"}, // pushed from os
"testing/synctest.testingSynctestTest": {"testing/synctest"}, // pushed from testing "testing/synctest.testingSynctestTest": {"testing/synctest"}, // pushed from testing
"runtime.addmoduledata": {}, // disallow all package "runtime.addmoduledata": {}, // disallow all packages
} }
// check if a linkname reference to symbol s from pkg is allowed // check if a linkname reference to symbol s from pkg is allowed

View file

@ -1616,6 +1616,7 @@ func TestCheckLinkname(t *testing.T) {
// pull linkname of a builtin symbol is not ok // pull linkname of a builtin symbol is not ok
{"builtin.go", false}, {"builtin.go", false},
{"addmoduledata.go", false}, {"addmoduledata.go", false},
{"freegc.go", false},
// legacy bad linkname is ok, for now // legacy bad linkname is ok, for now
{"fastrand.go", true}, {"fastrand.go", true},
{"badlinkname.go", true}, {"badlinkname.go", true},

View file

@ -0,0 +1,18 @@
// Copyright 2025 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.
// Linkname runtime.freegc is not allowed.
package main
import (
_ "unsafe"
)
//go:linkname freegc runtime.freegc
func freegc()
func main() {
freegc()
}

View file

@ -143,6 +143,13 @@ func (g *procGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
viewerEv := traceviewer.InstantEvent{ viewerEv := traceviewer.InstantEvent{
Resource: uint64(proc), Resource: uint64(proc),
Stack: ctx.Stack(viewerFrames(ev.Stack())), Stack: ctx.Stack(viewerFrames(ev.Stack())),
// Annotate with the thread and proc. The proc is redundant, but this is to
// stay consistent with the thread view, where it's useful information.
Arg: format.SchedCtxArg{
ProcID: uint64(st.Resource.Proc()),
ThreadID: uint64(ev.Thread()),
},
} }
from, to := st.Proc() from, to := st.Proc()
@ -156,7 +163,6 @@ func (g *procGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
start = ctx.startTime start = ctx.startTime
} }
viewerEv.Name = "proc start" viewerEv.Name = "proc start"
viewerEv.Arg = format.ThreadIDArg{ThreadID: uint64(ev.Thread())}
viewerEv.Ts = ctx.elapsed(start) viewerEv.Ts = ctx.elapsed(start)
ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, 1) ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, 1)
} }

View file

@ -138,14 +138,17 @@ func (g *threadGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
} }
} }
type procArg struct {
Proc uint64 `json:"proc,omitempty"`
}
st := ev.StateTransition() st := ev.StateTransition()
viewerEv := traceviewer.InstantEvent{ viewerEv := traceviewer.InstantEvent{
Resource: uint64(ev.Thread()), Resource: uint64(ev.Thread()),
Stack: ctx.Stack(viewerFrames(ev.Stack())), Stack: ctx.Stack(viewerFrames(ev.Stack())),
Arg: procArg{Proc: uint64(st.Resource.Proc())},
// Annotate with the thread and proc. The thread is redundant, but this is to
// stay consistent with the proc view.
Arg: format.SchedCtxArg{
ProcID: uint64(st.Resource.Proc()),
ThreadID: uint64(ev.Thread()),
},
} }
from, to := st.Proc() from, to := st.Proc()
@ -159,7 +162,6 @@ func (g *threadGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
start = ctx.startTime start = ctx.startTime
} }
viewerEv.Name = "proc start" viewerEv.Name = "proc start"
viewerEv.Arg = format.ThreadIDArg{ThreadID: uint64(ev.Thread())}
viewerEv.Ts = ctx.elapsed(start) viewerEv.Ts = ctx.elapsed(start)
// TODO(mknyszek): We don't have a state machine for threads, so approximate // TODO(mknyszek): We don't have a state machine for threads, so approximate
// running threads with running Ps. // running threads with running Ps.

View file

@ -33,7 +33,7 @@ type printer struct {
} }
// printf prints to the buffer. // printf prints to the buffer.
func (p *printer) printf(format string, args ...interface{}) { func (p *printer) printf(format string, args ...any) {
fmt.Fprintf(p, format, args...) fmt.Fprintf(p, format, args...)
} }

Some files were not shown because too many files have changed in this diff Show more