2016-10-13 06:57:00 -04:00
|
|
|
// Copyright 2016 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 ssa
|
|
|
|
|
|
2016-12-06 17:08:06 -08:00
|
|
|
import (
|
2020-12-29 01:22:50 -08:00
|
|
|
"cmd/compile/internal/reflectdata"
|
cmd/compile: change ssa.Type into *types.Type
When package ssa was created, Type was in package gc.
To avoid circular dependencies, we used an interface (ssa.Type)
to represent type information in SSA.
In the Go 1.9 cycle, gri extricated the Type type from package gc.
As a result, we can now use it in package ssa.
Now, instead of package types depending on package ssa,
it is the other way.
This is a more sensible dependency tree,
and helps compiler performance a bit.
Though this is a big CL, most of the changes are
mechanical and uninteresting.
Interesting bits:
* Add new singleton globals to package types for the special
SSA types Memory, Void, Invalid, Flags, and Int128.
* Add two new Types, TSSA for the special types,
and TTUPLE, for SSA tuple types.
ssa.MakeTuple is now types.NewTuple.
* Move type comparison result constants CMPlt, CMPeq, and CMPgt
to package types.
* We had picked the name "types" in our rules for the handy
list of types provided by ssa.Config. That conflicted with
the types package name, so change it to "typ".
* Update the type comparison routine to handle tuples and special
types inline.
* Teach gc/fmt.go how to print special types.
* We can now eliminate ElemTypes in favor of just Elem,
and probably also some other duplicated Type methods
designed to return ssa.Type instead of *types.Type.
* The ssa tests were using their own dummy types,
and they were not particularly careful about types in general.
Of necessity, this CL switches them to use *types.Type;
it does not make them more type-accurate.
Unfortunately, using types.Type means initializing a bit
of the types universe.
This is prime for refactoring and improvement.
This shrinks ssa.Value; it now fits in a smaller size class
on 64 bit systems. This doesn't have a giant impact,
though, since most Values are preallocated in a chunk.
name old alloc/op new alloc/op delta
Template 37.9MB ± 0% 37.7MB ± 0% -0.57% (p=0.000 n=10+8)
Unicode 28.9MB ± 0% 28.7MB ± 0% -0.52% (p=0.000 n=10+10)
GoTypes 110MB ± 0% 109MB ± 0% -0.88% (p=0.000 n=10+10)
Flate 24.7MB ± 0% 24.6MB ± 0% -0.66% (p=0.000 n=10+10)
GoParser 31.1MB ± 0% 30.9MB ± 0% -0.61% (p=0.000 n=10+9)
Reflect 73.9MB ± 0% 73.4MB ± 0% -0.62% (p=0.000 n=10+8)
Tar 25.8MB ± 0% 25.6MB ± 0% -0.77% (p=0.000 n=9+10)
XML 41.2MB ± 0% 40.9MB ± 0% -0.80% (p=0.000 n=10+10)
[Geo mean] 40.5MB 40.3MB -0.68%
name old allocs/op new allocs/op delta
Template 385k ± 0% 386k ± 0% ~ (p=0.356 n=10+9)
Unicode 343k ± 1% 344k ± 0% ~ (p=0.481 n=10+10)
GoTypes 1.16M ± 0% 1.16M ± 0% -0.16% (p=0.004 n=10+10)
Flate 238k ± 1% 238k ± 1% ~ (p=0.853 n=10+10)
GoParser 320k ± 0% 320k ± 0% ~ (p=0.720 n=10+9)
Reflect 957k ± 0% 957k ± 0% ~ (p=0.460 n=10+8)
Tar 252k ± 0% 252k ± 0% ~ (p=0.133 n=9+10)
XML 400k ± 0% 400k ± 0% ~ (p=0.796 n=10+10)
[Geo mean] 428k 428k -0.01%
Removing all the interface calls helps non-trivially with CPU, though.
name old time/op new time/op delta
Template 178ms ± 4% 173ms ± 3% -2.90% (p=0.000 n=94+96)
Unicode 85.0ms ± 4% 83.9ms ± 4% -1.23% (p=0.000 n=96+96)
GoTypes 543ms ± 3% 528ms ± 3% -2.73% (p=0.000 n=98+96)
Flate 116ms ± 3% 113ms ± 4% -2.34% (p=0.000 n=96+99)
GoParser 144ms ± 3% 140ms ± 4% -2.80% (p=0.000 n=99+97)
Reflect 344ms ± 3% 334ms ± 4% -3.02% (p=0.000 n=100+99)
Tar 106ms ± 5% 103ms ± 4% -3.30% (p=0.000 n=98+94)
XML 198ms ± 5% 192ms ± 4% -2.88% (p=0.000 n=92+95)
[Geo mean] 178ms 173ms -2.65%
name old user-time/op new user-time/op delta
Template 229ms ± 5% 224ms ± 5% -2.36% (p=0.000 n=95+99)
Unicode 107ms ± 6% 106ms ± 5% -1.13% (p=0.001 n=93+95)
GoTypes 696ms ± 4% 679ms ± 4% -2.45% (p=0.000 n=97+99)
Flate 137ms ± 4% 134ms ± 5% -2.66% (p=0.000 n=99+96)
GoParser 176ms ± 5% 172ms ± 8% -2.27% (p=0.000 n=98+100)
Reflect 430ms ± 6% 411ms ± 5% -4.46% (p=0.000 n=100+92)
Tar 128ms ±13% 123ms ±13% -4.21% (p=0.000 n=100+100)
XML 239ms ± 6% 233ms ± 6% -2.50% (p=0.000 n=95+97)
[Geo mean] 220ms 213ms -2.76%
Change-Id: I15c7d6268347f8358e75066dfdbd77db24e8d0c1
Reviewed-on: https://go-review.googlesource.com/42145
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-04-28 14:12:28 -07:00
|
|
|
"cmd/compile/internal/types"
|
2017-02-06 13:30:40 -08:00
|
|
|
"cmd/internal/obj"
|
2020-04-28 10:20:55 -07:00
|
|
|
"cmd/internal/objabi"
|
2016-12-06 17:08:06 -08:00
|
|
|
"cmd/internal/src"
|
2019-10-06 17:38:39 -07:00
|
|
|
"fmt"
|
2022-10-20 14:20:41 -07:00
|
|
|
"internal/buildcfg"
|
2016-12-06 17:08:06 -08:00
|
|
|
)
|
2016-10-13 06:57:00 -04:00
|
|
|
|
2019-10-06 17:38:39 -07:00
|
|
|
// A ZeroRegion records parts of an object which are known to be zero.
|
2019-01-04 17:34:33 -08:00
|
|
|
// A ZeroRegion only applies to a single memory state.
|
2019-10-06 17:38:39 -07:00
|
|
|
// Each bit in mask is set if the corresponding pointer-sized word of
|
|
|
|
|
// the base object is known to be zero.
|
|
|
|
|
// In other words, if mask & (1<<i) != 0, then [base+i*ptrSize, base+(i+1)*ptrSize)
|
|
|
|
|
// is known to be zero.
|
2019-01-04 17:34:33 -08:00
|
|
|
type ZeroRegion struct {
|
|
|
|
|
base *Value
|
2019-10-06 17:38:39 -07:00
|
|
|
mask uint64
|
2019-01-04 17:34:33 -08:00
|
|
|
}
|
|
|
|
|
|
2022-10-09 18:43:34 -07:00
|
|
|
// mightBeHeapPointer reports whether v might point to the heap.
|
|
|
|
|
// v must have pointer type.
|
|
|
|
|
func mightBeHeapPointer(v *Value) bool {
|
|
|
|
|
if IsGlobalAddr(v) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// mightContainHeapPointer reports whether the data currently at addresses
|
|
|
|
|
// [ptr,ptr+size) might contain heap pointers. "currently" means at memory state mem.
|
|
|
|
|
// zeroes contains ZeroRegion data to help make that decision (see computeZeroMap).
|
|
|
|
|
func mightContainHeapPointer(ptr *Value, size int64, mem *Value, zeroes map[ID]ZeroRegion) bool {
|
|
|
|
|
if IsReadOnlyGlobalAddr(ptr) {
|
|
|
|
|
// The read-only globals section cannot contain any heap pointers.
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// See if we can prove that the queried memory is all zero.
|
|
|
|
|
|
|
|
|
|
// Find base pointer and offset. Hopefully, the base is the result of a new(T).
|
|
|
|
|
var off int64
|
|
|
|
|
for ptr.Op == OpOffPtr {
|
|
|
|
|
off += ptr.AuxInt
|
|
|
|
|
ptr = ptr.Args[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ptrSize := ptr.Block.Func.Config.PtrSize
|
2023-07-08 08:46:37 -07:00
|
|
|
if off%ptrSize != 0 {
|
|
|
|
|
return true // see issue 61187
|
|
|
|
|
}
|
|
|
|
|
if size%ptrSize != 0 {
|
2022-10-09 18:43:34 -07:00
|
|
|
ptr.Fatalf("unaligned pointer write")
|
|
|
|
|
}
|
|
|
|
|
if off < 0 || off+size > 64*ptrSize {
|
|
|
|
|
// memory range goes off end of tracked offsets
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
z := zeroes[mem.ID]
|
|
|
|
|
if ptr != z.base {
|
|
|
|
|
// This isn't the object we know about at this memory state.
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
// Mask of bits we're asking about
|
|
|
|
|
m := (uint64(1)<<(size/ptrSize) - 1) << (off / ptrSize)
|
|
|
|
|
|
|
|
|
|
if z.mask&m == m {
|
|
|
|
|
// All locations are known to be zero, so no heap pointers.
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-22 11:46:44 +01:00
|
|
|
// needwb reports whether we need write barrier for store op v.
|
2017-02-05 23:43:31 -05:00
|
|
|
// v must be Store/Move/Zero.
|
2019-01-04 17:34:33 -08:00
|
|
|
// zeroes provides known zero information (keyed by ID of memory-type values).
|
2022-10-09 18:43:34 -07:00
|
|
|
func needwb(v *Value, zeroes map[ID]ZeroRegion) bool {
|
cmd/compile: change ssa.Type into *types.Type
When package ssa was created, Type was in package gc.
To avoid circular dependencies, we used an interface (ssa.Type)
to represent type information in SSA.
In the Go 1.9 cycle, gri extricated the Type type from package gc.
As a result, we can now use it in package ssa.
Now, instead of package types depending on package ssa,
it is the other way.
This is a more sensible dependency tree,
and helps compiler performance a bit.
Though this is a big CL, most of the changes are
mechanical and uninteresting.
Interesting bits:
* Add new singleton globals to package types for the special
SSA types Memory, Void, Invalid, Flags, and Int128.
* Add two new Types, TSSA for the special types,
and TTUPLE, for SSA tuple types.
ssa.MakeTuple is now types.NewTuple.
* Move type comparison result constants CMPlt, CMPeq, and CMPgt
to package types.
* We had picked the name "types" in our rules for the handy
list of types provided by ssa.Config. That conflicted with
the types package name, so change it to "typ".
* Update the type comparison routine to handle tuples and special
types inline.
* Teach gc/fmt.go how to print special types.
* We can now eliminate ElemTypes in favor of just Elem,
and probably also some other duplicated Type methods
designed to return ssa.Type instead of *types.Type.
* The ssa tests were using their own dummy types,
and they were not particularly careful about types in general.
Of necessity, this CL switches them to use *types.Type;
it does not make them more type-accurate.
Unfortunately, using types.Type means initializing a bit
of the types universe.
This is prime for refactoring and improvement.
This shrinks ssa.Value; it now fits in a smaller size class
on 64 bit systems. This doesn't have a giant impact,
though, since most Values are preallocated in a chunk.
name old alloc/op new alloc/op delta
Template 37.9MB ± 0% 37.7MB ± 0% -0.57% (p=0.000 n=10+8)
Unicode 28.9MB ± 0% 28.7MB ± 0% -0.52% (p=0.000 n=10+10)
GoTypes 110MB ± 0% 109MB ± 0% -0.88% (p=0.000 n=10+10)
Flate 24.7MB ± 0% 24.6MB ± 0% -0.66% (p=0.000 n=10+10)
GoParser 31.1MB ± 0% 30.9MB ± 0% -0.61% (p=0.000 n=10+9)
Reflect 73.9MB ± 0% 73.4MB ± 0% -0.62% (p=0.000 n=10+8)
Tar 25.8MB ± 0% 25.6MB ± 0% -0.77% (p=0.000 n=9+10)
XML 41.2MB ± 0% 40.9MB ± 0% -0.80% (p=0.000 n=10+10)
[Geo mean] 40.5MB 40.3MB -0.68%
name old allocs/op new allocs/op delta
Template 385k ± 0% 386k ± 0% ~ (p=0.356 n=10+9)
Unicode 343k ± 1% 344k ± 0% ~ (p=0.481 n=10+10)
GoTypes 1.16M ± 0% 1.16M ± 0% -0.16% (p=0.004 n=10+10)
Flate 238k ± 1% 238k ± 1% ~ (p=0.853 n=10+10)
GoParser 320k ± 0% 320k ± 0% ~ (p=0.720 n=10+9)
Reflect 957k ± 0% 957k ± 0% ~ (p=0.460 n=10+8)
Tar 252k ± 0% 252k ± 0% ~ (p=0.133 n=9+10)
XML 400k ± 0% 400k ± 0% ~ (p=0.796 n=10+10)
[Geo mean] 428k 428k -0.01%
Removing all the interface calls helps non-trivially with CPU, though.
name old time/op new time/op delta
Template 178ms ± 4% 173ms ± 3% -2.90% (p=0.000 n=94+96)
Unicode 85.0ms ± 4% 83.9ms ± 4% -1.23% (p=0.000 n=96+96)
GoTypes 543ms ± 3% 528ms ± 3% -2.73% (p=0.000 n=98+96)
Flate 116ms ± 3% 113ms ± 4% -2.34% (p=0.000 n=96+99)
GoParser 144ms ± 3% 140ms ± 4% -2.80% (p=0.000 n=99+97)
Reflect 344ms ± 3% 334ms ± 4% -3.02% (p=0.000 n=100+99)
Tar 106ms ± 5% 103ms ± 4% -3.30% (p=0.000 n=98+94)
XML 198ms ± 5% 192ms ± 4% -2.88% (p=0.000 n=92+95)
[Geo mean] 178ms 173ms -2.65%
name old user-time/op new user-time/op delta
Template 229ms ± 5% 224ms ± 5% -2.36% (p=0.000 n=95+99)
Unicode 107ms ± 6% 106ms ± 5% -1.13% (p=0.001 n=93+95)
GoTypes 696ms ± 4% 679ms ± 4% -2.45% (p=0.000 n=97+99)
Flate 137ms ± 4% 134ms ± 5% -2.66% (p=0.000 n=99+96)
GoParser 176ms ± 5% 172ms ± 8% -2.27% (p=0.000 n=98+100)
Reflect 430ms ± 6% 411ms ± 5% -4.46% (p=0.000 n=100+92)
Tar 128ms ±13% 123ms ±13% -4.21% (p=0.000 n=100+100)
XML 239ms ± 6% 233ms ± 6% -2.50% (p=0.000 n=95+97)
[Geo mean] 220ms 213ms -2.76%
Change-Id: I15c7d6268347f8358e75066dfdbd77db24e8d0c1
Reviewed-on: https://go-review.googlesource.com/42145
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-04-28 14:12:28 -07:00
|
|
|
t, ok := v.Aux.(*types.Type)
|
2017-02-05 23:43:31 -05:00
|
|
|
if !ok {
|
|
|
|
|
v.Fatalf("store aux is not a type: %s", v.LongString())
|
|
|
|
|
}
|
2020-08-21 20:20:12 -07:00
|
|
|
if !t.HasPointers() {
|
2017-02-05 23:43:31 -05:00
|
|
|
return false
|
|
|
|
|
}
|
2022-10-09 18:43:34 -07:00
|
|
|
dst := v.Args[0]
|
|
|
|
|
if IsStackAddr(dst) {
|
|
|
|
|
return false // writes into the stack don't need write barrier
|
2017-02-05 23:43:31 -05:00
|
|
|
}
|
2022-10-09 18:43:34 -07:00
|
|
|
// If we're writing to a place that might have heap pointers, we need
|
|
|
|
|
// the write barrier.
|
|
|
|
|
if mightContainHeapPointer(dst, t.Size(), v.MemoryArg(), zeroes) {
|
|
|
|
|
return true
|
2018-11-27 12:40:16 -08:00
|
|
|
}
|
2022-10-09 18:43:34 -07:00
|
|
|
// Lastly, check if the values we're writing might be heap pointers.
|
|
|
|
|
// If they aren't, we don't need a write barrier.
|
|
|
|
|
switch v.Op {
|
|
|
|
|
case OpStore:
|
|
|
|
|
if !mightBeHeapPointer(v.Args[1]) {
|
|
|
|
|
return false
|
2019-10-06 17:38:39 -07:00
|
|
|
}
|
2022-10-09 18:43:34 -07:00
|
|
|
case OpZero:
|
|
|
|
|
return false // nil is not a heap pointer
|
|
|
|
|
case OpMove:
|
|
|
|
|
if !mightContainHeapPointer(v.Args[1], t.Size(), v.Args[2], zeroes) {
|
|
|
|
|
return false
|
2019-01-04 17:34:33 -08:00
|
|
|
}
|
2022-10-09 18:43:34 -07:00
|
|
|
default:
|
|
|
|
|
v.Fatalf("store op unknown: %s", v.LongString())
|
2019-01-04 17:34:33 -08:00
|
|
|
}
|
2017-02-05 23:43:31 -05:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-01 16:46:43 -07:00
|
|
|
// needWBsrc reports whether GC needs to see v when it is the source of a store.
|
|
|
|
|
func needWBsrc(v *Value) bool {
|
|
|
|
|
return !IsGlobalAddr(v)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// needWBdst reports whether GC needs to see what used to be in *ptr when ptr is
|
|
|
|
|
// the target of a pointer store.
|
|
|
|
|
func needWBdst(ptr, mem *Value, zeroes map[ID]ZeroRegion) bool {
|
|
|
|
|
// Detect storing to zeroed memory.
|
|
|
|
|
var off int64
|
|
|
|
|
for ptr.Op == OpOffPtr {
|
|
|
|
|
off += ptr.AuxInt
|
|
|
|
|
ptr = ptr.Args[0]
|
|
|
|
|
}
|
|
|
|
|
ptrSize := ptr.Block.Func.Config.PtrSize
|
|
|
|
|
if off%ptrSize != 0 {
|
2023-07-08 08:46:37 -07:00
|
|
|
return true // see issue 61187
|
2022-11-01 16:46:43 -07:00
|
|
|
}
|
|
|
|
|
if off < 0 || off >= 64*ptrSize {
|
|
|
|
|
// write goes off end of tracked offsets
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
z := zeroes[mem.ID]
|
|
|
|
|
if ptr != z.base {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
// If destination is known to be zeroed, we don't need the write barrier
|
|
|
|
|
// to record the old value in *ptr.
|
|
|
|
|
return z.mask>>uint(off/ptrSize)&1 == 0
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-05 23:43:31 -05:00
|
|
|
// writebarrier pass inserts write barriers for store ops (Store, Move, Zero)
|
|
|
|
|
// when necessary (the condition above). It rewrites store ops to branches
|
|
|
|
|
// and runtime calls, like
|
2016-10-13 06:57:00 -04:00
|
|
|
//
|
2022-01-29 19:07:27 -05:00
|
|
|
// if writeBarrier.enabled {
|
2022-11-01 16:46:43 -07:00
|
|
|
// buf := gcWriteBarrier2() // Not a regular Go call
|
|
|
|
|
// buf[0] = val
|
|
|
|
|
// buf[1] = *ptr
|
2022-01-29 19:07:27 -05:00
|
|
|
// }
|
2022-11-01 16:46:43 -07:00
|
|
|
// *ptr = val
|
2016-10-13 06:57:00 -04:00
|
|
|
//
|
|
|
|
|
// A sequence of WB stores for many pointer fields of a single type will
|
|
|
|
|
// be emitted together, with a single branch.
|
|
|
|
|
func writebarrier(f *Func) {
|
2017-03-16 22:42:10 -07:00
|
|
|
if !f.fe.UseWriteBarrier() {
|
2017-02-05 23:43:31 -05:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-01 16:46:43 -07:00
|
|
|
// Number of write buffer entries we can request at once.
|
|
|
|
|
// Must match runtime/mwbbuf.go:wbMaxEntriesPerCall.
|
|
|
|
|
// It must also match the number of instances of runtime.gcWriteBarrier{X}.
|
|
|
|
|
const maxEntries = 8
|
|
|
|
|
|
2017-02-01 14:27:40 -05:00
|
|
|
var sb, sp, wbaddr, const0 *Value
|
2022-11-01 16:46:43 -07:00
|
|
|
var cgoCheckPtrWrite, cgoCheckMemmove *obj.LSym
|
2022-11-01 14:18:09 -07:00
|
|
|
var wbZero, wbMove *obj.LSym
|
2017-02-01 14:27:40 -05:00
|
|
|
var stores, after []*Value
|
2022-11-01 16:46:43 -07:00
|
|
|
var sset, sset2 *sparseSet
|
2017-02-01 14:27:40 -05:00
|
|
|
var storeNumber []int32
|
|
|
|
|
|
2023-01-09 09:49:32 -08:00
|
|
|
// Compute map from a value to the SelectN [1] value that uses it.
|
|
|
|
|
select1 := f.Cache.allocValueSlice(f.NumValues())
|
|
|
|
|
defer func() { f.Cache.freeValueSlice(select1) }()
|
|
|
|
|
for _, b := range f.Blocks {
|
|
|
|
|
for _, v := range b.Values {
|
|
|
|
|
if v.Op != OpSelectN {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if v.AuxInt != 1 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
select1[v.Args[0].ID] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
zeroes := f.computeZeroMap(select1)
|
2017-02-01 14:27:40 -05:00
|
|
|
for _, b := range f.Blocks { // range loop is safe since the blocks we added contain no stores to expand
|
2017-02-05 23:43:31 -05:00
|
|
|
// first, identify all the stores that need to insert a write barrier.
|
|
|
|
|
// mark them with WB ops temporarily. record presence of WB ops.
|
2017-08-30 11:12:52 -04:00
|
|
|
nWBops := 0 // count of temporarily created WB ops remaining to be rewritten in the current block
|
2017-02-01 14:27:40 -05:00
|
|
|
for _, v := range b.Values {
|
2016-10-13 06:57:00 -04:00
|
|
|
switch v.Op {
|
2017-02-05 23:43:31 -05:00
|
|
|
case OpStore, OpMove, OpZero:
|
2022-10-09 18:43:34 -07:00
|
|
|
if needwb(v, zeroes) {
|
2016-10-13 06:57:00 -04:00
|
|
|
switch v.Op {
|
2017-02-05 23:43:31 -05:00
|
|
|
case OpStore:
|
|
|
|
|
v.Op = OpStoreWB
|
|
|
|
|
case OpMove:
|
|
|
|
|
v.Op = OpMoveWB
|
|
|
|
|
case OpZero:
|
|
|
|
|
v.Op = OpZeroWB
|
2016-10-13 06:57:00 -04:00
|
|
|
}
|
2017-08-30 11:12:52 -04:00
|
|
|
nWBops++
|
2016-10-13 06:57:00 -04:00
|
|
|
}
|
2017-02-01 14:27:40 -05:00
|
|
|
}
|
|
|
|
|
}
|
2017-08-30 11:12:52 -04:00
|
|
|
if nWBops == 0 {
|
2017-02-01 14:27:40 -05:00
|
|
|
continue
|
|
|
|
|
}
|
2016-10-13 06:57:00 -04:00
|
|
|
|
2017-02-01 14:27:40 -05:00
|
|
|
if wbaddr == nil {
|
|
|
|
|
// lazily initialize global values for write barrier test and calls
|
|
|
|
|
// find SB and SP values in entry block
|
|
|
|
|
initpos := f.Entry.Pos
|
2020-07-09 15:47:26 -04:00
|
|
|
sp, sb = f.spSb()
|
2017-09-18 14:53:56 -07:00
|
|
|
wbsym := f.fe.Syslook("writeBarrier")
|
2017-03-18 10:16:03 -07:00
|
|
|
wbaddr = f.Entry.NewValue1A(initpos, OpAddr, f.Config.Types.UInt32Ptr, wbsym, sb)
|
2022-11-01 14:18:09 -07:00
|
|
|
wbZero = f.fe.Syslook("wbZero")
|
|
|
|
|
wbMove = f.fe.Syslook("wbMove")
|
2022-10-20 14:20:41 -07:00
|
|
|
if buildcfg.Experiment.CgoCheck2 {
|
|
|
|
|
cgoCheckPtrWrite = f.fe.Syslook("cgoCheckPtrWrite")
|
|
|
|
|
cgoCheckMemmove = f.fe.Syslook("cgoCheckMemmove")
|
|
|
|
|
}
|
2018-04-03 15:52:30 +01:00
|
|
|
const0 = f.ConstInt32(f.Config.Types.UInt32, 0)
|
2017-02-01 14:27:40 -05:00
|
|
|
|
|
|
|
|
// allocate auxiliary data structures for computing store order
|
|
|
|
|
sset = f.newSparseSet(f.NumValues())
|
|
|
|
|
defer f.retSparseSet(sset)
|
2022-11-01 16:46:43 -07:00
|
|
|
sset2 = f.newSparseSet(f.NumValues())
|
|
|
|
|
defer f.retSparseSet(sset2)
|
2022-10-18 16:07:36 -07:00
|
|
|
storeNumber = f.Cache.allocInt32Slice(f.NumValues())
|
|
|
|
|
defer f.Cache.freeInt32Slice(storeNumber)
|
2017-02-01 14:27:40 -05:00
|
|
|
}
|
2016-10-13 06:57:00 -04:00
|
|
|
|
2017-02-01 14:27:40 -05:00
|
|
|
// order values in store order
|
|
|
|
|
b.Values = storeOrder(b.Values, sset, storeNumber)
|
|
|
|
|
again:
|
|
|
|
|
// find the start and end of the last contiguous WB store sequence.
|
|
|
|
|
// a branch will be inserted there. values after it will be moved
|
|
|
|
|
// to a new block.
|
|
|
|
|
var last *Value
|
|
|
|
|
var start, end int
|
2023-08-18 12:44:37 -07:00
|
|
|
var nonPtrStores int
|
2017-02-01 14:27:40 -05:00
|
|
|
values := b.Values
|
2025-01-10 17:33:26 -08:00
|
|
|
hasMove := false
|
2017-04-27 11:56:46 -07:00
|
|
|
FindSeq:
|
2017-02-01 14:27:40 -05:00
|
|
|
for i := len(values) - 1; i >= 0; i-- {
|
|
|
|
|
w := values[i]
|
2017-04-27 11:56:46 -07:00
|
|
|
switch w.Op {
|
|
|
|
|
case OpStoreWB, OpMoveWB, OpZeroWB:
|
|
|
|
|
start = i
|
2017-02-01 14:27:40 -05:00
|
|
|
if last == nil {
|
|
|
|
|
last = w
|
|
|
|
|
end = i + 1
|
2016-12-03 19:17:16 -05:00
|
|
|
}
|
2023-08-18 12:44:37 -07:00
|
|
|
nonPtrStores = 0
|
2025-01-10 17:33:26 -08:00
|
|
|
if w.Op == OpMoveWB {
|
|
|
|
|
hasMove = true
|
|
|
|
|
}
|
2022-07-22 15:08:07 -07:00
|
|
|
case OpVarDef, OpVarLive:
|
2017-04-27 11:56:46 -07:00
|
|
|
continue
|
2023-08-18 12:44:37 -07:00
|
|
|
case OpStore:
|
|
|
|
|
if last == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
nonPtrStores++
|
|
|
|
|
if nonPtrStores > 2 {
|
|
|
|
|
break FindSeq
|
|
|
|
|
}
|
2025-01-10 17:33:26 -08:00
|
|
|
if hasMove {
|
|
|
|
|
// We need to ensure that this store happens
|
|
|
|
|
// before we issue a wbMove, as the wbMove might
|
|
|
|
|
// use the result of this store as its source.
|
|
|
|
|
// Even though this store is not write-barrier
|
|
|
|
|
// eligible, it might nevertheless be the store
|
|
|
|
|
// of a pointer to the stack, which is then the
|
|
|
|
|
// source of the move.
|
|
|
|
|
// See issue 71228.
|
|
|
|
|
break FindSeq
|
|
|
|
|
}
|
2017-04-27 11:56:46 -07:00
|
|
|
default:
|
|
|
|
|
if last == nil {
|
|
|
|
|
continue
|
2016-10-13 06:57:00 -04:00
|
|
|
}
|
2017-04-27 11:56:46 -07:00
|
|
|
break FindSeq
|
2017-02-01 14:27:40 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
stores = append(stores[:0], b.Values[start:end]...) // copy to avoid aliasing
|
|
|
|
|
after = append(after[:0], b.Values[end:]...)
|
|
|
|
|
b.Values = b.Values[:start]
|
|
|
|
|
|
|
|
|
|
// find the memory before the WB stores
|
2017-03-03 13:44:18 -08:00
|
|
|
mem := stores[0].MemoryArg()
|
2017-02-01 14:27:40 -05:00
|
|
|
pos := stores[0].Pos
|
2022-11-01 14:18:09 -07:00
|
|
|
|
|
|
|
|
// If the source of a MoveWB is volatile (will be clobbered by a
|
|
|
|
|
// function call), we need to copy it to a temporary location, as
|
|
|
|
|
// marshaling the args of wbMove might clobber the value we're
|
|
|
|
|
// trying to move.
|
|
|
|
|
// Look for volatile source, copy it to temporary before we check
|
|
|
|
|
// the write barrier flag.
|
|
|
|
|
// It is unlikely to have more than one of them. Just do a linear
|
|
|
|
|
// search instead of using a map.
|
|
|
|
|
// See issue 15854.
|
|
|
|
|
type volatileCopy struct {
|
|
|
|
|
src *Value // address of original volatile value
|
|
|
|
|
tmp *Value // address of temporary we've copied the volatile value into
|
|
|
|
|
}
|
|
|
|
|
var volatiles []volatileCopy
|
|
|
|
|
|
|
|
|
|
if !(f.ABIDefault == f.ABI1 && len(f.Config.intParamRegs) >= 3) {
|
|
|
|
|
// We don't need to do this if the calls we're going to do take
|
|
|
|
|
// all their arguments in registers.
|
|
|
|
|
// 3 is the magic number because it covers wbZero, wbMove, cgoCheckMemmove.
|
|
|
|
|
copyLoop:
|
|
|
|
|
for _, w := range stores {
|
|
|
|
|
if w.Op == OpMoveWB {
|
|
|
|
|
val := w.Args[1]
|
|
|
|
|
if isVolatile(val) {
|
|
|
|
|
for _, c := range volatiles {
|
|
|
|
|
if val == c.src {
|
|
|
|
|
continue copyLoop // already copied
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t := val.Type.Elem()
|
2023-09-06 22:42:11 -07:00
|
|
|
tmp := f.NewLocal(w.Pos, t)
|
2022-11-01 14:18:09 -07:00
|
|
|
mem = b.NewValue1A(w.Pos, OpVarDef, types.TypeMem, tmp, mem)
|
|
|
|
|
tmpaddr := b.NewValue2A(w.Pos, OpLocalAddr, t.PtrTo(), tmp, sp, mem)
|
|
|
|
|
siz := t.Size()
|
|
|
|
|
mem = b.NewValue3I(w.Pos, OpMove, types.TypeMem, siz, tmpaddr, val, mem)
|
|
|
|
|
mem.Aux = t
|
|
|
|
|
volatiles = append(volatiles, volatileCopy{val, tmpaddr})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build branch point.
|
2017-02-01 14:27:40 -05:00
|
|
|
bThen := f.NewBlock(BlockPlain)
|
|
|
|
|
bEnd := f.NewBlock(b.Kind)
|
|
|
|
|
bThen.Pos = pos
|
|
|
|
|
bEnd.Pos = b.Pos
|
|
|
|
|
b.Pos = pos
|
|
|
|
|
|
2022-11-01 16:46:43 -07:00
|
|
|
// Set up control flow for end block.
|
2019-08-12 20:19:58 +01:00
|
|
|
bEnd.CopyControls(b)
|
2017-02-01 14:27:40 -05:00
|
|
|
bEnd.Likely = b.Likely
|
|
|
|
|
for _, e := range b.Succs {
|
|
|
|
|
bEnd.Succs = append(bEnd.Succs, e)
|
|
|
|
|
e.b.Preds[e.i].b = bEnd
|
|
|
|
|
}
|
2016-10-13 06:57:00 -04:00
|
|
|
|
2017-02-01 14:27:40 -05:00
|
|
|
// set up control flow for write barrier test
|
|
|
|
|
// load word, test word, avoiding partial register write from load byte.
|
cmd/compile: change ssa.Type into *types.Type
When package ssa was created, Type was in package gc.
To avoid circular dependencies, we used an interface (ssa.Type)
to represent type information in SSA.
In the Go 1.9 cycle, gri extricated the Type type from package gc.
As a result, we can now use it in package ssa.
Now, instead of package types depending on package ssa,
it is the other way.
This is a more sensible dependency tree,
and helps compiler performance a bit.
Though this is a big CL, most of the changes are
mechanical and uninteresting.
Interesting bits:
* Add new singleton globals to package types for the special
SSA types Memory, Void, Invalid, Flags, and Int128.
* Add two new Types, TSSA for the special types,
and TTUPLE, for SSA tuple types.
ssa.MakeTuple is now types.NewTuple.
* Move type comparison result constants CMPlt, CMPeq, and CMPgt
to package types.
* We had picked the name "types" in our rules for the handy
list of types provided by ssa.Config. That conflicted with
the types package name, so change it to "typ".
* Update the type comparison routine to handle tuples and special
types inline.
* Teach gc/fmt.go how to print special types.
* We can now eliminate ElemTypes in favor of just Elem,
and probably also some other duplicated Type methods
designed to return ssa.Type instead of *types.Type.
* The ssa tests were using their own dummy types,
and they were not particularly careful about types in general.
Of necessity, this CL switches them to use *types.Type;
it does not make them more type-accurate.
Unfortunately, using types.Type means initializing a bit
of the types universe.
This is prime for refactoring and improvement.
This shrinks ssa.Value; it now fits in a smaller size class
on 64 bit systems. This doesn't have a giant impact,
though, since most Values are preallocated in a chunk.
name old alloc/op new alloc/op delta
Template 37.9MB ± 0% 37.7MB ± 0% -0.57% (p=0.000 n=10+8)
Unicode 28.9MB ± 0% 28.7MB ± 0% -0.52% (p=0.000 n=10+10)
GoTypes 110MB ± 0% 109MB ± 0% -0.88% (p=0.000 n=10+10)
Flate 24.7MB ± 0% 24.6MB ± 0% -0.66% (p=0.000 n=10+10)
GoParser 31.1MB ± 0% 30.9MB ± 0% -0.61% (p=0.000 n=10+9)
Reflect 73.9MB ± 0% 73.4MB ± 0% -0.62% (p=0.000 n=10+8)
Tar 25.8MB ± 0% 25.6MB ± 0% -0.77% (p=0.000 n=9+10)
XML 41.2MB ± 0% 40.9MB ± 0% -0.80% (p=0.000 n=10+10)
[Geo mean] 40.5MB 40.3MB -0.68%
name old allocs/op new allocs/op delta
Template 385k ± 0% 386k ± 0% ~ (p=0.356 n=10+9)
Unicode 343k ± 1% 344k ± 0% ~ (p=0.481 n=10+10)
GoTypes 1.16M ± 0% 1.16M ± 0% -0.16% (p=0.004 n=10+10)
Flate 238k ± 1% 238k ± 1% ~ (p=0.853 n=10+10)
GoParser 320k ± 0% 320k ± 0% ~ (p=0.720 n=10+9)
Reflect 957k ± 0% 957k ± 0% ~ (p=0.460 n=10+8)
Tar 252k ± 0% 252k ± 0% ~ (p=0.133 n=9+10)
XML 400k ± 0% 400k ± 0% ~ (p=0.796 n=10+10)
[Geo mean] 428k 428k -0.01%
Removing all the interface calls helps non-trivially with CPU, though.
name old time/op new time/op delta
Template 178ms ± 4% 173ms ± 3% -2.90% (p=0.000 n=94+96)
Unicode 85.0ms ± 4% 83.9ms ± 4% -1.23% (p=0.000 n=96+96)
GoTypes 543ms ± 3% 528ms ± 3% -2.73% (p=0.000 n=98+96)
Flate 116ms ± 3% 113ms ± 4% -2.34% (p=0.000 n=96+99)
GoParser 144ms ± 3% 140ms ± 4% -2.80% (p=0.000 n=99+97)
Reflect 344ms ± 3% 334ms ± 4% -3.02% (p=0.000 n=100+99)
Tar 106ms ± 5% 103ms ± 4% -3.30% (p=0.000 n=98+94)
XML 198ms ± 5% 192ms ± 4% -2.88% (p=0.000 n=92+95)
[Geo mean] 178ms 173ms -2.65%
name old user-time/op new user-time/op delta
Template 229ms ± 5% 224ms ± 5% -2.36% (p=0.000 n=95+99)
Unicode 107ms ± 6% 106ms ± 5% -1.13% (p=0.001 n=93+95)
GoTypes 696ms ± 4% 679ms ± 4% -2.45% (p=0.000 n=97+99)
Flate 137ms ± 4% 134ms ± 5% -2.66% (p=0.000 n=99+96)
GoParser 176ms ± 5% 172ms ± 8% -2.27% (p=0.000 n=98+100)
Reflect 430ms ± 6% 411ms ± 5% -4.46% (p=0.000 n=100+92)
Tar 128ms ±13% 123ms ±13% -4.21% (p=0.000 n=100+100)
XML 239ms ± 6% 233ms ± 6% -2.50% (p=0.000 n=95+97)
[Geo mean] 220ms 213ms -2.76%
Change-Id: I15c7d6268347f8358e75066dfdbd77db24e8d0c1
Reviewed-on: https://go-review.googlesource.com/42145
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-04-28 14:12:28 -07:00
|
|
|
cfgtypes := &f.Config.Types
|
|
|
|
|
flag := b.NewValue2(pos, OpLoad, cfgtypes.UInt32, wbaddr, mem)
|
|
|
|
|
flag = b.NewValue2(pos, OpNeq32, cfgtypes.Bool, flag, const0)
|
2017-02-01 14:27:40 -05:00
|
|
|
b.Kind = BlockIf
|
|
|
|
|
b.SetControl(flag)
|
|
|
|
|
b.Likely = BranchUnlikely
|
|
|
|
|
b.Succs = b.Succs[:0]
|
|
|
|
|
b.AddEdgeTo(bThen)
|
2022-11-01 16:46:43 -07:00
|
|
|
b.AddEdgeTo(bEnd)
|
2017-02-01 14:27:40 -05:00
|
|
|
bThen.AddEdgeTo(bEnd)
|
|
|
|
|
|
2022-11-01 16:46:43 -07:00
|
|
|
// For each write barrier store, append write barrier code to bThen.
|
2017-02-01 14:27:40 -05:00
|
|
|
memThen := mem
|
2022-11-01 16:46:43 -07:00
|
|
|
var curCall *Value
|
|
|
|
|
var curPtr *Value
|
2023-08-21 14:10:24 -07:00
|
|
|
addEntry := func(pos src.XPos, v *Value) {
|
2022-11-01 16:46:43 -07:00
|
|
|
if curCall == nil || curCall.AuxInt == maxEntries {
|
|
|
|
|
t := types.NewTuple(types.Types[types.TUINTPTR].PtrTo(), types.TypeMem)
|
|
|
|
|
curCall = bThen.NewValue1(pos, OpWB, t, memThen)
|
|
|
|
|
curPtr = bThen.NewValue1(pos, OpSelect0, types.Types[types.TUINTPTR].PtrTo(), curCall)
|
|
|
|
|
memThen = bThen.NewValue1(pos, OpSelect1, types.TypeMem, curCall)
|
|
|
|
|
}
|
|
|
|
|
// Store value in write buffer
|
|
|
|
|
num := curCall.AuxInt
|
|
|
|
|
curCall.AuxInt = num + 1
|
|
|
|
|
wbuf := bThen.NewValue1I(pos, OpOffPtr, types.Types[types.TUINTPTR].PtrTo(), num*f.Config.PtrSize, curPtr)
|
|
|
|
|
memThen = bThen.NewValue3A(pos, OpStore, types.TypeMem, types.Types[types.TUINTPTR], wbuf, v, memThen)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Note: we can issue the write barrier code in any order. In particular,
|
|
|
|
|
// it doesn't matter if they are in a different order *even if* they end
|
|
|
|
|
// up referring to overlapping memory regions. For instance if an OpStore
|
|
|
|
|
// stores to a location that is later read by an OpMove. In all cases
|
|
|
|
|
// any pointers we must get into the write barrier buffer still make it,
|
|
|
|
|
// possibly in a different order and possibly a different (but definitely
|
|
|
|
|
// more than 0) number of times.
|
|
|
|
|
// In light of that, we process all the OpStoreWBs first. This minimizes
|
|
|
|
|
// the amount of spill/restore code we need around the Zero/Move calls.
|
|
|
|
|
|
|
|
|
|
// srcs contains the value IDs of pointer values we've put in the write barrier buffer.
|
|
|
|
|
srcs := sset
|
|
|
|
|
srcs.clear()
|
|
|
|
|
// dsts contains the value IDs of locations which we've read a pointer out of
|
|
|
|
|
// and put the result in the write barrier buffer.
|
|
|
|
|
dsts := sset2
|
|
|
|
|
dsts.clear()
|
|
|
|
|
|
|
|
|
|
for _, w := range stores {
|
|
|
|
|
if w.Op != OpStoreWB {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
pos := w.Pos
|
|
|
|
|
ptr := w.Args[0]
|
|
|
|
|
val := w.Args[1]
|
|
|
|
|
if !srcs.contains(val.ID) && needWBsrc(val) {
|
|
|
|
|
srcs.add(val.ID)
|
2023-08-21 14:10:24 -07:00
|
|
|
addEntry(pos, val)
|
2022-11-01 16:46:43 -07:00
|
|
|
}
|
|
|
|
|
if !dsts.contains(ptr.ID) && needWBdst(ptr, w.Args[2], zeroes) {
|
|
|
|
|
dsts.add(ptr.ID)
|
|
|
|
|
// Load old value from store target.
|
|
|
|
|
// Note: This turns bad pointer writes into bad
|
|
|
|
|
// pointer reads, which could be confusing. We could avoid
|
|
|
|
|
// reading from obviously bad pointers, which would
|
|
|
|
|
// take care of the vast majority of these. We could
|
|
|
|
|
// patch this up in the signal handler, or use XCHG to
|
|
|
|
|
// combine the read and the write.
|
|
|
|
|
oldVal := bThen.NewValue2(pos, OpLoad, types.Types[types.TUINTPTR], ptr, memThen)
|
|
|
|
|
// Save old value to write buffer.
|
2023-08-21 14:10:24 -07:00
|
|
|
addEntry(pos, oldVal)
|
2022-11-01 16:46:43 -07:00
|
|
|
}
|
2023-04-11 16:40:12 -04:00
|
|
|
f.fe.Func().SetWBPos(pos)
|
2022-11-01 16:46:43 -07:00
|
|
|
nWBops--
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-01 14:27:40 -05:00
|
|
|
for _, w := range stores {
|
2017-04-27 06:00:18 -07:00
|
|
|
pos := w.Pos
|
2017-02-01 14:27:40 -05:00
|
|
|
switch w.Op {
|
|
|
|
|
case OpZeroWB:
|
2022-11-01 14:18:09 -07:00
|
|
|
dst := w.Args[0]
|
|
|
|
|
typ := reflectdata.TypeLinksym(w.Aux.(*types.Type))
|
|
|
|
|
// zeroWB(&typ, dst)
|
|
|
|
|
taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb)
|
|
|
|
|
memThen = wbcall(pos, bThen, wbZero, sp, memThen, taddr, dst)
|
2023-04-11 16:40:12 -04:00
|
|
|
f.fe.Func().SetWBPos(pos)
|
2017-08-30 11:12:52 -04:00
|
|
|
nWBops--
|
2022-11-01 14:18:09 -07:00
|
|
|
case OpMoveWB:
|
|
|
|
|
dst := w.Args[0]
|
|
|
|
|
src := w.Args[1]
|
|
|
|
|
if isVolatile(src) {
|
|
|
|
|
for _, c := range volatiles {
|
|
|
|
|
if src == c.src {
|
|
|
|
|
src = c.tmp
|
|
|
|
|
break
|
2019-03-21 13:40:28 -04:00
|
|
|
}
|
|
|
|
|
}
|
2017-10-26 12:33:04 -04:00
|
|
|
}
|
2022-11-01 14:18:09 -07:00
|
|
|
typ := reflectdata.TypeLinksym(w.Aux.(*types.Type))
|
|
|
|
|
// moveWB(&typ, dst, src)
|
|
|
|
|
taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb)
|
|
|
|
|
memThen = wbcall(pos, bThen, wbMove, sp, memThen, taddr, dst, src)
|
2023-04-11 16:40:12 -04:00
|
|
|
f.fe.Func().SetWBPos(pos)
|
2022-11-01 14:18:09 -07:00
|
|
|
nWBops--
|
2017-04-27 11:56:46 -07:00
|
|
|
}
|
2022-11-01 14:18:09 -07:00
|
|
|
}
|
2022-11-01 16:46:43 -07:00
|
|
|
|
2022-11-01 14:18:09 -07:00
|
|
|
// merge memory
|
2022-11-01 16:46:43 -07:00
|
|
|
mem = bEnd.NewValue2(pos, OpPhi, types.TypeMem, mem, memThen)
|
2016-10-13 06:57:00 -04:00
|
|
|
|
2022-11-01 14:18:09 -07:00
|
|
|
// Do raw stores after merge point.
|
|
|
|
|
for _, w := range stores {
|
2023-08-21 14:10:24 -07:00
|
|
|
pos := w.Pos
|
2017-03-13 21:51:08 -04:00
|
|
|
switch w.Op {
|
|
|
|
|
case OpStoreWB:
|
2022-11-01 14:18:09 -07:00
|
|
|
ptr := w.Args[0]
|
|
|
|
|
val := w.Args[1]
|
2022-10-20 14:20:41 -07:00
|
|
|
if buildcfg.Experiment.CgoCheck2 {
|
|
|
|
|
// Issue cgo checking code.
|
2022-11-01 14:18:09 -07:00
|
|
|
mem = wbcall(pos, bEnd, cgoCheckPtrWrite, sp, mem, ptr, val)
|
2022-10-20 14:20:41 -07:00
|
|
|
}
|
2022-11-01 14:18:09 -07:00
|
|
|
mem = bEnd.NewValue3A(pos, OpStore, types.TypeMem, w.Aux, ptr, val, mem)
|
|
|
|
|
case OpZeroWB:
|
|
|
|
|
dst := w.Args[0]
|
|
|
|
|
mem = bEnd.NewValue2I(pos, OpZero, types.TypeMem, w.AuxInt, dst, mem)
|
|
|
|
|
mem.Aux = w.Aux
|
2017-03-13 21:51:08 -04:00
|
|
|
case OpMoveWB:
|
2022-11-01 14:18:09 -07:00
|
|
|
dst := w.Args[0]
|
|
|
|
|
src := w.Args[1]
|
|
|
|
|
if isVolatile(src) {
|
|
|
|
|
for _, c := range volatiles {
|
|
|
|
|
if src == c.src {
|
|
|
|
|
src = c.tmp
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-10-20 14:20:41 -07:00
|
|
|
if buildcfg.Experiment.CgoCheck2 {
|
|
|
|
|
// Issue cgo checking code.
|
2022-11-01 14:18:09 -07:00
|
|
|
typ := reflectdata.TypeLinksym(w.Aux.(*types.Type))
|
|
|
|
|
taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb)
|
|
|
|
|
mem = wbcall(pos, bEnd, cgoCheckMemmove, sp, mem, taddr, dst, src)
|
2022-10-20 14:20:41 -07:00
|
|
|
}
|
2022-11-01 14:18:09 -07:00
|
|
|
mem = bEnd.NewValue3I(pos, OpMove, types.TypeMem, w.AuxInt, dst, src, mem)
|
|
|
|
|
mem.Aux = w.Aux
|
2022-07-22 15:08:07 -07:00
|
|
|
case OpVarDef, OpVarLive:
|
2022-11-01 14:18:09 -07:00
|
|
|
mem = bEnd.NewValue1A(pos, w.Op, types.TypeMem, w.Aux, mem)
|
2023-08-18 12:44:37 -07:00
|
|
|
case OpStore:
|
|
|
|
|
ptr := w.Args[0]
|
|
|
|
|
val := w.Args[1]
|
|
|
|
|
mem = bEnd.NewValue3A(pos, OpStore, types.TypeMem, w.Aux, ptr, val, mem)
|
2017-02-01 14:27:40 -05:00
|
|
|
}
|
|
|
|
|
}
|
2016-10-13 06:57:00 -04:00
|
|
|
|
2022-10-09 19:06:23 -07:00
|
|
|
// The last store becomes the WBend marker. This marker is used by the liveness
|
|
|
|
|
// pass to determine what parts of the code are preemption-unsafe.
|
|
|
|
|
// All subsequent memory operations use this memory, so we have to sacrifice the
|
|
|
|
|
// previous last memory op to become this new value.
|
2017-02-01 14:27:40 -05:00
|
|
|
bEnd.Values = append(bEnd.Values, last)
|
|
|
|
|
last.Block = bEnd
|
2022-10-09 19:06:23 -07:00
|
|
|
last.reset(OpWBend)
|
2019-10-30 10:29:47 -07:00
|
|
|
last.Pos = last.Pos.WithNotStmt()
|
cmd/compile: change ssa.Type into *types.Type
When package ssa was created, Type was in package gc.
To avoid circular dependencies, we used an interface (ssa.Type)
to represent type information in SSA.
In the Go 1.9 cycle, gri extricated the Type type from package gc.
As a result, we can now use it in package ssa.
Now, instead of package types depending on package ssa,
it is the other way.
This is a more sensible dependency tree,
and helps compiler performance a bit.
Though this is a big CL, most of the changes are
mechanical and uninteresting.
Interesting bits:
* Add new singleton globals to package types for the special
SSA types Memory, Void, Invalid, Flags, and Int128.
* Add two new Types, TSSA for the special types,
and TTUPLE, for SSA tuple types.
ssa.MakeTuple is now types.NewTuple.
* Move type comparison result constants CMPlt, CMPeq, and CMPgt
to package types.
* We had picked the name "types" in our rules for the handy
list of types provided by ssa.Config. That conflicted with
the types package name, so change it to "typ".
* Update the type comparison routine to handle tuples and special
types inline.
* Teach gc/fmt.go how to print special types.
* We can now eliminate ElemTypes in favor of just Elem,
and probably also some other duplicated Type methods
designed to return ssa.Type instead of *types.Type.
* The ssa tests were using their own dummy types,
and they were not particularly careful about types in general.
Of necessity, this CL switches them to use *types.Type;
it does not make them more type-accurate.
Unfortunately, using types.Type means initializing a bit
of the types universe.
This is prime for refactoring and improvement.
This shrinks ssa.Value; it now fits in a smaller size class
on 64 bit systems. This doesn't have a giant impact,
though, since most Values are preallocated in a chunk.
name old alloc/op new alloc/op delta
Template 37.9MB ± 0% 37.7MB ± 0% -0.57% (p=0.000 n=10+8)
Unicode 28.9MB ± 0% 28.7MB ± 0% -0.52% (p=0.000 n=10+10)
GoTypes 110MB ± 0% 109MB ± 0% -0.88% (p=0.000 n=10+10)
Flate 24.7MB ± 0% 24.6MB ± 0% -0.66% (p=0.000 n=10+10)
GoParser 31.1MB ± 0% 30.9MB ± 0% -0.61% (p=0.000 n=10+9)
Reflect 73.9MB ± 0% 73.4MB ± 0% -0.62% (p=0.000 n=10+8)
Tar 25.8MB ± 0% 25.6MB ± 0% -0.77% (p=0.000 n=9+10)
XML 41.2MB ± 0% 40.9MB ± 0% -0.80% (p=0.000 n=10+10)
[Geo mean] 40.5MB 40.3MB -0.68%
name old allocs/op new allocs/op delta
Template 385k ± 0% 386k ± 0% ~ (p=0.356 n=10+9)
Unicode 343k ± 1% 344k ± 0% ~ (p=0.481 n=10+10)
GoTypes 1.16M ± 0% 1.16M ± 0% -0.16% (p=0.004 n=10+10)
Flate 238k ± 1% 238k ± 1% ~ (p=0.853 n=10+10)
GoParser 320k ± 0% 320k ± 0% ~ (p=0.720 n=10+9)
Reflect 957k ± 0% 957k ± 0% ~ (p=0.460 n=10+8)
Tar 252k ± 0% 252k ± 0% ~ (p=0.133 n=9+10)
XML 400k ± 0% 400k ± 0% ~ (p=0.796 n=10+10)
[Geo mean] 428k 428k -0.01%
Removing all the interface calls helps non-trivially with CPU, though.
name old time/op new time/op delta
Template 178ms ± 4% 173ms ± 3% -2.90% (p=0.000 n=94+96)
Unicode 85.0ms ± 4% 83.9ms ± 4% -1.23% (p=0.000 n=96+96)
GoTypes 543ms ± 3% 528ms ± 3% -2.73% (p=0.000 n=98+96)
Flate 116ms ± 3% 113ms ± 4% -2.34% (p=0.000 n=96+99)
GoParser 144ms ± 3% 140ms ± 4% -2.80% (p=0.000 n=99+97)
Reflect 344ms ± 3% 334ms ± 4% -3.02% (p=0.000 n=100+99)
Tar 106ms ± 5% 103ms ± 4% -3.30% (p=0.000 n=98+94)
XML 198ms ± 5% 192ms ± 4% -2.88% (p=0.000 n=92+95)
[Geo mean] 178ms 173ms -2.65%
name old user-time/op new user-time/op delta
Template 229ms ± 5% 224ms ± 5% -2.36% (p=0.000 n=95+99)
Unicode 107ms ± 6% 106ms ± 5% -1.13% (p=0.001 n=93+95)
GoTypes 696ms ± 4% 679ms ± 4% -2.45% (p=0.000 n=97+99)
Flate 137ms ± 4% 134ms ± 5% -2.66% (p=0.000 n=99+96)
GoParser 176ms ± 5% 172ms ± 8% -2.27% (p=0.000 n=98+100)
Reflect 430ms ± 6% 411ms ± 5% -4.46% (p=0.000 n=100+92)
Tar 128ms ±13% 123ms ±13% -4.21% (p=0.000 n=100+100)
XML 239ms ± 6% 233ms ± 6% -2.50% (p=0.000 n=95+97)
[Geo mean] 220ms 213ms -2.76%
Change-Id: I15c7d6268347f8358e75066dfdbd77db24e8d0c1
Reviewed-on: https://go-review.googlesource.com/42145
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-04-28 14:12:28 -07:00
|
|
|
last.Type = types.TypeMem
|
2022-10-09 19:06:23 -07:00
|
|
|
last.AddArg(mem)
|
|
|
|
|
|
|
|
|
|
// Free all the old stores, except last which became the WBend marker.
|
2017-02-01 14:27:40 -05:00
|
|
|
for _, w := range stores {
|
|
|
|
|
if w != last {
|
|
|
|
|
w.resetArgs()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, w := range stores {
|
|
|
|
|
if w != last {
|
|
|
|
|
f.freeValue(w)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// put values after the store sequence into the end block
|
|
|
|
|
bEnd.Values = append(bEnd.Values, after...)
|
|
|
|
|
for _, w := range after {
|
|
|
|
|
w.Block = bEnd
|
|
|
|
|
}
|
2016-10-13 06:57:00 -04:00
|
|
|
|
2017-02-01 14:27:40 -05:00
|
|
|
// if we have more stores in this block, do this block again
|
2017-08-30 11:12:52 -04:00
|
|
|
if nWBops > 0 {
|
|
|
|
|
goto again
|
2016-10-13 06:57:00 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-04 17:34:33 -08:00
|
|
|
// computeZeroMap returns a map from an ID of a memory value to
|
|
|
|
|
// a set of locations that are known to be zeroed at that memory value.
|
2023-01-09 09:49:32 -08:00
|
|
|
func (f *Func) computeZeroMap(select1 []*Value) map[ID]ZeroRegion {
|
|
|
|
|
|
2019-10-06 17:38:39 -07:00
|
|
|
ptrSize := f.Config.PtrSize
|
2019-01-04 17:34:33 -08:00
|
|
|
// Keep track of which parts of memory are known to be zero.
|
|
|
|
|
// This helps with removing write barriers for various initialization patterns.
|
|
|
|
|
// This analysis is conservative. We only keep track, for each memory state, of
|
2019-10-06 17:38:39 -07:00
|
|
|
// which of the first 64 words of a single object are known to be zero.
|
2019-01-04 17:34:33 -08:00
|
|
|
zeroes := map[ID]ZeroRegion{}
|
|
|
|
|
// Find new objects.
|
|
|
|
|
for _, b := range f.Blocks {
|
|
|
|
|
for _, v := range b.Values {
|
2023-01-09 09:49:32 -08:00
|
|
|
if mem, ok := IsNewObject(v, select1); ok {
|
cmd/compile: set LocalPkg.Path to -p flag
Since CL 391014, cmd/compile now requires the -p flag to be set the
build system. This CL changes it to initialize LocalPkg.Path to the
provided path, rather than relying on writing out `"".` into object
files and expecting cmd/link to substitute them.
However, this actually involved a rather long tail of fixes. Many have
already been submitted, but a few notable ones that have to land
simultaneously with changing LocalPkg:
1. When compiling package runtime, there are really two "runtime"
packages: types.LocalPkg (the source package itself) and
ir.Pkgs.Runtime (the compiler's internal representation, for synthetic
references). Previously, these ended up creating separate link
symbols (`"".xxx` and `runtime.xxx`, respectively), but now they both
end up as `runtime.xxx`, which causes lsym collisions (notably
inittask and funcsyms).
2. test/codegen tests need to be updated to expect symbols to be named
`command-line-arguments.xxx` rather than `"".foo`.
3. The issue20014 test case is sensitive to the sort order of field
tracking symbols. In particular, the local package now sorts to its
natural place in the list, rather than to the front.
Thanks to David Chase for helping track down all of the fixes needed
for this CL.
Updates #51734.
Change-Id: Iba3041cf7ad967d18c6e17922fa06ba11798b565
Reviewed-on: https://go-review.googlesource.com/c/go/+/393715
Reviewed-by: David Chase <drchase@google.com>
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2022-03-17 13:27:40 -07:00
|
|
|
// While compiling package runtime itself, we might see user
|
|
|
|
|
// calls to newobject, which will have result type
|
|
|
|
|
// unsafe.Pointer instead. We can't easily infer how large the
|
|
|
|
|
// allocated memory is, so just skip it.
|
|
|
|
|
if types.LocalPkg.Path == "runtime" && v.Type.IsUnsafePtr() {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-06 17:38:39 -07:00
|
|
|
nptr := v.Type.Elem().Size() / ptrSize
|
|
|
|
|
if nptr > 64 {
|
|
|
|
|
nptr = 64
|
|
|
|
|
}
|
|
|
|
|
zeroes[mem.ID] = ZeroRegion{base: v, mask: 1<<uint(nptr) - 1}
|
2019-01-04 17:34:33 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Find stores to those new objects.
|
|
|
|
|
for {
|
|
|
|
|
changed := false
|
|
|
|
|
for _, b := range f.Blocks {
|
|
|
|
|
// Note: iterating forwards helps convergence, as values are
|
|
|
|
|
// typically (but not always!) in store order.
|
|
|
|
|
for _, v := range b.Values {
|
|
|
|
|
if v.Op != OpStore {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
z, ok := zeroes[v.MemoryArg().ID]
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
ptr := v.Args[0]
|
|
|
|
|
var off int64
|
|
|
|
|
size := v.Aux.(*types.Type).Size()
|
|
|
|
|
for ptr.Op == OpOffPtr {
|
|
|
|
|
off += ptr.AuxInt
|
|
|
|
|
ptr = ptr.Args[0]
|
|
|
|
|
}
|
|
|
|
|
if ptr != z.base {
|
|
|
|
|
// Different base object - we don't know anything.
|
|
|
|
|
// We could even be writing to the base object we know
|
|
|
|
|
// about, but through an aliased but offset pointer.
|
|
|
|
|
// So we have to throw all the zero information we have away.
|
|
|
|
|
continue
|
|
|
|
|
}
|
2019-10-06 17:38:39 -07:00
|
|
|
// Round to cover any partially written pointer slots.
|
|
|
|
|
// Pointer writes should never be unaligned like this, but non-pointer
|
|
|
|
|
// writes to pointer-containing types will do this.
|
|
|
|
|
if d := off % ptrSize; d != 0 {
|
|
|
|
|
off -= d
|
|
|
|
|
size += d
|
2019-01-04 17:34:33 -08:00
|
|
|
}
|
2019-10-06 17:38:39 -07:00
|
|
|
if d := size % ptrSize; d != 0 {
|
|
|
|
|
size += ptrSize - d
|
|
|
|
|
}
|
|
|
|
|
// Clip to the 64 words that we track.
|
|
|
|
|
min := off
|
|
|
|
|
max := off + size
|
|
|
|
|
if min < 0 {
|
|
|
|
|
min = 0
|
|
|
|
|
}
|
|
|
|
|
if max > 64*ptrSize {
|
|
|
|
|
max = 64 * ptrSize
|
|
|
|
|
}
|
|
|
|
|
// Clear bits for parts that we are writing (and hence
|
|
|
|
|
// will no longer necessarily be zero).
|
|
|
|
|
for i := min; i < max; i += ptrSize {
|
|
|
|
|
bit := i / ptrSize
|
|
|
|
|
z.mask &^= 1 << uint(bit)
|
2019-01-04 17:34:33 -08:00
|
|
|
}
|
2019-10-06 17:38:39 -07:00
|
|
|
if z.mask == 0 {
|
|
|
|
|
// No more known zeros - don't bother keeping.
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// Save updated known zero contents for new store.
|
2019-01-04 17:34:33 -08:00
|
|
|
if zeroes[v.ID] != z {
|
|
|
|
|
zeroes[v.ID] = z
|
|
|
|
|
changed = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !changed {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-06 17:38:39 -07:00
|
|
|
if f.pass.debug > 0 {
|
|
|
|
|
fmt.Printf("func %s\n", f.Name)
|
|
|
|
|
for mem, z := range zeroes {
|
|
|
|
|
fmt.Printf(" memory=v%d ptr=%v zeromask=%b\n", mem, z.base, z.mask)
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-04 17:34:33 -08:00
|
|
|
return zeroes
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-13 06:57:00 -04:00
|
|
|
// wbcall emits write barrier runtime call in b, returns memory.
|
2022-11-01 14:18:09 -07:00
|
|
|
func wbcall(pos src.XPos, b *Block, fn *obj.LSym, sp, mem *Value, args ...*Value) *Value {
|
2016-10-13 06:57:00 -04:00
|
|
|
config := b.Func.Config
|
2022-11-01 14:18:09 -07:00
|
|
|
typ := config.Types.Uintptr // type of all argument values
|
|
|
|
|
nargs := len(args)
|
2016-10-13 06:57:00 -04:00
|
|
|
|
2021-03-29 18:46:37 -04:00
|
|
|
// TODO (register args) this is a bit of a hack.
|
|
|
|
|
inRegs := b.Func.ABIDefault == b.Func.ABI1 && len(config.intParamRegs) >= 3
|
|
|
|
|
|
2022-11-01 14:18:09 -07:00
|
|
|
if !inRegs {
|
|
|
|
|
// Store arguments to the appropriate stack slot.
|
|
|
|
|
off := config.ctxt.Arch.FixedFrameSize
|
|
|
|
|
for _, arg := range args {
|
|
|
|
|
stkaddr := b.NewValue1I(pos, OpOffPtr, typ.PtrTo(), off, sp)
|
|
|
|
|
mem = b.NewValue3A(pos, OpStore, types.TypeMem, typ, stkaddr, arg, mem)
|
|
|
|
|
off += typ.Size()
|
2021-03-29 18:46:37 -04:00
|
|
|
}
|
2022-11-01 14:18:09 -07:00
|
|
|
args = args[:0]
|
2016-10-13 06:57:00 -04:00
|
|
|
}
|
|
|
|
|
|
2022-11-01 14:18:09 -07:00
|
|
|
args = append(args, mem)
|
2016-10-13 06:57:00 -04:00
|
|
|
|
|
|
|
|
// issue call
|
2022-11-01 14:18:09 -07:00
|
|
|
argTypes := make([]*types.Type, nargs, 3) // at most 3 args; allows stack allocation
|
|
|
|
|
for i := 0; i < nargs; i++ {
|
|
|
|
|
argTypes[i] = typ
|
|
|
|
|
}
|
2023-08-20 15:37:34 -07:00
|
|
|
call := b.NewValue0A(pos, OpStaticCall, types.TypeResultMem, StaticAuxCall(fn, b.Func.ABIDefault.ABIAnalyzeTypes(argTypes, nil)))
|
2022-11-01 14:18:09 -07:00
|
|
|
call.AddArgs(args...)
|
|
|
|
|
call.AuxInt = int64(nargs) * typ.Size()
|
2021-03-29 18:46:37 -04:00
|
|
|
return b.NewValue1I(pos, OpSelectN, types.TypeMem, 0, call)
|
2016-10-13 06:57:00 -04:00
|
|
|
}
|
|
|
|
|
|
2022-11-11 19:22:35 +08:00
|
|
|
// round to a multiple of r, r is a power of 2.
|
2016-10-13 06:57:00 -04:00
|
|
|
func round(o int64, r int64) int64 {
|
|
|
|
|
return (o + r - 1) &^ (r - 1)
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-27 12:40:16 -08:00
|
|
|
// IsStackAddr reports whether v is known to be an address of a stack slot.
|
2017-03-17 11:53:24 -04:00
|
|
|
func IsStackAddr(v *Value) bool {
|
2016-10-13 06:57:00 -04:00
|
|
|
for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy {
|
|
|
|
|
v = v.Args[0]
|
|
|
|
|
}
|
|
|
|
|
switch v.Op {
|
cmd/compile: restore tail call for method wrappers
For certain type of method wrappers we used to generate a tail
call. That was disabled in CL 307234 when register ABI is used,
because with the current IR it was difficult to generate a tail
call with the arguments in the right places. The problem was that
the IR does not contain a CALL-like node with arguments; instead,
it contains an OAS node that adjusts the receiver, than an
OTAILCALL node that just contains the target, but no argument
(with the assumption that the OAS node will put the adjusted
receiver in the right place). With register ABI, putting
arguments in registers are done in SSA. The assignment (OAS)
doesn't put the receiver in register.
This CL changes the IR of a tail call to take an actual OCALL
node. Specifically, a tail call is represented as
OTAILCALL (OCALL target args...)
This way, the call target and args are connected through the OCALL
node. So the call can be analyzed in SSA and the args can be passed
in the right places.
(Alternatively, we could have OTAILCALL node directly take the
target and the args, without the OCALL node. Using an OCALL node is
convenient as there are existing code that processes OCALL nodes
which do not need to be changed. Also, a tail call is similar to
ORETURN (OCALL target args...), except it doesn't preserve the
frame. I did the former but I'm open to change.)
The SSA representation is similar. Previously, the IR lowers to
a Store the receiver then a BlockRetJmp which jumps to the target
(without putting the arg in register). Now we use a TailCall op,
which takes the target and the args. The call expansion pass and
the register allocator handles TailCall pretty much like a
StaticCall, and it will do the right ABI analysis and put the args
in the right places. (Args other than the receiver are already in
the right places. For register args it generates no code for them.
For stack args currently it generates a self copy. I'll work on
optimize that out.) BlockRetJmp is still used, signaling it is a
tail call. The actual call is made in the TailCall op so
BlockRetJmp generates no code (we could use BlockExit if we like).
This slightly reduces binary size:
old new
cmd/go 14003088 13953936
cmd/link 6275552 6271456
Change-Id: I2d16d8d419fe1f17554916d317427383e17e27f0
Reviewed-on: https://go-review.googlesource.com/c/go/+/350145
Trust: Cherry Mui <cherryyz@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Reviewed-by: David Chase <drchase@google.com>
2021-09-10 22:05:55 -04:00
|
|
|
case OpSP, OpLocalAddr, OpSelectNAddr, OpGetCallerSP:
|
2016-10-13 06:57:00 -04:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
2017-02-02 19:47:59 -05:00
|
|
|
|
2019-01-04 17:34:33 -08:00
|
|
|
// IsGlobalAddr reports whether v is known to be an address of a global (or nil).
|
2018-11-27 12:40:16 -08:00
|
|
|
func IsGlobalAddr(v *Value) bool {
|
2021-07-29 21:15:52 -07:00
|
|
|
for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy {
|
|
|
|
|
v = v.Args[0]
|
|
|
|
|
}
|
2019-01-04 17:34:33 -08:00
|
|
|
if v.Op == OpAddr && v.Args[0].Op == OpSB {
|
|
|
|
|
return true // address of a global
|
|
|
|
|
}
|
2019-09-04 17:45:50 -07:00
|
|
|
if v.Op == OpConstNil {
|
|
|
|
|
return true
|
2019-01-04 17:34:33 -08:00
|
|
|
}
|
2019-10-06 17:38:39 -07:00
|
|
|
if v.Op == OpLoad && IsReadOnlyGlobalAddr(v.Args[0]) {
|
|
|
|
|
return true // loading from a read-only global - the resulting address can't be a heap address.
|
|
|
|
|
}
|
2019-01-04 17:34:33 -08:00
|
|
|
return false
|
2018-11-27 12:40:16 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IsReadOnlyGlobalAddr reports whether v is known to be an address of a read-only global.
|
|
|
|
|
func IsReadOnlyGlobalAddr(v *Value) bool {
|
2019-09-04 17:45:50 -07:00
|
|
|
if v.Op == OpConstNil {
|
2019-08-02 10:36:03 -07:00
|
|
|
// Nil pointers are read only. See issue 33438.
|
|
|
|
|
return true
|
|
|
|
|
}
|
2022-10-09 18:43:34 -07:00
|
|
|
if v.Op == OpAddr && v.Aux != nil && v.Aux.(*obj.LSym).Type == objabi.SRODATA {
|
2019-10-06 17:38:39 -07:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
2018-11-27 12:40:16 -08:00
|
|
|
}
|
|
|
|
|
|
2021-04-08 17:46:21 -04:00
|
|
|
// IsNewObject reports whether v is a pointer to a freshly allocated & zeroed object,
|
|
|
|
|
// if so, also returns the memory state mem at which v is zero.
|
2023-01-09 09:49:32 -08:00
|
|
|
func IsNewObject(v *Value, select1 []*Value) (mem *Value, ok bool) {
|
2021-04-08 17:46:21 -04:00
|
|
|
f := v.Block.Func
|
|
|
|
|
c := f.Config
|
|
|
|
|
if f.ABIDefault == f.ABI1 && len(c.intParamRegs) >= 1 {
|
|
|
|
|
if v.Op != OpSelectN || v.AuxInt != 0 {
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
2023-01-09 09:49:32 -08:00
|
|
|
mem = select1[v.Args[0].ID]
|
2021-04-08 17:46:21 -04:00
|
|
|
if mem == nil {
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if v.Op != OpLoad {
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
mem = v.MemoryArg()
|
|
|
|
|
if mem.Op != OpSelectN {
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
if mem.Type != types.TypeMem {
|
|
|
|
|
return nil, false
|
|
|
|
|
} // assume it is the right selection if true
|
2018-11-27 12:40:16 -08:00
|
|
|
}
|
2021-04-08 17:46:21 -04:00
|
|
|
call := mem.Args[0]
|
|
|
|
|
if call.Op != OpStaticCall {
|
|
|
|
|
return nil, false
|
2021-02-04 16:42:35 -05:00
|
|
|
}
|
2021-04-08 17:46:21 -04:00
|
|
|
if !isSameCall(call.Aux, "runtime.newobject") {
|
|
|
|
|
return nil, false
|
2018-11-27 12:40:16 -08:00
|
|
|
}
|
2021-04-08 17:46:21 -04:00
|
|
|
if f.ABIDefault == f.ABI1 && len(c.intParamRegs) >= 1 {
|
|
|
|
|
if v.Args[0] == call {
|
|
|
|
|
return mem, true
|
|
|
|
|
}
|
|
|
|
|
return nil, false
|
2018-11-27 12:40:16 -08:00
|
|
|
}
|
|
|
|
|
if v.Args[0].Op != OpOffPtr {
|
2021-04-08 17:46:21 -04:00
|
|
|
return nil, false
|
2018-11-27 12:40:16 -08:00
|
|
|
}
|
|
|
|
|
if v.Args[0].Args[0].Op != OpSP {
|
2021-04-08 17:46:21 -04:00
|
|
|
return nil, false
|
2018-11-27 12:40:16 -08:00
|
|
|
}
|
2022-04-18 13:41:08 -04:00
|
|
|
if v.Args[0].AuxInt != c.ctxt.Arch.FixedFrameSize+c.RegSize { // offset of return value
|
2021-04-08 17:46:21 -04:00
|
|
|
return nil, false
|
2018-11-27 12:40:16 -08:00
|
|
|
}
|
2021-04-08 17:46:21 -04:00
|
|
|
return mem, true
|
2018-11-27 12:40:16 -08:00
|
|
|
}
|
|
|
|
|
|
2018-03-27 13:50:08 -07:00
|
|
|
// IsSanitizerSafeAddr reports whether v is known to be an address
|
|
|
|
|
// that doesn't need instrumentation.
|
|
|
|
|
func IsSanitizerSafeAddr(v *Value) bool {
|
|
|
|
|
for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy {
|
|
|
|
|
v = v.Args[0]
|
|
|
|
|
}
|
|
|
|
|
switch v.Op {
|
2020-07-27 16:46:35 -04:00
|
|
|
case OpSP, OpLocalAddr, OpSelectNAddr:
|
2018-03-27 13:50:08 -07:00
|
|
|
// Stack addresses are always safe.
|
|
|
|
|
return true
|
|
|
|
|
case OpITab, OpStringPtr, OpGetClosurePtr:
|
|
|
|
|
// Itabs, string data, and closure fields are
|
|
|
|
|
// read-only once initialized.
|
|
|
|
|
return true
|
|
|
|
|
case OpAddr:
|
2022-04-19 19:41:40 -04:00
|
|
|
vt := v.Aux.(*obj.LSym).Type
|
|
|
|
|
return vt == objabi.SRODATA || vt == objabi.SLIBFUZZER_8BIT_COUNTER || vt == objabi.SCOVERAGE_COUNTER || vt == objabi.SCOVERAGE_AUXVAR
|
2018-03-27 13:50:08 -07:00
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-27 12:40:16 -08:00
|
|
|
// isVolatile reports whether v is a pointer to argument region on stack which
|
2017-02-02 19:47:59 -05:00
|
|
|
// will be clobbered by a function call.
|
|
|
|
|
func isVolatile(v *Value) bool {
|
2020-07-27 16:46:35 -04:00
|
|
|
for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy || v.Op == OpSelectNAddr {
|
2017-02-02 19:47:59 -05:00
|
|
|
v = v.Args[0]
|
|
|
|
|
}
|
|
|
|
|
return v.Op == OpSP
|
|
|
|
|
}
|