Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/cmd/compile/internal/ssa/_gen/RISCV64.rules
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,14 @@
(SRLI [x] (MOVHUreg y)) && x >= 16 => (MOVDconst [0])
(SRLI [x] (MOVWUreg y)) && x >= 32 => (MOVDconst [0])

// Remove unnecessary masking with shifts.
(SLL x (ANDI [63] y)) => (SLL x y)
(SRL x (ANDI [63] y)) => (SRL x y)
(SRA x (ANDI [63] y)) => (SRA x y)
(SLLW x (ANDI [31] y)) => (SLLW x y)
(SRLW x (ANDI [31] y)) => (SRLW x y)
(SRAW x (ANDI [31] y)) => (SRAW x y)

// Fold constant into immediate instructions where possible.
(ADD (MOVDconst <t> [val]) x) && ssa.Is32Bit(val) && !t.IsPtr() => (ADDI [val] x)
(AND (MOVDconst [val]) x) && ssa.Is32Bit(val) => (ANDI [val] x)
Expand Down
17 changes: 9 additions & 8 deletions src/cmd/compile/internal/ssa/dfplus_iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@ import (

// IterDomFrontierPlus iterates the DF+ of seeds: every block at which
// a phi may need to be placed if a variable were defined in the seed
// blocks. Blocks are yielded at most once, in a deterministic order;
// an early break stops the walk. Seed blocks themselves are not
// yielded as such, but a seed that is also a merge point (e.g. a loop
// header) is.
// blocks. For each frontier block, it also yields the block whose
// outgoing edge discovered the frontier. Frontier blocks are yielded
// at most once, in a deterministic order; an early break stops the walk.
// Seed blocks themselves are not yielded as such, but a seed that is
// also a merge point (e.g. a loop header) is.
// seeds iterator is consumed in full before the walk starts (the current
// algorithm has to walk deeper roots first).
// CFG must not change while iteration is in progress; inserting
// values (like phis) is fine.
func (f *Func) IterDomFrontierPlus(seeds iter.Seq[*Block]) iter.Seq[*Block] {
return func(yield func(*Block) bool) {
func (f *Func) IterDomFrontierPlus(seeds iter.Seq[*Block]) iter.Seq2[*Block, *Block] {
return func(yield func(*Block, *Block) bool) {
// Materialize the seeds into a pooled slice reused by walkDFPlus.
s := f.Cache.AllocBlockSlice(f.NumBlocks())[:0]
defer f.Cache.FreeBlockSlice(s[:cap(s)])
Expand Down Expand Up @@ -61,7 +62,7 @@ const (
// plus the frontier found, and memory is O(f.NumBlocks()). The walk reads
// the CFG's edges and uses the cached dominator tree.
// The seeds slice is reused in place by the PiggyBank.
func (f *Func) walkDFPlus(seeds []*Block, yield func(*Block) bool) {
func (f *Func) walkDFPlus(seeds []*Block, yield func(*Block, *Block) bool) {
sdom := f.Sdom()

// Roots to process, deepest first.
Expand Down Expand Up @@ -120,7 +121,7 @@ func (f *Func) walkDFPlus(seeds []*Block, yield func(*Block) bool) {
flags[c.ID] |= flagPiggyBanked
heap.Push(&piggyBank, c)
}
if !yield(c) {
if !yield(c, b) {
return
}
}
Expand Down
24 changes: 23 additions & 1 deletion src/cmd/compile/internal/ssa/dfplus_iter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,29 @@ func TestIterDomFrontierPlusSeedAtMerge(t *testing.T) {
}
}

func collectBlockIDs(seq iter.Seq[*Block]) []ID {
func TestIterDomFrontierPlusOrigin(t *testing.T) {
f := (&Config{}).NewFunc(nil, &Cache{})
entry := f.NewBlock(block.BlockIf)
f.Entry = entry
left := f.NewBlock(block.BlockPlain)
right := f.NewBlock(block.BlockPlain)
merge := f.NewBlock(block.BlockExit)

entry.AddEdgeTo(left)
entry.AddEdgeTo(right)
left.AddEdgeTo(merge)
right.AddEdgeTo(merge)

var got [][2]*Block
for b, origin := range f.IterDomFrontierPlus(slices.Values([]*Block{left})) {
got = append(got, [2]*Block{b, origin})
}
if want := [][2]*Block{{merge, left}}; !slices.Equal(got, want) {
t.Fatalf("got frontier and origin %v, want %v", got, want)
}
}

func collectBlockIDs(seq iter.Seq2[*Block, *Block]) []ID {
var ids []ID
for b := range seq {
ids = append(ids, b.ID)
Expand Down
20 changes: 20 additions & 0 deletions src/cmd/compile/internal/ssacompile/cpufeatures.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@ func ifEffect(b *ssa.Block) (features ssa.CPUfeatures, taken int) {
return
}

// noCodeValue reports whether v is known to emit no machine code, so
// that even a SIMD type does not imply the presence of any CPU feature.
func noCodeValue(v *ssa.Value) bool {
switch v.Op {
case ssaop.OpZeroSIMD, ssaop.OpPhi, ssaop.OpCopy, ssaop.OpSelectN,
ssaop.OpArg, ssaop.OpArgIntReg, ssaop.OpArgFloatReg:
return true
}
return false
}

func cpufeatures(f *ssa.Func) {
arch := f.Config.Ctxt.Arch.Family
// TODO there are other SIMD architectures
Expand Down Expand Up @@ -183,6 +194,15 @@ func cpufeatures(f *ssa.Func) {
// instruction that would fault if the feature (avx, avx512)
// were not present, then assume that the feature is present
// for all the instructions in the block, a fault is a fault.
if noCodeValue(v) {
// v emits no instruction, so its type implies
// nothing about the CPU. In particular a
// SIMD-typed zero is just a reference to the
// fixed all-zeros register and flows freely
// through code that must run on machines
// without AVX.
continue
}
t := v.Type
if t.IsResults() {
for i := 0; i < t.NumFields(); i++ {
Expand Down
19 changes: 19 additions & 0 deletions src/cmd/compile/internal/ssacompile/critical.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,39 @@ func critical(f *ssa.Func) {
// the new blocks to be re-examined.
d = f.NewBlock(block.BlockPlain)
d.Pos = p.Pos
// CPU features are execution-invariant facts: d
// executes only after p and unconditionally jumps
// to b, so both blocks' features hold in d.
d.CPUfeatures = p.CPUfeatures | b.CPUfeatures
blocks[argID] = d
if f.Pass.Debug > 0 {
f.Warnl(p.Pos, "split critical edge")
if d.CPUfeatures != ssa.CPUNone {
f.Warnl(p.Pos, "split-edge block b%d has features %v", d.ID, d.CPUfeatures)
}
}
} else {
reusedBlock = true
// d gains another predecessor, so only the
// features common to all of its predecessors
// (plus b's) are still guaranteed.
d.CPUfeatures &= p.CPUfeatures | b.CPUfeatures
if f.Pass.Debug > 0 && d.CPUfeatures != ssa.CPUNone {
f.Warnl(p.Pos, "reused split-edge block b%d has features %v", d.ID, d.CPUfeatures)
}
}
} else {
// no existing block, so allocate a new block
// to place on the edge
d = f.NewBlock(block.BlockPlain)
d.Pos = p.Pos
// See above for why d inherits these features.
d.CPUfeatures = p.CPUfeatures | b.CPUfeatures
if f.Pass.Debug > 0 {
f.Warnl(p.Pos, "split critical edge")
if d.CPUfeatures != ssa.CPUNone {
f.Warnl(p.Pos, "split-edge block b%d has features %v", d.ID, d.CPUfeatures)
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/cmd/compile/internal/ssagen/phi.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,9 @@ func (s *phiState) insertPhis() {

func (s *phiState) insertVarPhis(n int, var_ ir.Node, defs []*ssa.Block, typ *types.Type) {
// Iterate DF+ of the defining blocks.
for c := range s.f.IterDomFrontierPlus(slices.Values(defs)) {
for c, b := range s.f.IterDomFrontierPlus(slices.Values(defs)) {
// Add a phi to block c for variable n.
v := c.NewValue0I(s.s.blockStarts[c.ID], ssaop.OpPhi, typ, int64(n))
v := c.NewValue0I(s.s.blockStarts[b.ID], ssaop.OpPhi, typ, int64(n))
// Note: we store the variable number in the phi's AuxInt field. Used temporarily by phi building.
if var_.Op() == ir.ONAME {
s.s.addNamedValue(var_.(*ir.Name), v)
Expand Down

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

14 changes: 7 additions & 7 deletions src/cmd/go/go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2228,19 +2228,19 @@ func TestTestCache(t *testing.T) {
// Changing the actual package should have limited effects.
tg.tempFile("src/p1/p1.go", "package p1\nvar X = 02\n")
tg.run("test", "-p=1", "-x", "-v", "-short", "t/...")

// p2 should have been rebuilt.
tg.grepStderr(`([\\/]compile|gccgo).*p2.go`, "did not recompile p2")
// p2 should not have been rebuilt.
tg.grepStderrNot(`([\\/]compile|gccgo).*p2.go`, "incorrectly recompiled p2")

// t1 does not import anything, should not have been rebuilt.
tg.grepStderrNot(`([\\/]compile|gccgo).*t1_test.go`, "incorrectly recompiled t1")
tg.grepStderrNot(`([\\/]link|gccgo).*t1_test`, "incorrectly relinked t1_test")
tg.grepStdout(`ok \tt/t1\t\(cached\)`, "did not cache t/t1")

// t2 imports p1 and must be rebuilt and relinked,
// but the change should not have any effect on the test binary,
// t2 imports p1 and it must not be rebuilt because p1's export data
// didn't change but must be relinked because p1's object data did.
// The change should not have any effect on the test binary,
// so the test should not have been rerun.
tg.grepStderr(`([\\/]compile|gccgo).*t2_test.go`, "did not recompile t2")
tg.grepStderrNot(`([\\/]compile|gccgo).*t2_test.go`, "incorrectly recompiled t2")
tg.grepStderr(`([\\/]link|gccgo).*t2\.test`, "did not relink t2_test")
// This check does not currently work with gccgo, as garbage
// collection of unused variables is not turned on by default.
Expand All @@ -2249,7 +2249,7 @@ func TestTestCache(t *testing.T) {
}

// t3 imports p1, and changing X changes t3's test binary.
tg.grepStderr(`([\\/]compile|gccgo).*t3_test.go`, "did not recompile t3")
tg.grepStderrNot(`([\\/]compile|gccgo).*t3_test.go`, "incorrectly recompiled t3")
tg.grepStderr(`([\\/]link|gccgo).*t3\.test`, "did not relink t3_test")
tg.grepStderr(`t3\.test.*-test.short`, "did not rerun t3_test")
tg.grepStdoutNot(`ok \tt/t3\t\(cached\)`, "reported cached t3_test result")
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/go/internal/work/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ type Action struct {
}

// BuildActionID returns the action ID section of a's build ID.
func (a *Action) BuildActionID() string { return actionID(a.buildID) }
func (a *Action) BuildActionID() string { return buildActionID(a.buildID) }

// BuildContentID returns the content ID section of a's build ID.
func (a *Action) BuildContentID() string { return contentID(a.buildID) }
func (a *Action) BuildContentID() string { return buildObjectID(a.buildID) }

// BuildID returns a's build ID.
func (a *Action) BuildID() string { return a.buildID }
Expand Down
Loading
Loading