Source file src/cmd/compile/internal/ssacompile/cpufeatures.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ssacompile
     6  
     7  import (
     8  	"fmt"
     9  	"internal/goarch"
    10  
    11  	"cmd/compile/internal/ssa"
    12  	"cmd/compile/internal/ssa/block"
    13  	"cmd/compile/internal/ssa/ssaop"
    14  	"cmd/compile/internal/types"
    15  	"cmd/internal/obj"
    16  )
    17  
    18  type localEffect struct {
    19  	start    ssa.CPUfeatures    // features present at beginning of block
    20  	internal ssa.CPUfeatures    // features implied by execution of block
    21  	end      [2]ssa.CPUfeatures // for BlockIf, features present on outgoing edges
    22  	visited  bool               // On the first iteration this will be false for backedges.
    23  }
    24  
    25  func (e localEffect) String() string {
    26  	return fmt.Sprintf("visited=%v, start=%v, internal=%v, end[0]=%v, end[1]=%v", e.visited, e.start, e.internal, e.end[0], e.end[1])
    27  }
    28  
    29  // ifEffect pattern matches for a BlockIf conditional on a load
    30  // of a field from internal/cpu.X86 and returns the corresponding
    31  // effect.
    32  func ifEffect(b *ssa.Block) (features ssa.CPUfeatures, taken int) {
    33  	// TODO generalize for other architectures.
    34  	if b.Kind != block.BlockIf {
    35  		return
    36  	}
    37  	c := b.Controls[0]
    38  
    39  	if c.Op == ssaop.OpNot {
    40  		taken = 1
    41  		c = c.Args[0]
    42  	}
    43  	if c.Op != ssaop.OpLoad {
    44  		return
    45  	}
    46  	offPtr := c.Args[0]
    47  	if offPtr.Op != ssaop.OpOffPtr {
    48  		return
    49  	}
    50  	addr := offPtr.Args[0]
    51  	if addr.Op != ssaop.OpAddr || addr.Args[0].Op != ssaop.OpSB {
    52  		return
    53  	}
    54  	sym := addr.Aux.(*obj.LSym)
    55  	if sym.Name != "internal/cpu.X86" {
    56  		return
    57  	}
    58  	o := offPtr.AuxInt
    59  	t := addr.Type
    60  	if !t.IsPtr() {
    61  		b.Func.Fatalf("The symbol %s is not a pointer, found %v instead", sym.Name, t)
    62  	}
    63  	t = t.Elem()
    64  	if !t.IsStruct() {
    65  		b.Func.Fatalf("The referent of symbol %s is not a struct, found %v instead", sym.Name, t)
    66  	}
    67  	match := ""
    68  	for _, f := range t.Fields() {
    69  		if o == f.Offset && f.Sym != nil {
    70  			match = f.Sym.Name
    71  			break
    72  		}
    73  	}
    74  
    75  	switch match {
    76  
    77  	case "HasAVX":
    78  		features = ssa.CPUavx
    79  	case "HasAVXVNNI":
    80  		features = ssa.CPUavx | ssa.CPUavxvnni
    81  	case "HasAVX2":
    82  		features = ssa.CPUavx2 | ssa.CPUavx
    83  
    84  		// Compiler currently treats these all alike.
    85  	case "HasAVX512", "HasAVX512F", "HasAVX512CD", "HasAVX512BW",
    86  		"HasAVX512DQ", "HasAVX512VL", "HasAVX512VPCLMULQDQ":
    87  		features = ssa.CPUavx512 | ssa.CPUavx2 | ssa.CPUavx
    88  
    89  	case "HasAVX512GFNI":
    90  		features = ssa.CPUavx512 | ssa.CPUgfni | ssa.CPUavx2 | ssa.CPUavx
    91  	case "HasAVX512VNNI":
    92  		features = ssa.CPUavx512 | ssa.CPUavx512vnni | ssa.CPUavx2 | ssa.CPUavx
    93  	case "HasAVX512VBMI":
    94  		features = ssa.CPUavx512 | ssa.CPUvbmi | ssa.CPUavx2 | ssa.CPUavx
    95  	case "HasAVX512VBMI2":
    96  		features = ssa.CPUavx512 | ssa.CPUvbmi2 | ssa.CPUavx2 | ssa.CPUavx
    97  	case "HasAVX512BITALG":
    98  		features = ssa.CPUavx512 | ssa.CPUbitalg | ssa.CPUavx2 | ssa.CPUavx
    99  	case "HasAVX512VPOPCNTDQ":
   100  		features = ssa.CPUavx512 | ssa.CPUvpopcntdq | ssa.CPUavx2 | ssa.CPUavx
   101  
   102  	case "HasBMI1":
   103  		features = ssa.CPUvbmi
   104  	case "HasBMI2":
   105  		features = ssa.CPUvbmi2
   106  
   107  		// Features that are not currently interesting to the compiler.
   108  	case "HasAES", "HasADX", "HasERMS", "HasFSRM", "HasFMA", "HasGFNI", "HasOSXSAVE",
   109  		"HasPCLMULQDQ", "HasPOPCNT", "HasRDTSCP", "HasSHA",
   110  		"HasSSE3", "HasSSSE3", "HasSSE41", "HasSSE42":
   111  
   112  	}
   113  	if b.Func.Pass.Debug > 2 {
   114  		b.Func.Warnl(b.Pos, "%s, block b%v has features offset %d, match is %s, features is %v", b.Func.Name, b.ID, o, match, features)
   115  	}
   116  	return
   117  }
   118  
   119  func cpufeatures(f *ssa.Func) {
   120  	arch := f.Config.Ctxt.Arch.Family
   121  	// TODO there are other SIMD architectures
   122  	if arch != goarch.AMD64 {
   123  		return
   124  	}
   125  
   126  	po := f.Postorder()
   127  
   128  	effects := make([]localEffect, 1+f.NumBlocks(), 1+f.NumBlocks())
   129  
   130  	features := func(t *types.Type) ssa.CPUfeatures {
   131  		if t.IsSIMD() {
   132  			switch t.Size() {
   133  			case 16, 32:
   134  				return ssa.CPUavx
   135  			case 64:
   136  				return ssa.CPUavx512 | ssa.CPUavx2 | ssa.CPUavx
   137  			}
   138  		}
   139  		return ssa.CPUNone
   140  	}
   141  
   142  	// visit blocks in reverse post order
   143  	// when b is visited, all of its predecessors (except for loop back edges)
   144  	// will have been visited
   145  	for i := len(po) - 1; i >= 0; i-- {
   146  		b := po[i]
   147  
   148  		var feat ssa.CPUfeatures
   149  
   150  		if b == f.Entry {
   151  			// Check the types of inputs and outputs, as well as annotations.
   152  			// Start with none and union all that is implied by all the types seen.
   153  			if f.Type != nil { // a problem for SSA tests
   154  				for _, field := range f.Type.RecvParamsResults() {
   155  					feat |= features(field.Type)
   156  				}
   157  			}
   158  
   159  		} else {
   160  			// Start with all and intersect over predecessors
   161  			feat = ssa.CPUAll
   162  			for _, p := range b.Preds {
   163  				pb := p.Block()
   164  				if !effects[pb.ID].visited {
   165  
   166  					continue
   167  				}
   168  				pi := p.Index()
   169  				if pb.Kind != block.BlockIf {
   170  					pi = 0
   171  				}
   172  
   173  				feat &= effects[pb.ID].end[pi]
   174  			}
   175  		}
   176  
   177  		e := localEffect{start: feat, visited: true}
   178  
   179  		// Separately capture the internal effects of this block
   180  		var internal ssa.CPUfeatures
   181  		for _, v := range b.Values {
   182  			// the rule applied here is, if the block contains any
   183  			// instruction that would fault if the feature (avx, avx512)
   184  			// were not present, then assume that the feature is present
   185  			// for all the instructions in the block, a fault is a fault.
   186  			t := v.Type
   187  			if t.IsResults() {
   188  				for i := 0; i < t.NumFields(); i++ {
   189  					feat |= features(t.FieldType(i))
   190  				}
   191  			} else {
   192  				internal |= features(v.Type)
   193  			}
   194  		}
   195  		e.internal = internal
   196  		feat |= internal
   197  
   198  		branchEffect, taken := ifEffect(b)
   199  		e.end = [2]ssa.CPUfeatures{feat, feat}
   200  		e.end[taken] |= branchEffect
   201  
   202  		effects[b.ID] = e
   203  		if f.Pass.Debug > 1 && feat != ssa.CPUNone {
   204  			f.Warnl(b.Pos, "%s, block b%v has features %v", b.Func.Name, b.ID, feat)
   205  		}
   206  
   207  		b.CPUfeatures = feat
   208  		f.MaxCPUFeatures |= feat // not necessary to refine this estimate below
   209  	}
   210  
   211  	// If the flow graph is irreducible, things can still change on backedges.
   212  	change := true
   213  	for change {
   214  		change = false
   215  		for i := len(po) - 1; i >= 0; i-- {
   216  			b := po[i]
   217  
   218  			if b == f.Entry {
   219  				continue // cannot change
   220  			}
   221  			feat := ssa.CPUAll
   222  			for _, p := range b.Preds {
   223  				pb := p.Block()
   224  				pi := p.Index()
   225  				if pb.Kind != block.BlockIf {
   226  					pi = 0
   227  				}
   228  				feat &= effects[pb.ID].end[pi]
   229  			}
   230  			e := effects[b.ID]
   231  			if feat == e.start {
   232  				continue
   233  			}
   234  			e.start = feat
   235  			effects[b.ID] = e
   236  			// uh-oh, something changed
   237  			if f.Pass.Debug > 1 {
   238  				f.Warnl(b.Pos, "%s, block b%v saw predecessor feature change", b.Func.Name, b.ID)
   239  			}
   240  
   241  			feat |= e.internal
   242  			if feat == e.end[0]&e.end[1] {
   243  				continue
   244  			}
   245  
   246  			branchEffect, taken := ifEffect(b)
   247  			e.end = [2]ssa.CPUfeatures{feat, feat}
   248  			e.end[taken] |= branchEffect
   249  
   250  			effects[b.ID] = e
   251  			b.CPUfeatures = feat
   252  			if f.Pass.Debug > 1 {
   253  				f.Warnl(b.Pos, "%s, block b%v has new features %v", b.Func.Name, b.ID, feat)
   254  			}
   255  			change = true
   256  		}
   257  	}
   258  	if f.Pass.Debug > 0 {
   259  		for _, b := range f.Blocks {
   260  			if b.CPUfeatures != ssa.CPUNone {
   261  				f.Warnl(b.Pos, "%s, block b%v has features %v", b.Func.Name, b.ID, b.CPUfeatures)
   262  			}
   263  
   264  		}
   265  	}
   266  }
   267  

View as plain text